mirror of
https://github.com/balena-io/balena-cli.git
synced 2025-06-25 02:47:55 +00:00
Compare commits
96 Commits
v12.55.5
...
output-fra
Author | SHA1 | Date | |
---|---|---|---|
ab1d8aa6ba | |||
d2330f9ed1 | |||
cc19b00998 | |||
ed5ac75a10 | |||
465b8a1b5e | |||
eccadbdcb9 | |||
31eb734af1 | |||
fa7b59d64f | |||
1e42bfa0d5 | |||
5464e550e7 | |||
c0f27a663d | |||
d1c61c62ab | |||
a9691bff57 | |||
f5d09a43cd | |||
d11e547e11 | |||
bd462aee02 | |||
f633c0468b | |||
e4f61a1242 | |||
96142a002e | |||
6b9a5cd89c | |||
ba2d3d60ec | |||
d1e66bc1a5 | |||
58799915a9 | |||
5f2d55f569 | |||
8d6e51391c | |||
8454b02988 | |||
879d98ef98 | |||
c4e317a290 | |||
7ca4d2d720 | |||
e1e88ec56d | |||
33f7fa3829 | |||
3d516e7c5f | |||
a8507508b7 | |||
008972b3d3 | |||
92b86330a0 | |||
2563c07c6a | |||
1d4b949cf3 | |||
d17e02a930 | |||
a355cbaa79 | |||
bd021c0a2d | |||
a80f676804 | |||
f723c58089 | |||
e27a4e2e31 | |||
b91b72c408 | |||
5cf84d3f1d | |||
7d58b8c120 | |||
851301a336 | |||
ec6fd050f6 | |||
6f81053882 | |||
dbd8a9a08c | |||
256f1abf1b | |||
acd352cb3c | |||
31f927c27c | |||
3d0f16168a | |||
b2d932afab | |||
398175f0b3 | |||
2fb9c6c773 | |||
66608b32e9 | |||
c403683edf | |||
1e6ab46ca3 | |||
02d3220f2d | |||
c86cdc8f84 | |||
84f02dc063 | |||
9145f2fb28 | |||
1164388d78 | |||
06f6094401 | |||
67e11467f7 | |||
c8dfd0ca65 | |||
8b110a835a | |||
7564d95f82 | |||
f12f2b79ef | |||
176d731f9e | |||
1ed39d1d37 | |||
580ca0d584 | |||
73572df7cf | |||
23b42b1a2b | |||
632322e3c2 | |||
4faa5d7f57 | |||
9b967592a9 | |||
e01483cd2b | |||
6d89ff4bbf | |||
126e731117 | |||
32d26ad074 | |||
2bcfec9d0f | |||
c04e63ab7d | |||
79be06820c | |||
ffb94c380f | |||
385b5e9ec6 | |||
8d3a4343cb | |||
6eeb16245b | |||
3961060f90 | |||
a6dfc9126a | |||
e7ddd07b7b | |||
fea351d960 | |||
40e0b2dbed | |||
3def4d0e4a |
File diff suppressed because it is too large
Load Diff
543
CHANGELOG.md
543
CHANGELOG.md
@ -4,6 +4,549 @@ All notable changes to this project will be documented in this file
|
|||||||
automatically by Versionist. DO NOT EDIT THIS FILE MANUALLY!
|
automatically by Versionist. DO NOT EDIT THIS FILE MANUALLY!
|
||||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||||
|
|
||||||
|
## 13.1.13 - 2022-02-10
|
||||||
|
|
||||||
|
* Drop unused awaitDevice utility function [Lucian Buzzo]
|
||||||
|
|
||||||
|
## 13.1.12 - 2022-02-09
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary> preload: Stop using the deprecated /device-types/v1 API endpoints [Thodoris Greasidis] </summary>
|
||||||
|
|
||||||
|
> ### balena-preload-12.0.0 - 2022-01-27
|
||||||
|
>
|
||||||
|
> * Improve types [Thodoris Greasidis]
|
||||||
|
> * Stop relying on the /device-types/v1 endpoints [Thodoris Greasidis]
|
||||||
|
> * Bump TypeScript to v4.5 [Thodoris Greasidis]
|
||||||
|
>
|
||||||
|
> <details>
|
||||||
|
> <summary> Bump balena-sdk to v16 [Thodoris Greasidis] </summary>
|
||||||
|
>
|
||||||
|
>> #### balena-sdk-16.0.0 - 2021-11-28
|
||||||
|
>>
|
||||||
|
>> * **BREAKING**: Merge the hostApp model into the OS model [Thodoris Greasidis]
|
||||||
|
>> * **BREAKING** Drop os.getSupportedVersions() method in favor of hostapp.getAvailableOsVersions() [Thodoris Greasidis]
|
||||||
|
>> * os.getMaxSatisfyingVersion: Add optional param to choose OS line type [Thodoris Greasidis]
|
||||||
|
>> * os.getMaxSatisfyingVersion: Include ESR versions [Thodoris Greasidis]
|
||||||
|
>> * os.getMaxSatisfyingVersion: Switch to use hostApps [Thodoris Greasidis]
|
||||||
|
>> * hostapp.getAvailableOsVersions: Add single device type argument overload [Thodoris Greasidis]
|
||||||
|
>> * hostapp.getAllOsVersions: Add single device type argument overload [Thodoris Greasidis]
|
||||||
|
>> * models.hostapp: Add a getAvailableOsVersions() convenience method [Thodoris Greasidis]
|
||||||
|
>> * Support optional extra PineOptions in hostapp.getAllOsVersions() [Thodoris Greasidis]
|
||||||
|
>> * **BREAKING** Include invalidated versions in hostapp.getAllOsVersions() [Thodoris Greasidis]
|
||||||
|
>> * models/application: Add getDirectlyAccessible & getAllDirectlyAccessible [Thodoris Greasidis]
|
||||||
|
>> * application.get: Add 'directly_accessible' convenience filter param [Thodoris Greasidis]
|
||||||
|
>> * application.getAll: Add 'directly_accessible' convenience filter param [Thodoris Greasidis]
|
||||||
|
>> * **BREAKING** Change application.getAll to include public apps [Thodoris Greasidis]
|
||||||
|
>> * **BREAKING** Drop targeting/retrieving apps by name in favor of slugs [Thodoris Greasidis]
|
||||||
|
>> * Bump minimum supported Typescript to v4.5.2 [Thodoris Greasidis]
|
||||||
|
>> * **BREAKING**: Stop actively supporting node 10 [Thodoris Greasidis]
|
||||||
|
>> * **BREAKING** Drop application.getAllWithDeviceServiceDetails() [Thodoris Greasidis]
|
||||||
|
>> * **BREAKING** Change apiKey.getAll() to return all key variants [Thodoris Greasidis]
|
||||||
|
>> * types: Drop is_in_local_mode from the Device model [Thodoris Greasidis]
|
||||||
|
>> * types: Drop user__is_member_of__application in favor of the term form [Thodoris Greasidis]
|
||||||
|
>> * typings: Drop Subscription's discounts__plan_addon property [Thodoris Greasidis]
|
||||||
|
>> * typings: Stop extending the JWTUser type in the User model [Thodoris Greasidis]
|
||||||
|
>> * models/config: Change the BETA device type state to NEW [Thodoris Greasidis]
|
||||||
|
>> * typings: Drop the PineWithSelectOnGet type [Thodoris Greasidis]
|
||||||
|
>> * Remove my_application from the supported resources [Thodoris Greasidis]
|
||||||
|
>> * typings: Properly type some Device properties [Thodoris Greasidis]
|
||||||
|
>> * typings: Drop the DeviceWithImageInstalls type [Thodoris Greasidis]
|
||||||
|
>>
|
||||||
|
>> #### balena-sdk-15.59.2 - 2021-11-28
|
||||||
|
>>
|
||||||
|
>>
|
||||||
|
>> <details>
|
||||||
|
>> <summary> Update balena-request to 11.5.0 [Thodoris Greasidis] </summary>
|
||||||
|
>>
|
||||||
|
>>> ##### balena-request-11.5.0 - 2021-11-28
|
||||||
|
>>>
|
||||||
|
>>> * Convert tests to JavaScript and drop coffeescript [Thodoris Greasidis]
|
||||||
|
>>> * Fix the jsdoc generation [Thodoris Greasidis]
|
||||||
|
>>> * Convert to typescript and publish typings [Thodoris Greasidis]
|
||||||
|
>>>
|
||||||
|
>> </details>
|
||||||
|
>>
|
||||||
|
>>
|
||||||
|
>> #### balena-sdk-15.59.1 - 2021-11-28
|
||||||
|
>>
|
||||||
|
>> * Fix the typings of the Image contract field [Thodoris Greasidis]
|
||||||
|
>> * Fix the typings for the Release contract field [Thodoris Greasidis]
|
||||||
|
>>
|
||||||
|
>> #### balena-sdk-15.59.0 - 2021-11-24
|
||||||
|
>>
|
||||||
|
>> * Add release setIsInvalidated function [Matthew Yarmolinsky]
|
||||||
|
>>
|
||||||
|
>> #### balena-sdk-15.58.1 - 2021-11-17
|
||||||
|
>>
|
||||||
|
>> * Update typescript to 4.5.2 [Thodoris Greasidis]
|
||||||
|
>>
|
||||||
|
>> #### balena-sdk-15.58.0 - 2021-11-16
|
||||||
|
>>
|
||||||
|
>> * models/release: Add note() method [Thodoris Greasidis]
|
||||||
|
>> * typings: Add the release.invalidation_reason property [Thodoris Greasidis]
|
||||||
|
>> * typings: Add the release.note property [Thodoris Greasidis]
|
||||||
|
>>
|
||||||
|
>> #### balena-sdk-15.57.2 - 2021-11-15
|
||||||
|
>>
|
||||||
|
>> * tests/logs: Increase the wait time for retrieving the subscribed logs [Thodoris Greasidis]
|
||||||
|
>> * tests/logs: Refactor to async-await [Thodoris Greasidis]
|
||||||
|
>>
|
||||||
|
>> #### balena-sdk-15.57.1 - 2021-11-11
|
||||||
|
>>
|
||||||
|
>> * typings: Fix $filters for resources with non numeric ids [Thodoris Greasidis]
|
||||||
|
>> * typings: Add application.can_use__application_as_host ReverseNavigation [Thodoris Greasidis]
|
||||||
|
>> * Add missing apiKey.getDeviceApiKeysByDevice docs [Thodoris Greasidis]
|
||||||
|
>>
|
||||||
|
>> #### balena-sdk-15.57.0 - 2021-11-05
|
||||||
|
>>
|
||||||
|
>> * models/api-key: Change update() & revoke() to work with all key variants [Thodoris Greasidis]
|
||||||
|
>>
|
||||||
|
>> #### balena-sdk-15.56.0 - 2021-11-04
|
||||||
|
>>
|
||||||
|
>> * models/apiKey: Add getDeviceApiKeysByDevice() method [Thodoris Greasidis]
|
||||||
|
>>
|
||||||
|
>> #### balena-sdk-15.55.0 - 2021-11-01
|
||||||
|
>>
|
||||||
|
>> * typings: Add the release.raw_version property [Thodoris Greasidis]
|
||||||
|
>>
|
||||||
|
>> #### balena-sdk-15.54.2 - 2021-10-25
|
||||||
|
>>
|
||||||
|
>> * application/create: Rely on the hostApps for detecting discontinued DTs [Thodoris Greasidis]
|
||||||
|
>>
|
||||||
|
>> #### balena-sdk-15.54.1 - 2021-10-22
|
||||||
|
>>
|
||||||
|
>> * tests/device: Async-await conversions & abstraction on multi-field tests [Thodoris Greasidis]
|
||||||
|
>>
|
||||||
|
>> #### balena-sdk-15.54.0 - 2021-10-20
|
||||||
|
>>
|
||||||
|
>> * tests: Register devices in chunks of 10 to avoid uuid conflicts in node [Thodoris Greasidis]
|
||||||
|
>> * Add known issue check on release isReccomanded logic [JSReds]
|
||||||
|
>> * Add known_issue_list to hostApp.getOsVersions() [JSReds]
|
||||||
|
>>
|
||||||
|
>> #### balena-sdk-15.53.0 - 2021-10-07
|
||||||
|
>>
|
||||||
|
>> * Add support for batch device supervisor updates [Thodoris Greasidis]
|
||||||
|
>>
|
||||||
|
>> #### balena-sdk-15.52.0 - 2021-10-06
|
||||||
|
>>
|
||||||
|
>> * Add support for batch device pinning to release [Thodoris Greasidis]
|
||||||
|
>>
|
||||||
|
>> #### balena-sdk-15.51.4 - 2021-09-28
|
||||||
|
>>
|
||||||
|
>> * auth.isLoggedIn: Treat BalenaExpiredToken errors as logged out indicator [Thodoris Greasidis]
|
||||||
|
>>
|
||||||
|
>> #### balena-sdk-15.51.3 - 2021-09-28
|
||||||
|
>>
|
||||||
|
>> * Convert application spec to TypeScript [Thodoris Greasidis]
|
||||||
|
>>
|
||||||
|
>> #### balena-sdk-15.51.2 - 2021-09-28
|
||||||
|
>>
|
||||||
|
>> * application.trackLatestRelease: Fix using draft/invalidated releases [Thodoris Greasidis]
|
||||||
|
>> * application.isTrackingLatestRelease: Exclude draft&invalidated releases [Thodoris Greasidis]
|
||||||
|
>>
|
||||||
|
>> #### balena-sdk-15.51.1 - 2021-09-20
|
||||||
|
>>
|
||||||
|
>>
|
||||||
|
>> <details>
|
||||||
|
>> <summary> Update balena-request to v11.4.2 [Kyle Harding] </summary>
|
||||||
|
>>
|
||||||
|
>>> ##### balena-request-11.4.2 - 2021-09-20
|
||||||
|
>>>
|
||||||
|
>>> * Allow overriding the default zlib flush setting [Kyle Harding]
|
||||||
|
>>>
|
||||||
|
>> </details>
|
||||||
|
>>
|
||||||
|
>>
|
||||||
|
>> #### balena-sdk-15.51.0 - 2021-09-16
|
||||||
|
>>
|
||||||
|
>> * os.getConfig: Add typings for the provisioningKeyName option [Nitish Agarwal]
|
||||||
|
>>
|
||||||
|
>> #### balena-sdk-15.50.1 - 2021-09-13
|
||||||
|
>>
|
||||||
|
>> * models/os: Always first normalize the device type slug [Thodoris Greasidis]
|
||||||
|
>>
|
||||||
|
>> #### balena-sdk-15.50.0 - 2021-09-10
|
||||||
|
>>
|
||||||
|
>> * Add release.finalize to promote draft releases to final [toochevere]
|
||||||
|
>>
|
||||||
|
>> #### balena-sdk-15.49.1 - 2021-09-10
|
||||||
|
>>
|
||||||
|
>> * typings: Drop the v5-model-only application_type.is_host_os [Thodoris Greasidis]
|
||||||
|
>>
|
||||||
|
>> #### balena-sdk-15.49.0 - 2021-09-06
|
||||||
|
>>
|
||||||
|
>> * os.getSupportedOsUpdateVersions: Use the hostApp releases [Thodoris Greasidis]
|
||||||
|
>> * os.download: Use the hostApp for finding the latest release [Thodoris Greasidis]
|
||||||
|
>>
|
||||||
|
>> #### balena-sdk-15.48.3 - 2021-08-27
|
||||||
|
>>
|
||||||
|
>>
|
||||||
|
>> <details>
|
||||||
|
>> <summary> Update balena-request to 11.4.1 [Kyle Harding] </summary>
|
||||||
|
>>
|
||||||
|
>>> ##### balena-request-11.4.1 - 2021-08-27
|
||||||
|
>>>
|
||||||
|
>>> * Allow more lenient gzip decompression [Kyle Harding]
|
||||||
|
>>>
|
||||||
|
>> </details>
|
||||||
|
>>
|
||||||
|
>>
|
||||||
|
>> #### balena-sdk-15.48.2 - 2021-08-27
|
||||||
|
>>
|
||||||
|
>> * Improve hostapp.getAllOsVersions performance & reduce fetched data [Thodoris Greasidis]
|
||||||
|
>>
|
||||||
|
>> #### balena-sdk-15.48.1 - 2021-08-27
|
||||||
|
>>
|
||||||
|
>> * Update typescript to 4.4.2 [Thodoris Greasidis]
|
||||||
|
>>
|
||||||
|
>> #### balena-sdk-15.48.0 - 2021-08-15
|
||||||
|
>>
|
||||||
|
>> * Deprecate the release.release_version property [Thodoris Greasidis]
|
||||||
|
>> * typings: Add the release versioning properties [Thodoris Greasidis]
|
||||||
|
>>
|
||||||
|
>> #### balena-sdk-15.47.1 - 2021-08-10
|
||||||
|
>>
|
||||||
|
>> * Run browser tests using the minified browser bundle [Thodoris Greasidis]
|
||||||
|
>> * Move to uglify-js to fix const assignment bug in minified build [Thodoris Greasidis]
|
||||||
|
>>
|
||||||
|
>> #### balena-sdk-15.47.0 - 2021-08-09
|
||||||
|
>>
|
||||||
|
>> * typings: Add the release.is_final & is_finalized_at__date properties [Thodoris Greasidis]
|
||||||
|
>>
|
||||||
|
>> #### balena-sdk-15.46.1 - 2021-07-28
|
||||||
|
>>
|
||||||
|
>> * apiKey.getAll: Return only NamedUserApiKeys for backwards compatibility [Thodoris Greasidis]
|
||||||
|
>>
|
||||||
|
>> #### balena-sdk-15.46.0 - 2021-07-27
|
||||||
|
>>
|
||||||
|
>> * Add email verification & email request methods [Nitish Agarwal]
|
||||||
|
>>
|
||||||
|
>> #### balena-sdk-15.45.0 - 2021-07-26
|
||||||
|
>>
|
||||||
|
>> * Update generateProvisioningKey to include keyName [Nitish Agarwal]
|
||||||
|
>>
|
||||||
|
> </details>
|
||||||
|
>
|
||||||
|
>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## 13.1.11 - 2022-01-19
|
||||||
|
|
||||||
|
* chore: lib/auth/utils.ts: Replace deprecated url.resolve, use async/await [Paulo Castro]
|
||||||
|
* chore: Update @types/node to v12.20.42 [Paulo Castro]
|
||||||
|
|
||||||
|
## 13.1.10 - 2022-01-16
|
||||||
|
|
||||||
|
* Update docs and package.json re min Node.js supported version (12.8.0) [Paulo Castro]
|
||||||
|
|
||||||
|
## 13.1.9 - 2022-01-14
|
||||||
|
|
||||||
|
* Update packages in response to `colors` package issues [Scott Lowe]
|
||||||
|
|
||||||
|
## 13.1.8 - 2022-01-11
|
||||||
|
|
||||||
|
* local push: Fix "invalid character '/' looking for beginning of value" [Paulo Castro]
|
||||||
|
* v14 preparations: Fix TypeError produced by 'npx oclif manifest' [Paulo Castro]
|
||||||
|
|
||||||
|
## 13.1.7 - 2022-01-06
|
||||||
|
|
||||||
|
* Update to pkg 5 [Pagan Gazzard]
|
||||||
|
|
||||||
|
## 13.1.6 - 2022-01-04
|
||||||
|
|
||||||
|
* Automation: enforce noImplicitAny for the type-checked javascript [Pagan Gazzard]
|
||||||
|
|
||||||
|
## 13.1.5 - 2022-01-04
|
||||||
|
|
||||||
|
* Build: switch from using inline-source via gulp to using it directly [Pagan Gazzard]
|
||||||
|
|
||||||
|
## 13.1.4 - 2022-01-03
|
||||||
|
|
||||||
|
* Update pkg [Pagan Gazzard]
|
||||||
|
|
||||||
|
## 13.1.3 - 2022-01-03
|
||||||
|
|
||||||
|
* Convert lib/utils/deploy-legacy to typescript [Pagan Gazzard]
|
||||||
|
|
||||||
|
## 13.1.2 - 2022-01-03
|
||||||
|
|
||||||
|
* Convert lib/utils/compose to typescript [Pagan Gazzard]
|
||||||
|
|
||||||
|
## 13.1.1 - 2021-12-30
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary> Update dependencies [Pagan Gazzard] </summary>
|
||||||
|
|
||||||
|
> ### docker-progress-5.0.1 - 2021-09-22
|
||||||
|
>
|
||||||
|
> * Fix for bad progress values from some registries causing a crash [Paul Jonathan Zoulin]
|
||||||
|
>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## 13.1.0 - 2021-12-29
|
||||||
|
|
||||||
|
* os configure, config generate: Add '--dev' option for OS developmentMode [Paulo Castro]
|
||||||
|
* local configure: Allow configuring 'developmentMode' in config.json [Paulo Castro]
|
||||||
|
* os build-config: Clarify command purpose in help output [Paulo Castro]
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary> device os-update: Add support for unified dev/prod balenaOS releases [Paulo Castro] </summary>
|
||||||
|
|
||||||
|
> ### balena-sdk-16.9.0 - 2021-12-24
|
||||||
|
>
|
||||||
|
> * Support upgrading .dev to unified OS releases [Thodoris Greasidis]
|
||||||
|
>
|
||||||
|
> ### balena-sdk-16.8.2 - 2021-12-24
|
||||||
|
>
|
||||||
|
> * tests: Stop using mochainon [Thodoris Greasidis]
|
||||||
|
>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## 13.0.2 - 2021-12-24
|
||||||
|
|
||||||
|
* Update oclif [Pagan Gazzard]
|
||||||
|
|
||||||
|
## 13.0.1 - 2021-12-24
|
||||||
|
|
||||||
|
* os versions, os download: Replace deprecated version fields [Paulo Castro]
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary> Update balena-sdk to v16.8.1 [Paulo Castro] </summary>
|
||||||
|
|
||||||
|
> ### balena-sdk-16.8.1 - 2021-12-23
|
||||||
|
>
|
||||||
|
> * logs: Stop using the querystring module [Thodoris Greasidis]
|
||||||
|
>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## 13.0.0 - 2021-12-23
|
||||||
|
|
||||||
|
* v13 RELEASE NOTES: see https://git.io/JDHxG [Paulo Castro]
|
||||||
|
* balena SDK v16: Ensure all SDK calls use fleet slug rather than name [Paulo Castro]
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary> Update balena-sdk to v16.8.0 [Paulo Castro] </summary>
|
||||||
|
|
||||||
|
> ### balena-sdk-16.8.0 - 2021-12-22
|
||||||
|
>
|
||||||
|
> * os.getConfig: Accept additional developmentMode configuration option [Thodoris Greasidis]
|
||||||
|
>
|
||||||
|
> ### balena-sdk-16.7.0 - 2021-12-22
|
||||||
|
>
|
||||||
|
> * os.download: Fix the inferred method typings [Thodoris Greasidis]
|
||||||
|
> * os.download: Accept additional configuration options [Thodoris Greasidis]
|
||||||
|
>
|
||||||
|
> ### balena-sdk-16.6.0 - 2021-12-22
|
||||||
|
>
|
||||||
|
> * models.os: Use the native hostApp OS release version if it is set [Thodoris Greasidis]
|
||||||
|
> * models.os: Deprecate OsVersion.rawVersion in favor or raw_version [Thodoris Greasidis]
|
||||||
|
>
|
||||||
|
> ### balena-sdk-16.5.0 - 2021-12-22
|
||||||
|
>
|
||||||
|
> * os.getAllOsVersions: Add support for invariant OS releases [Thodoris Greasidis]
|
||||||
|
>
|
||||||
|
> ### balena-sdk-16.4.1 - 2021-12-21
|
||||||
|
>
|
||||||
|
> * os.getMaxSatisfyingVersion: Add ">" semver range tests [Thodoris Greasidis]
|
||||||
|
>
|
||||||
|
> ### balena-sdk-16.4.0 - 2021-12-21
|
||||||
|
>
|
||||||
|
> * os.getMaxSatisfyingVersion: Add support for ESR releases [Thodoris Greasidis]
|
||||||
|
>
|
||||||
|
> ### balena-sdk-16.3.0 - 2021-12-21
|
||||||
|
>
|
||||||
|
> * application.getAppByName: Add 'directly_accessible' convenience filter [Thodoris Greasidis]
|
||||||
|
>
|
||||||
|
> ### balena-sdk-16.2.3 - 2021-12-17
|
||||||
|
>
|
||||||
|
> * FIx the return type of config.getConfigVarSchema() [Thodoris Greasidis]
|
||||||
|
>
|
||||||
|
> ### balena-sdk-16.2.2 - 2021-12-17
|
||||||
|
>
|
||||||
|
> * os.getAvailableOsVersions: Exclude draft and non-successful releases [Thodoris Greasidis]
|
||||||
|
> * os.getAllOsVersions: Deprecate OsVersion.isRecommended [Thodoris Greasidis]
|
||||||
|
> * os.getAllOsVersions: Deprecate OsVersion.formattedVersion [Thodoris Greasidis]
|
||||||
|
>
|
||||||
|
> ### balena-sdk-16.2.1 - 2021-12-17
|
||||||
|
>
|
||||||
|
> * Drop require-npm4-to-publish [Thodoris Greasidis]
|
||||||
|
>
|
||||||
|
> ### balena-sdk-16.2.0 - 2021-12-17
|
||||||
|
>
|
||||||
|
> * minor: Add Configuration Variables Schema method [Vipul Gupta (@vipulgupta2048)]
|
||||||
|
>
|
||||||
|
> ### balena-sdk-16.1.0 - 2021-12-08
|
||||||
|
>
|
||||||
|
> * Add description field to generateProvisioningKey for apps. [Nitish Agarwal]
|
||||||
|
>
|
||||||
|
> ### balena-sdk-16.0.0 - 2021-11-28
|
||||||
|
>
|
||||||
|
> * **BREAKING**: Merge the hostApp model into the OS model [Thodoris Greasidis]
|
||||||
|
> * **BREAKING** Drop os.getSupportedVersions() method in favor of hostapp.getAvailableOsVersions() [Thodoris Greasidis]
|
||||||
|
> * os.getMaxSatisfyingVersion: Add optional param to choose OS line type [Thodoris Greasidis]
|
||||||
|
> * os.getMaxSatisfyingVersion: Include ESR versions [Thodoris Greasidis]
|
||||||
|
> * os.getMaxSatisfyingVersion: Switch to use hostApps [Thodoris Greasidis]
|
||||||
|
> * hostapp.getAvailableOsVersions: Add single device type argument overload [Thodoris Greasidis]
|
||||||
|
> * hostapp.getAllOsVersions: Add single device type argument overload [Thodoris Greasidis]
|
||||||
|
> * models.hostapp: Add a getAvailableOsVersions() convenience method [Thodoris Greasidis]
|
||||||
|
> * Support optional extra PineOptions in hostapp.getAllOsVersions() [Thodoris Greasidis]
|
||||||
|
> * **BREAKING** Include invalidated versions in hostapp.getAllOsVersions() [Thodoris Greasidis]
|
||||||
|
> * models/application: Add getDirectlyAccessible & getAllDirectlyAccessible [Thodoris Greasidis]
|
||||||
|
> * application.get: Add 'directly_accessible' convenience filter param [Thodoris Greasidis]
|
||||||
|
> * application.getAll: Add 'directly_accessible' convenience filter param [Thodoris Greasidis]
|
||||||
|
> * **BREAKING** Change application.getAll to include public apps [Thodoris Greasidis]
|
||||||
|
> * **BREAKING** Drop targeting/retrieving apps by name in favor of slugs [Thodoris Greasidis]
|
||||||
|
> * Bump minimum supported Typescript to v4.5.2 [Thodoris Greasidis]
|
||||||
|
> * **BREAKING**: Stop actively supporting node 10 [Thodoris Greasidis]
|
||||||
|
> * **BREAKING** Drop application.getAllWithDeviceServiceDetails() [Thodoris Greasidis]
|
||||||
|
> * **BREAKING** Change apiKey.getAll() to return all key variants [Thodoris Greasidis]
|
||||||
|
> * types: Drop is_in_local_mode from the Device model [Thodoris Greasidis]
|
||||||
|
> * types: Drop user__is_member_of__application in favor of the term form [Thodoris Greasidis]
|
||||||
|
> * typings: Drop Subscription's discounts__plan_addon property [Thodoris Greasidis]
|
||||||
|
> * typings: Stop extending the JWTUser type in the User model [Thodoris Greasidis]
|
||||||
|
> * models/config: Change the BETA device type state to NEW [Thodoris Greasidis]
|
||||||
|
> * typings: Drop the PineWithSelectOnGet type [Thodoris Greasidis]
|
||||||
|
> * Remove my_application from the supported resources [Thodoris Greasidis]
|
||||||
|
> * typings: Properly type some Device properties [Thodoris Greasidis]
|
||||||
|
> * typings: Drop the DeviceWithImageInstalls type [Thodoris Greasidis]
|
||||||
|
>
|
||||||
|
> ### balena-sdk-15.59.2 - 2021-11-28
|
||||||
|
>
|
||||||
|
>
|
||||||
|
> <details>
|
||||||
|
> <summary> Update balena-request to 11.5.0 [Thodoris Greasidis] </summary>
|
||||||
|
>
|
||||||
|
>> #### balena-request-11.5.0 - 2021-11-28
|
||||||
|
>>
|
||||||
|
>> * Convert tests to JavaScript and drop coffeescript [Thodoris Greasidis]
|
||||||
|
>> * Fix the jsdoc generation [Thodoris Greasidis]
|
||||||
|
>> * Convert to typescript and publish typings [Thodoris Greasidis]
|
||||||
|
>>
|
||||||
|
> </details>
|
||||||
|
>
|
||||||
|
>
|
||||||
|
> ### balena-sdk-15.59.1 - 2021-11-28
|
||||||
|
>
|
||||||
|
> * Fix the typings of the Image contract field [Thodoris Greasidis]
|
||||||
|
> * Fix the typings for the Release contract field [Thodoris Greasidis]
|
||||||
|
>
|
||||||
|
> ### balena-sdk-15.59.0 - 2021-11-24
|
||||||
|
>
|
||||||
|
> * Add release setIsInvalidated function [Matthew Yarmolinsky]
|
||||||
|
>
|
||||||
|
> ### balena-sdk-15.58.1 - 2021-11-17
|
||||||
|
>
|
||||||
|
> * Update typescript to 4.5.2 [Thodoris Greasidis]
|
||||||
|
>
|
||||||
|
> ### balena-sdk-15.58.0 - 2021-11-16
|
||||||
|
>
|
||||||
|
> * models/release: Add note() method [Thodoris Greasidis]
|
||||||
|
> * typings: Add the release.invalidation_reason property [Thodoris Greasidis]
|
||||||
|
> * typings: Add the release.note property [Thodoris Greasidis]
|
||||||
|
>
|
||||||
|
> ### balena-sdk-15.57.2 - 2021-11-15
|
||||||
|
>
|
||||||
|
> * tests/logs: Increase the wait time for retrieving the subscribed logs [Thodoris Greasidis]
|
||||||
|
> * tests/logs: Refactor to async-await [Thodoris Greasidis]
|
||||||
|
>
|
||||||
|
> ### balena-sdk-15.57.1 - 2021-11-11
|
||||||
|
>
|
||||||
|
> * typings: Fix $filters for resources with non numeric ids [Thodoris Greasidis]
|
||||||
|
> * typings: Add application.can_use__application_as_host ReverseNavigation [Thodoris Greasidis]
|
||||||
|
> * Add missing apiKey.getDeviceApiKeysByDevice docs [Thodoris Greasidis]
|
||||||
|
>
|
||||||
|
> ### balena-sdk-15.57.0 - 2021-11-05
|
||||||
|
>
|
||||||
|
> * models/api-key: Change update() & revoke() to work with all key variants [Thodoris Greasidis]
|
||||||
|
>
|
||||||
|
> ### balena-sdk-15.56.0 - 2021-11-04
|
||||||
|
>
|
||||||
|
> * models/apiKey: Add getDeviceApiKeysByDevice() method [Thodoris Greasidis]
|
||||||
|
>
|
||||||
|
> ### balena-sdk-15.55.0 - 2021-11-01
|
||||||
|
>
|
||||||
|
> * typings: Add the release.raw_version property [Thodoris Greasidis]
|
||||||
|
>
|
||||||
|
> ### balena-sdk-15.54.2 - 2021-10-25
|
||||||
|
>
|
||||||
|
> * application/create: Rely on the hostApps for detecting discontinued DTs [Thodoris Greasidis]
|
||||||
|
>
|
||||||
|
> ### balena-sdk-15.54.1 - 2021-10-22
|
||||||
|
>
|
||||||
|
> * tests/device: Async-await conversions & abstraction on multi-field tests [Thodoris Greasidis]
|
||||||
|
>
|
||||||
|
> ### balena-sdk-15.54.0 - 2021-10-20
|
||||||
|
>
|
||||||
|
> * tests: Register devices in chunks of 10 to avoid uuid conflicts in node [Thodoris Greasidis]
|
||||||
|
> * Add known issue check on release isReccomanded logic [JSReds]
|
||||||
|
> * Add known_issue_list to hostApp.getOsVersions() [JSReds]
|
||||||
|
>
|
||||||
|
> ### balena-sdk-15.53.0 - 2021-10-07
|
||||||
|
>
|
||||||
|
> * Add support for batch device supervisor updates [Thodoris Greasidis]
|
||||||
|
>
|
||||||
|
> ### balena-sdk-15.52.0 - 2021-10-06
|
||||||
|
>
|
||||||
|
> * Add support for batch device pinning to release [Thodoris Greasidis]
|
||||||
|
>
|
||||||
|
> ### balena-sdk-15.51.4 - 2021-09-28
|
||||||
|
>
|
||||||
|
> * auth.isLoggedIn: Treat BalenaExpiredToken errors as logged out indicator [Thodoris Greasidis]
|
||||||
|
>
|
||||||
|
> ### balena-sdk-15.51.3 - 2021-09-28
|
||||||
|
>
|
||||||
|
> * Convert application spec to TypeScript [Thodoris Greasidis]
|
||||||
|
>
|
||||||
|
> ### balena-sdk-15.51.2 - 2021-09-28
|
||||||
|
>
|
||||||
|
> * application.trackLatestRelease: Fix using draft/invalidated releases [Thodoris Greasidis]
|
||||||
|
> * application.isTrackingLatestRelease: Exclude draft&invalidated releases [Thodoris Greasidis]
|
||||||
|
>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
* device, devices: Print the fleet's slug in 'org/fleetName' format [Paulo Castro]
|
||||||
|
* envs: Print the fleet's slug in 'org/fleetName' format [Paulo Castro]
|
||||||
|
* os configure: Remove deprecated '--device-api-key' option [Paulo Castro]
|
||||||
|
* Clean up unused v13 feature switch code [Paulo Castro]
|
||||||
|
* config read/write/inject/reconfigure: Place '--type' option behind v14 switch [Paulo Castro]
|
||||||
|
* fleet create: Don't print fleet's numeric database ID in confirmation msg [Paulo Castro]
|
||||||
|
* devices supported: Remove deprecated '--verbose' and '--discontinued' options [Paulo Castro]
|
||||||
|
* build/deploy/push: Remove deprecated '--convert-eol' option [Paulo Castro]
|
||||||
|
* Move some v13 features behind v14 switch. [Scott Lowe]
|
||||||
|
* Remove deprecated '--app' and '--application' options (renamed to '--fleet') [Paulo Castro]
|
||||||
|
* Remove deprecated commands 'app' and 'apps' (renamed to 'fleet' and 'fleets') [Paulo Castro]
|
||||||
|
* build/deploy/push: Remove deprecated '--[no]gitignore' option [Paulo Castro]
|
||||||
|
* v13 release: Flip the v13 feature switch [Paulo Castro]
|
||||||
|
* v13 release: Drop support for Node.js v10 (package.json engines.node) [Paulo Castro]
|
||||||
|
|
||||||
|
## 12.55.11 - 2021-12-23
|
||||||
|
|
||||||
|
* Update to typescript 4.5 [Pagan Gazzard]
|
||||||
|
|
||||||
|
## 12.55.10 - 2021-12-23
|
||||||
|
|
||||||
|
* Update dev dependencies [Pagan Gazzard]
|
||||||
|
|
||||||
|
## 12.55.9 - 2021-12-22
|
||||||
|
|
||||||
|
* os download: Future-proof '--version' format for unified dev/prod variants [Paulo Castro]
|
||||||
|
|
||||||
|
## 12.55.8 - 2021-12-21
|
||||||
|
|
||||||
|
* Include version info when installed on windows [Pagan Gazzard]
|
||||||
|
* Switch from the deprecated oclif-dev commands to the oclif commands [Pagan Gazzard]
|
||||||
|
|
||||||
|
## 12.55.7 - 2021-12-14
|
||||||
|
|
||||||
|
* push: Remove hardcoded 'balenaCloud' in console message [Pranav Peshwe]
|
||||||
|
|
||||||
|
## 12.55.6 - 2021-12-14
|
||||||
|
|
||||||
|
* Fix symbolic link regression in push & deploy [Thodoris Greasidis]
|
||||||
|
|
||||||
## 12.55.5 - 2021-12-13
|
## 12.55.5 - 2021-12-13
|
||||||
|
|
||||||
* Drop unnecessary directory list created during balena deploy & push [Thodoris Greasidis]
|
* Drop unnecessary directory list created during balena deploy & push [Thodoris Greasidis]
|
||||||
|
@ -267,4 +267,3 @@ gotchas to bear in mind:
|
|||||||
replace: `spec: 'tests/**/*.spec.ts',`
|
replace: `spec: 'tests/**/*.spec.ts',`
|
||||||
|
|
||||||
with: `spec: ['tests/auth/*.spec.ts', 'tests/**/deploy.spec.ts'],`
|
with: `spec: ['tests/auth/*.spec.ts', 'tests/**/deploy.spec.ts'],`
|
||||||
|
|
||||||
|
@ -78,7 +78,7 @@ If you are a Node.js developer, you may wish to install the balena CLI via [npm]
|
|||||||
The npm installation involves building native (platform-specific) binary modules, which require
|
The npm installation involves building native (platform-specific) binary modules, which require
|
||||||
some development tools to be installed first, as follows.
|
some development tools to be installed first, as follows.
|
||||||
|
|
||||||
> **The balena CLI currently requires Node.js version 10 (min 10.20.0) or 12.**
|
> **The balena CLI currently requires Node.js version 12 (min 12.8.0).**
|
||||||
> **Versions 13 and later are not yet fully supported.**
|
> **Versions 13 and later are not yet fully supported.**
|
||||||
|
|
||||||
### Install development tools
|
### Install development tools
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
import type { JsonVersions } from '../lib/commands/version';
|
import type { JsonVersions } from '../lib/commands/version';
|
||||||
|
|
||||||
import { run as oclifRun } from '@oclif/dev-cli';
|
import { run as oclifRun } from 'oclif';
|
||||||
import * as archiver from 'archiver';
|
import * as archiver from 'archiver';
|
||||||
import * as Bluebird from 'bluebird';
|
import * as Bluebird from 'bluebird';
|
||||||
import { execFile } from 'child_process';
|
import { execFile } from 'child_process';
|
||||||
@ -64,7 +64,7 @@ const standaloneZips: PathByPlatform = {
|
|||||||
|
|
||||||
const oclifInstallers: PathByPlatform = {
|
const oclifInstallers: PathByPlatform = {
|
||||||
darwin: dPath('macos', `balena-${version}.pkg`),
|
darwin: dPath('macos', `balena-${version}.pkg`),
|
||||||
win32: dPath('win', `balena-${version}-${arch}.exe`),
|
win32: dPath('win32', `balena-${version}-${arch}.exe`),
|
||||||
};
|
};
|
||||||
|
|
||||||
const renamedOclifInstallers: PathByPlatform = {
|
const renamedOclifInstallers: PathByPlatform = {
|
||||||
@ -96,6 +96,7 @@ async function diffPkgOutput(pkgOut: string) {
|
|||||||
'> pkg@',
|
'> pkg@',
|
||||||
'> Fetching base Node.js binaries',
|
'> Fetching base Node.js binaries',
|
||||||
' fetched-',
|
' fetched-',
|
||||||
|
'prebuild-install WARN install No prebuilt binaries found',
|
||||||
];
|
];
|
||||||
const modulesRE =
|
const modulesRE =
|
||||||
process.platform === 'win32'
|
process.platform === 'win32'
|
||||||
@ -322,6 +323,10 @@ async function signFilesForNotarization() {
|
|||||||
if (!item.stats.isFile()) {
|
if (!item.stats.isFile()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (path.basename(item.path).endsWith('.node.bak')) {
|
||||||
|
console.log('Removing pkg .node.bak file', item.path);
|
||||||
|
fs.unlinkSync(item.path);
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
path.basename(item.path).endsWith('.zip') &&
|
path.basename(item.path).endsWith('.zip') &&
|
||||||
path.dirname(item.path).includes('test')
|
path.dirname(item.path).includes('test')
|
||||||
@ -456,7 +461,7 @@ async function notarizeMacInstaller(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run the `oclif-dev pack:win` or `pack:macos` command (depending on the value
|
* Run the `oclif pack:win` or `pack:macos` command (depending on the value
|
||||||
* of process.platform) to generate the native installers (which end up under
|
* of process.platform) to generate the native installers (which end up under
|
||||||
* the 'dist' folder). There are some harcoded options such as selecting only
|
* the 'dist' folder). There are some harcoded options such as selecting only
|
||||||
* 64-bit binaries under Windows.
|
* 64-bit binaries under Windows.
|
||||||
@ -486,7 +491,7 @@ export async function buildOclifInstaller() {
|
|||||||
await signFilesForNotarization();
|
await signFilesForNotarization();
|
||||||
}
|
}
|
||||||
console.log('=======================================================');
|
console.log('=======================================================');
|
||||||
console.log(`oclif-dev "${packCmd}" "${packOpts.join('" "')}"`);
|
console.log(`oclif "${packCmd}" "${packOpts.join('" "')}"`);
|
||||||
console.log(`cwd="${process.cwd()}" ROOT="${ROOT}"`);
|
console.log(`cwd="${process.cwd()}" ROOT="${ROOT}"`);
|
||||||
console.log('=======================================================');
|
console.log('=======================================================');
|
||||||
await oclifRun([packCmd].concat(...packOpts));
|
await oclifRun([packCmd].concat(...packOpts));
|
||||||
|
@ -36,19 +36,12 @@ const capitanoDoc = {
|
|||||||
{
|
{
|
||||||
title: 'Fleet',
|
title: 'Fleet',
|
||||||
files: [
|
files: [
|
||||||
'build/commands/apps.js',
|
|
||||||
'build/commands/fleets.js',
|
'build/commands/fleets.js',
|
||||||
'build/commands/app/index.js',
|
|
||||||
'build/commands/fleet/index.js',
|
'build/commands/fleet/index.js',
|
||||||
'build/commands/app/create.js',
|
|
||||||
'build/commands/fleet/create.js',
|
'build/commands/fleet/create.js',
|
||||||
'build/commands/app/purge.js',
|
|
||||||
'build/commands/fleet/purge.js',
|
'build/commands/fleet/purge.js',
|
||||||
'build/commands/app/rename.js',
|
|
||||||
'build/commands/fleet/rename.js',
|
'build/commands/fleet/rename.js',
|
||||||
'build/commands/app/restart.js',
|
|
||||||
'build/commands/fleet/restart.js',
|
'build/commands/fleet/restart.js',
|
||||||
'build/commands/app/rm.js',
|
|
||||||
'build/commands/fleet/rm.js',
|
'build/commands/fleet/rm.js',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -6,6 +6,8 @@
|
|||||||
*
|
*
|
||||||
* We don't `require('semver')` to allow this script to be run as a npm
|
* We don't `require('semver')` to allow this script to be run as a npm
|
||||||
* 'preinstall' hook, at which point no dependencies have been installed.
|
* 'preinstall' hook, at which point no dependencies have been installed.
|
||||||
|
*
|
||||||
|
* @param {string} version
|
||||||
*/
|
*/
|
||||||
function parseSemver(version) {
|
function parseSemver(version) {
|
||||||
const match = /v?(\d+)\.(\d+).(\d+)/.exec(version);
|
const match = /v?(\d+)\.(\d+).(\d+)/.exec(version);
|
||||||
@ -16,6 +18,10 @@ function parseSemver(version) {
|
|||||||
return [parseInt(major, 10), parseInt(minor, 10), parseInt(patch, 10)];
|
return [parseInt(major, 10), parseInt(minor, 10), parseInt(patch, 10)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} v1
|
||||||
|
* @param {string} v2
|
||||||
|
*/
|
||||||
function semverGte(v1, v2) {
|
function semverGte(v1, v2) {
|
||||||
let v1Array = parseSemver(v1);
|
let v1Array = parseSemver(v1);
|
||||||
let v2Array = parseSemver(v2);
|
let v2Array = parseSemver(v2);
|
||||||
|
@ -36,8 +36,8 @@ const run = async (cmd: string) => {
|
|||||||
}
|
}
|
||||||
resolve({ stdout, stderr });
|
resolve({ stdout, stderr });
|
||||||
});
|
});
|
||||||
p.stdout.pipe(process.stdout);
|
p.stdout?.pipe(process.stdout);
|
||||||
p.stderr.pipe(process.stderr);
|
p.stderr?.pipe(process.stderr);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -117,7 +117,7 @@ export async function which(program: string): Promise<string> {
|
|||||||
*/
|
*/
|
||||||
export async function whichSpawn(
|
export async function whichSpawn(
|
||||||
programName: string,
|
programName: string,
|
||||||
args?: string[],
|
args: string[] = [],
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const program = await which(programName);
|
const program = await which(programName);
|
||||||
let error: Error | undefined;
|
let error: Error | undefined;
|
||||||
|
@ -8,10 +8,9 @@ _balena() {
|
|||||||
local context state line curcontext="$curcontext"
|
local context state line curcontext="$curcontext"
|
||||||
|
|
||||||
# Valid top-level completions
|
# Valid top-level completions
|
||||||
main_commands=( apps build deploy envs fleets join keys leave login logout logs note orgs preload push releases scan settings ssh support tags tunnel version whoami api-key app app config device device devices env fleet fleet internal key key local os release release tag util )
|
main_commands=( build deploy envs fleets join keys leave login logout logs note orgs preload push releases scan settings ssh support tags tunnel version whoami api-key config device device devices env fleet fleet internal key key local os release release tag util )
|
||||||
# Sub-completions
|
# Sub-completions
|
||||||
api_key_cmds=( generate )
|
api_key_cmds=( generate )
|
||||||
app_cmds=( create purge rename restart rm )
|
|
||||||
config_cmds=( generate inject read reconfigure write )
|
config_cmds=( generate inject read reconfigure write )
|
||||||
device_cmds=( deactivate identify init local-mode move os-update public-url purge reboot register rename restart rm shutdown )
|
device_cmds=( deactivate identify init local-mode move os-update public-url purge reboot register rename restart rm shutdown )
|
||||||
devices_cmds=( supported )
|
devices_cmds=( supported )
|
||||||
@ -44,9 +43,6 @@ _balena_sec_cmds() {
|
|||||||
"api-key")
|
"api-key")
|
||||||
_describe -t api_key_cmds 'api-key_cmd' api_key_cmds "$@" && ret=0
|
_describe -t api_key_cmds 'api-key_cmd' api_key_cmds "$@" && ret=0
|
||||||
;;
|
;;
|
||||||
"app")
|
|
||||||
_describe -t app_cmds 'app_cmd' app_cmds "$@" && ret=0
|
|
||||||
;;
|
|
||||||
"config")
|
"config")
|
||||||
_describe -t config_cmds 'config_cmd' config_cmds "$@" && ret=0
|
_describe -t config_cmds 'config_cmd' config_cmds "$@" && ret=0
|
||||||
;;
|
;;
|
||||||
|
@ -7,10 +7,9 @@ _balena_complete()
|
|||||||
local cur prev
|
local cur prev
|
||||||
|
|
||||||
# Valid top-level completions
|
# Valid top-level completions
|
||||||
main_commands="apps build deploy envs fleets join keys leave login logout logs note orgs preload push releases scan settings ssh support tags tunnel version whoami api-key app app config device device devices env fleet fleet internal key key local os release release tag util"
|
main_commands="build deploy envs fleets join keys leave login logout logs note orgs preload push releases scan settings ssh support tags tunnel version whoami api-key config device device devices env fleet fleet internal key key local os release release tag util"
|
||||||
# Sub-completions
|
# Sub-completions
|
||||||
api_key_cmds="generate"
|
api_key_cmds="generate"
|
||||||
app_cmds="create purge rename restart rm"
|
|
||||||
config_cmds="generate inject read reconfigure write"
|
config_cmds="generate inject read reconfigure write"
|
||||||
device_cmds="deactivate identify init local-mode move os-update public-url purge reboot register rename restart rm shutdown"
|
device_cmds="deactivate identify init local-mode move os-update public-url purge reboot register rename restart rm shutdown"
|
||||||
devices_cmds="supported"
|
devices_cmds="supported"
|
||||||
@ -38,9 +37,6 @@ _balena_complete()
|
|||||||
api-key)
|
api-key)
|
||||||
COMPREPLY=( $(compgen -W "$api_key_cmds" -- $cur) )
|
COMPREPLY=( $(compgen -W "$api_key_cmds" -- $cur) )
|
||||||
;;
|
;;
|
||||||
app)
|
|
||||||
COMPREPLY=( $(compgen -W "$app_cmds" -- $cur) )
|
|
||||||
;;
|
|
||||||
config)
|
config)
|
||||||
COMPREPLY=( $(compgen -W "$config_cmds" -- $cur) )
|
COMPREPLY=( $(compgen -W "$config_cmds" -- $cur) )
|
||||||
;;
|
;;
|
||||||
|
File diff suppressed because it is too large
Load Diff
15
gulpfile.js
15
gulpfile.js
@ -1,15 +0,0 @@
|
|||||||
const gulp = require('gulp');
|
|
||||||
const inlinesource = require('gulp-inline-source');
|
|
||||||
|
|
||||||
const OPTIONS = {
|
|
||||||
files: {
|
|
||||||
pages: 'lib/auth/pages/*.ejs',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
gulp.task('pages', () =>
|
|
||||||
gulp
|
|
||||||
.src(OPTIONS.files.pages)
|
|
||||||
.pipe(inlinesource())
|
|
||||||
.pipe(gulp.dest('build/auth/pages')),
|
|
||||||
);
|
|
@ -14,57 +14,44 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as _ from 'lodash';
|
|
||||||
import * as url from 'url';
|
|
||||||
import { getBalenaSdk } from '../utils/lazy';
|
import { getBalenaSdk } from '../utils/lazy';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Get dashboard CLI login URL
|
* Get dashboard CLI login URL
|
||||||
* @function
|
|
||||||
* @protected
|
|
||||||
*
|
*
|
||||||
* @param {String} callbackUrl - callback url
|
* @param callbackUrl - Callback url, e.g. 'http://127.0.0.1:3000'
|
||||||
* @fulfil {String} - dashboard login url
|
* @returns Dashboard login URL, e.g.:
|
||||||
* @returns {Promise}
|
* 'https://dashboard.balena-cloud.com/login/cli/http%253A%252F%252F127.0.0.1%253A59581%252Fauth'
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* utils.getDashboardLoginURL('http://127.0.0.1:3000').then (url) ->
|
|
||||||
* console.log(url)
|
|
||||||
*/
|
*/
|
||||||
export const getDashboardLoginURL = (callbackUrl: string) => {
|
export async function getDashboardLoginURL(
|
||||||
|
callbackUrl: string,
|
||||||
|
): Promise<string> {
|
||||||
// Encode percentages signs from the escaped url
|
// Encode percentages signs from the escaped url
|
||||||
// characters to avoid angular getting confused.
|
// characters to avoid angular getting confused.
|
||||||
callbackUrl = encodeURIComponent(callbackUrl).replace(/%/g, '%25');
|
callbackUrl = encodeURIComponent(callbackUrl).replace(/%/g, '%25');
|
||||||
|
|
||||||
return getBalenaSdk()
|
const [{ URL }, dashboardUrl] = await Promise.all([
|
||||||
.settings.get('dashboardUrl')
|
import('url'),
|
||||||
.then((dashboardUrl) =>
|
getBalenaSdk().settings.get('dashboardUrl'),
|
||||||
url.resolve(dashboardUrl, `/login/cli/${callbackUrl}`),
|
]);
|
||||||
);
|
return new URL(`/login/cli/${callbackUrl}`, dashboardUrl).href;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Log in using a token, but only if the token is valid
|
* Log in using a token, but only if the token is valid.
|
||||||
* @function
|
|
||||||
* @protected
|
|
||||||
*
|
*
|
||||||
* @description
|
|
||||||
* This function checks that the token is not only well-structured
|
* This function checks that the token is not only well-structured
|
||||||
* but that it also authenticates with the server successfully.
|
* but that it also authenticates with the server successfully.
|
||||||
*
|
*
|
||||||
* If authenticated, the token is persisted, if not then the previous
|
* If authenticated, the token is persisted, if not then the previous
|
||||||
* login state is restored.
|
* login state is restored.
|
||||||
*
|
*
|
||||||
* @param {String} token - session token or api key
|
* @param token - session token or api key
|
||||||
* @fulfil {Boolean} - whether the login was successful or not
|
* @returns whether the login was successful or not
|
||||||
* @returns {Promise}
|
|
||||||
*
|
|
||||||
* utils.loginIfTokenValid('...').then (loggedIn) ->
|
|
||||||
* if loggedIn
|
|
||||||
* console.log('Token is valid!')
|
|
||||||
*/
|
*/
|
||||||
export const loginIfTokenValid = async (token: string): Promise<boolean> => {
|
export async function loginIfTokenValid(token?: string): Promise<boolean> {
|
||||||
if (_.isEmpty(token?.trim())) {
|
token = (token || '').trim();
|
||||||
|
if (!token) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const balena = getBalenaSdk();
|
const balena = getBalenaSdk();
|
||||||
@ -86,4 +73,4 @@ export const loginIfTokenValid = async (token: string): Promise<boolean> => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return isLoggedIn;
|
return isLoggedIn;
|
||||||
};
|
}
|
||||||
|
@ -171,4 +171,5 @@ export default abstract class BalenaCommand extends Command {
|
|||||||
|
|
||||||
protected outputMessage = output.outputMessage;
|
protected outputMessage = output.outputMessage;
|
||||||
protected outputData = output.outputData;
|
protected outputData = output.outputData;
|
||||||
|
protected printTitle = output.printTitle;
|
||||||
}
|
}
|
||||||
|
@ -1,181 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright 2016-2021 Balena Ltd.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { flags } from '@oclif/command';
|
|
||||||
import type { Output as ParserOutput } from '@oclif/parser';
|
|
||||||
import type { Application } from 'balena-sdk';
|
|
||||||
|
|
||||||
import Command from '../../command';
|
|
||||||
import { ExpectedError } from '../../errors';
|
|
||||||
import * as cf from '../../utils/common-flags';
|
|
||||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
|
||||||
import { appToFleetCmdMsg, warnify } from '../../utils/messages';
|
|
||||||
|
|
||||||
interface FlagsDef {
|
|
||||||
organization?: string;
|
|
||||||
type?: string; // application device type
|
|
||||||
help: void;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ArgsDef {
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class FleetCreateCmd extends Command {
|
|
||||||
public static description = stripIndent`
|
|
||||||
Create a fleet.
|
|
||||||
|
|
||||||
Create a new balena fleet.
|
|
||||||
|
|
||||||
You can specify the organization the fleet should belong to using
|
|
||||||
the \`--organization\` option. The organization's handle, not its name,
|
|
||||||
should be provided. Organization handles can be listed with the
|
|
||||||
\`balena orgs\` command.
|
|
||||||
|
|
||||||
The fleet's default device type is specified with the \`--type\` option.
|
|
||||||
The \`balena devices supported\` command can be used to list the available
|
|
||||||
device types.
|
|
||||||
|
|
||||||
Interactive dropdowns will be shown for selection if no device type or
|
|
||||||
organization is specified and there are multiple options to choose from.
|
|
||||||
If there is a single option to choose from, it will be chosen automatically.
|
|
||||||
This interactive behavior can be disabled by explicitly specifying a device
|
|
||||||
type and organization.
|
|
||||||
`;
|
|
||||||
|
|
||||||
public static examples = [
|
|
||||||
'$ balena fleet create MyFleet',
|
|
||||||
'$ balena fleet create MyFleet --organization mmyorg',
|
|
||||||
'$ balena fleet create MyFleet -o myorg --type raspberry-pi',
|
|
||||||
];
|
|
||||||
|
|
||||||
public static args = [
|
|
||||||
{
|
|
||||||
name: 'name',
|
|
||||||
description: 'fleet name',
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
public static usage = 'fleet create <name>';
|
|
||||||
|
|
||||||
public static flags: flags.Input<FlagsDef> = {
|
|
||||||
organization: flags.string({
|
|
||||||
char: 'o',
|
|
||||||
description: 'handle of the organization the fleet should belong to',
|
|
||||||
}),
|
|
||||||
type: flags.string({
|
|
||||||
char: 't',
|
|
||||||
description:
|
|
||||||
'fleet device type (Check available types with `balena devices supported`)',
|
|
||||||
}),
|
|
||||||
help: cf.help,
|
|
||||||
};
|
|
||||||
|
|
||||||
public static authenticated = true;
|
|
||||||
|
|
||||||
public async run(parserOutput?: ParserOutput<FlagsDef, ArgsDef>) {
|
|
||||||
const { args: params, flags: options } =
|
|
||||||
parserOutput || this.parse<FlagsDef, ArgsDef>(FleetCreateCmd);
|
|
||||||
|
|
||||||
// Ascertain device type
|
|
||||||
const deviceType =
|
|
||||||
options.type ||
|
|
||||||
(await (await import('../../utils/patterns')).selectDeviceType());
|
|
||||||
|
|
||||||
// Ascertain organization
|
|
||||||
const organization =
|
|
||||||
options.organization?.toLowerCase() || (await this.getOrganization());
|
|
||||||
|
|
||||||
// Create application
|
|
||||||
let application: Application;
|
|
||||||
try {
|
|
||||||
application = await getBalenaSdk().models.application.create({
|
|
||||||
name: params.name,
|
|
||||||
deviceType,
|
|
||||||
organization,
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
if ((err.message || '').toLowerCase().includes('unique')) {
|
|
||||||
// BalenaRequestError: Request error: "organization" and "app_name" must be unique.
|
|
||||||
throw new ExpectedError(
|
|
||||||
`Error: fleet "${params.name}" already exists in organization "${organization}".`,
|
|
||||||
);
|
|
||||||
} else if ((err.message || '').toLowerCase().includes('unauthorized')) {
|
|
||||||
// BalenaRequestError: Request error: Unauthorized
|
|
||||||
throw new ExpectedError(
|
|
||||||
`Error: You are not authorized to create fleets in organization "${organization}".`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Output
|
|
||||||
const { isV13 } = await import('../../utils/version');
|
|
||||||
console.log(
|
|
||||||
isV13()
|
|
||||||
? `Fleet created: slug "${application.slug}", device type "${deviceType}"`
|
|
||||||
: `Fleet created: ${application.slug} (${deviceType}, id ${application.id})`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getOrganization() {
|
|
||||||
const { getOwnOrganizations } = await import('../../utils/sdk');
|
|
||||||
const organizations = await getOwnOrganizations(getBalenaSdk());
|
|
||||||
|
|
||||||
if (organizations.length === 0) {
|
|
||||||
// User is not a member of any organizations (should not happen).
|
|
||||||
throw new Error('This account is not a member of any organizations');
|
|
||||||
} else if (organizations.length === 1) {
|
|
||||||
// User is a member of only one organization - use this.
|
|
||||||
return organizations[0].handle;
|
|
||||||
} else {
|
|
||||||
// User is a member of multiple organizations -
|
|
||||||
const { selectOrganization } = await import('../../utils/patterns');
|
|
||||||
return selectOrganization(organizations);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class AppCreateCmd extends FleetCreateCmd {
|
|
||||||
public static description = stripIndent`
|
|
||||||
DEPRECATED alias for the 'fleet create' command
|
|
||||||
|
|
||||||
${appToFleetCmdMsg
|
|
||||||
.split('\n')
|
|
||||||
.map((l) => `\t\t${l}`)
|
|
||||||
.join('\n')}
|
|
||||||
|
|
||||||
For command usage, see 'balena help fleet create'
|
|
||||||
`;
|
|
||||||
public static examples = [];
|
|
||||||
public static usage = 'app create <name>';
|
|
||||||
public static args = FleetCreateCmd.args;
|
|
||||||
public static flags = FleetCreateCmd.flags;
|
|
||||||
public static authenticated = FleetCreateCmd.authenticated;
|
|
||||||
public static primary = FleetCreateCmd.primary;
|
|
||||||
|
|
||||||
public async run() {
|
|
||||||
// call this.parse() before deprecation message to parse '-h'
|
|
||||||
const parserOutput = this.parse<FlagsDef, ArgsDef>(AppCreateCmd);
|
|
||||||
if (process.stderr.isTTY) {
|
|
||||||
console.error(warnify(appToFleetCmdMsg));
|
|
||||||
}
|
|
||||||
await super.run(parserOutput);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,135 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright 2016-2021 Balena Ltd.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { flags } from '@oclif/command';
|
|
||||||
import type { Output as ParserOutput } from '@oclif/parser';
|
|
||||||
import type { Release } from 'balena-sdk';
|
|
||||||
|
|
||||||
import Command from '../../command';
|
|
||||||
import * as cf from '../../utils/common-flags';
|
|
||||||
import * as ca from '../../utils/common-args';
|
|
||||||
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
|
|
||||||
import {
|
|
||||||
applicationIdInfo,
|
|
||||||
appToFleetCmdMsg,
|
|
||||||
warnify,
|
|
||||||
} from '../../utils/messages';
|
|
||||||
import { isV13 } from '../../utils/version';
|
|
||||||
import type { DataOutputOptions } from '../../framework';
|
|
||||||
|
|
||||||
interface FlagsDef extends DataOutputOptions {
|
|
||||||
help: void;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ArgsDef {
|
|
||||||
fleet: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class FleetCmd extends Command {
|
|
||||||
public static description = stripIndent`
|
|
||||||
Display information about a single fleet.
|
|
||||||
|
|
||||||
Display detailed information about a single fleet.
|
|
||||||
|
|
||||||
${applicationIdInfo.split('\n').join('\n\t\t')}
|
|
||||||
`;
|
|
||||||
public static examples = [
|
|
||||||
'$ balena fleet MyFleet',
|
|
||||||
'$ balena fleet myorg/myfleet',
|
|
||||||
];
|
|
||||||
|
|
||||||
public static args = [ca.fleetRequired];
|
|
||||||
|
|
||||||
public static usage = 'fleet <fleet>';
|
|
||||||
|
|
||||||
public static flags: flags.Input<FlagsDef> = {
|
|
||||||
help: cf.help,
|
|
||||||
...(isV13() ? cf.dataOutputFlags : {}),
|
|
||||||
};
|
|
||||||
|
|
||||||
public static authenticated = true;
|
|
||||||
public static primary = true;
|
|
||||||
|
|
||||||
public async run(parserOutput?: ParserOutput<FlagsDef, ArgsDef>) {
|
|
||||||
const { args: params, flags: options } =
|
|
||||||
parserOutput || this.parse<FlagsDef, ArgsDef>(FleetCmd);
|
|
||||||
|
|
||||||
const { getApplication } = await import('../../utils/sdk');
|
|
||||||
|
|
||||||
const application = (await getApplication(getBalenaSdk(), params.fleet, {
|
|
||||||
$expand: {
|
|
||||||
is_for__device_type: { $select: 'slug' },
|
|
||||||
should_be_running__release: { $select: 'commit' },
|
|
||||||
},
|
|
||||||
})) as ApplicationWithDeviceType & {
|
|
||||||
should_be_running__release: [Release?];
|
|
||||||
// For display purposes:
|
|
||||||
device_type: string;
|
|
||||||
commit?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
application.device_type = application.is_for__device_type[0].slug;
|
|
||||||
application.commit = application.should_be_running__release[0]?.commit;
|
|
||||||
|
|
||||||
if (isV13()) {
|
|
||||||
await this.outputData(
|
|
||||||
application,
|
|
||||||
['app_name', 'id', 'device_type', 'slug', 'commit'],
|
|
||||||
options,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// Emulate table.vertical title output, but avoid uppercasing and inserting spaces
|
|
||||||
console.log(`== ${application.app_name}`);
|
|
||||||
console.log(
|
|
||||||
getVisuals().table.vertical(application, [
|
|
||||||
'id',
|
|
||||||
'device_type',
|
|
||||||
'slug',
|
|
||||||
'commit',
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class AppCmd extends FleetCmd {
|
|
||||||
public static description = stripIndent`
|
|
||||||
DEPRECATED alias for the 'fleet' command
|
|
||||||
|
|
||||||
${appToFleetCmdMsg
|
|
||||||
.split('\n')
|
|
||||||
.map((l) => `\t\t${l}`)
|
|
||||||
.join('\n')}
|
|
||||||
|
|
||||||
For command usage, see 'balena help fleet'
|
|
||||||
`;
|
|
||||||
public static examples = [];
|
|
||||||
public static usage = 'app <fleet>';
|
|
||||||
public static args = FleetCmd.args;
|
|
||||||
public static flags = FleetCmd.flags;
|
|
||||||
public static authenticated = FleetCmd.authenticated;
|
|
||||||
public static primary = FleetCmd.primary;
|
|
||||||
|
|
||||||
public async run() {
|
|
||||||
// call this.parse() before deprecation message to parse '-h'
|
|
||||||
const parserOutput = this.parse<FlagsDef, ArgsDef>(AppCmd);
|
|
||||||
if (process.stderr.isTTY) {
|
|
||||||
console.error(warnify(appToFleetCmdMsg));
|
|
||||||
}
|
|
||||||
await super.run(parserOutput);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,115 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright 2016-2020 Balena Ltd.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { flags } from '@oclif/command';
|
|
||||||
import type { Output as ParserOutput } from '@oclif/parser';
|
|
||||||
|
|
||||||
import Command from '../../command';
|
|
||||||
import * as cf from '../../utils/common-flags';
|
|
||||||
import * as ca from '../../utils/common-args';
|
|
||||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
|
||||||
import {
|
|
||||||
applicationIdInfo,
|
|
||||||
appToFleetCmdMsg,
|
|
||||||
warnify,
|
|
||||||
} from '../../utils/messages';
|
|
||||||
|
|
||||||
interface FlagsDef {
|
|
||||||
help: void;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ArgsDef {
|
|
||||||
fleet: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class FleetPurgeCmd extends Command {
|
|
||||||
public static description = stripIndent`
|
|
||||||
Purge data from a fleet.
|
|
||||||
|
|
||||||
Purge data from all devices belonging to a fleet.
|
|
||||||
This will clear the fleet's '/data' directory.
|
|
||||||
|
|
||||||
${applicationIdInfo.split('\n').join('\n\t\t')}
|
|
||||||
`;
|
|
||||||
|
|
||||||
public static examples = [
|
|
||||||
'$ balena fleet purge MyFleet',
|
|
||||||
'$ balena fleet purge myorg/myfleet',
|
|
||||||
];
|
|
||||||
|
|
||||||
public static args = [ca.fleetRequired];
|
|
||||||
|
|
||||||
public static usage = 'fleet purge <fleet>';
|
|
||||||
|
|
||||||
public static flags: flags.Input<FlagsDef> = {
|
|
||||||
help: cf.help,
|
|
||||||
};
|
|
||||||
|
|
||||||
public static authenticated = true;
|
|
||||||
|
|
||||||
public async run(parserOutput?: ParserOutput<FlagsDef, ArgsDef>) {
|
|
||||||
const { args: params } =
|
|
||||||
parserOutput || this.parse<FlagsDef, ArgsDef>(FleetPurgeCmd);
|
|
||||||
|
|
||||||
const { getApplication } = await import('../../utils/sdk');
|
|
||||||
|
|
||||||
const balena = getBalenaSdk();
|
|
||||||
|
|
||||||
// balena.models.application.purge only accepts a numeric id
|
|
||||||
// so we must first fetch the app to get it's id,
|
|
||||||
const application = await getApplication(balena, params.fleet);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await balena.models.application.purge(application.id);
|
|
||||||
} catch (e) {
|
|
||||||
if (e.message.toLowerCase().includes('no online device(s) found')) {
|
|
||||||
// application.purge throws an error if no devices are online
|
|
||||||
// ignore in this case.
|
|
||||||
} else {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class AppPurgeCmd extends FleetPurgeCmd {
|
|
||||||
public static description = stripIndent`
|
|
||||||
DEPRECATED alias for the 'fleet purge' command
|
|
||||||
|
|
||||||
${appToFleetCmdMsg
|
|
||||||
.split('\n')
|
|
||||||
.map((l) => `\t\t${l}`)
|
|
||||||
.join('\n')}
|
|
||||||
|
|
||||||
For command usage, see 'balena help fleet purge'
|
|
||||||
`;
|
|
||||||
public static examples = [];
|
|
||||||
public static usage = 'app purge <fleet>';
|
|
||||||
public static args = FleetPurgeCmd.args;
|
|
||||||
public static flags = FleetPurgeCmd.flags;
|
|
||||||
public static authenticated = FleetPurgeCmd.authenticated;
|
|
||||||
public static primary = FleetPurgeCmd.primary;
|
|
||||||
|
|
||||||
public async run() {
|
|
||||||
// call this.parse() before deprecation message to parse '-h'
|
|
||||||
const parserOutput = this.parse<FlagsDef, ArgsDef>(AppPurgeCmd);
|
|
||||||
if (process.stderr.isTTY) {
|
|
||||||
console.error(warnify(appToFleetCmdMsg));
|
|
||||||
}
|
|
||||||
await super.run(parserOutput);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,170 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright 2016-2020 Balena Ltd.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { flags } from '@oclif/command';
|
|
||||||
import type { Output as ParserOutput } from '@oclif/parser';
|
|
||||||
import type { ApplicationType } from 'balena-sdk';
|
|
||||||
|
|
||||||
import Command from '../../command';
|
|
||||||
import * as cf from '../../utils/common-flags';
|
|
||||||
import * as ca from '../../utils/common-args';
|
|
||||||
import { getBalenaSdk, stripIndent, getCliForm } from '../../utils/lazy';
|
|
||||||
import {
|
|
||||||
applicationIdInfo,
|
|
||||||
appToFleetCmdMsg,
|
|
||||||
warnify,
|
|
||||||
} from '../../utils/messages';
|
|
||||||
|
|
||||||
interface FlagsDef {
|
|
||||||
help: void;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ArgsDef {
|
|
||||||
fleet: string;
|
|
||||||
newName?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class FleetRenameCmd extends Command {
|
|
||||||
public static description = stripIndent`
|
|
||||||
Rename a fleet.
|
|
||||||
|
|
||||||
Rename a fleet.
|
|
||||||
|
|
||||||
Note, if the \`newName\` parameter is omitted, it will be
|
|
||||||
prompted for interactively.
|
|
||||||
|
|
||||||
${applicationIdInfo.split('\n').join('\n\t\t')}
|
|
||||||
`;
|
|
||||||
|
|
||||||
public static examples = [
|
|
||||||
'$ balena fleet rename OldName',
|
|
||||||
'$ balena fleet rename OldName NewName',
|
|
||||||
'$ balena fleet rename myorg/oldname NewName',
|
|
||||||
];
|
|
||||||
|
|
||||||
public static args = [
|
|
||||||
ca.fleetRequired,
|
|
||||||
{
|
|
||||||
name: 'newName',
|
|
||||||
description: 'the new name for the fleet',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
public static usage = 'fleet rename <fleet> [newName]';
|
|
||||||
|
|
||||||
public static flags: flags.Input<FlagsDef> = {
|
|
||||||
help: cf.help,
|
|
||||||
};
|
|
||||||
|
|
||||||
public static authenticated = true;
|
|
||||||
|
|
||||||
public async run(parserOutput?: ParserOutput<FlagsDef, ArgsDef>) {
|
|
||||||
const { args: params } =
|
|
||||||
parserOutput || this.parse<FlagsDef, ArgsDef>(FleetRenameCmd);
|
|
||||||
|
|
||||||
const { validateApplicationName } = await import('../../utils/validation');
|
|
||||||
const { ExpectedError } = await import('../../errors');
|
|
||||||
|
|
||||||
const balena = getBalenaSdk();
|
|
||||||
|
|
||||||
// Disambiguate target application (if params.params is a number, it could either be an ID or a numerical name)
|
|
||||||
const { getApplication } = await import('../../utils/sdk');
|
|
||||||
const application = await getApplication(balena, params.fleet, {
|
|
||||||
$expand: {
|
|
||||||
application_type: {
|
|
||||||
$select: ['is_legacy'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check app exists
|
|
||||||
if (!application) {
|
|
||||||
throw new ExpectedError(`Error: fleet ${params.fleet} not found.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check app supports renaming
|
|
||||||
const appType = (application.application_type as ApplicationType[])?.[0];
|
|
||||||
if (appType.is_legacy) {
|
|
||||||
throw new ExpectedError(
|
|
||||||
`Fleet ${params.fleet} is of 'legacy' type, and cannot be renamed.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ascertain new name
|
|
||||||
const newName =
|
|
||||||
params.newName ||
|
|
||||||
(await getCliForm().ask({
|
|
||||||
message: 'Please enter the new name for this fleet:',
|
|
||||||
type: 'input',
|
|
||||||
validate: validateApplicationName,
|
|
||||||
})) ||
|
|
||||||
'';
|
|
||||||
|
|
||||||
// Rename
|
|
||||||
try {
|
|
||||||
await balena.models.application.rename(application.id, newName);
|
|
||||||
} catch (e) {
|
|
||||||
// BalenaRequestError: Request error: "organization" and "app_name" must be unique.
|
|
||||||
if ((e.message || '').toLowerCase().includes('unique')) {
|
|
||||||
throw new ExpectedError(`Error: fleet ${newName} already exists.`);
|
|
||||||
}
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get application again, to be sure of results
|
|
||||||
const renamedApplication = await balena.models.application.get(
|
|
||||||
application.id,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Output result
|
|
||||||
console.log(`Fleet renamed`);
|
|
||||||
console.log('From:');
|
|
||||||
console.log(`\tname: ${application.app_name}`);
|
|
||||||
console.log(`\tslug: ${application.slug}`);
|
|
||||||
console.log('To:');
|
|
||||||
console.log(`\tname: ${renamedApplication.app_name}`);
|
|
||||||
console.log(`\tslug: ${renamedApplication.slug}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class AppRenameCmd extends FleetRenameCmd {
|
|
||||||
public static description = stripIndent`
|
|
||||||
DEPRECATED alias for the 'fleet rename' command
|
|
||||||
|
|
||||||
${appToFleetCmdMsg
|
|
||||||
.split('\n')
|
|
||||||
.map((l) => `\t\t${l}`)
|
|
||||||
.join('\n')}
|
|
||||||
|
|
||||||
For command usage, see 'balena help fleet rename'
|
|
||||||
`;
|
|
||||||
public static examples = [];
|
|
||||||
public static usage = 'app rename <fleet> [newName]';
|
|
||||||
public static args = FleetRenameCmd.args;
|
|
||||||
public static flags = FleetRenameCmd.flags;
|
|
||||||
public static authenticated = FleetRenameCmd.authenticated;
|
|
||||||
public static primary = FleetRenameCmd.primary;
|
|
||||||
|
|
||||||
public async run() {
|
|
||||||
// call this.parse() before deprecation message to parse '-h'
|
|
||||||
const parserOutput = this.parse<FlagsDef, ArgsDef>(AppRenameCmd);
|
|
||||||
if (process.stderr.isTTY) {
|
|
||||||
console.error(warnify(appToFleetCmdMsg));
|
|
||||||
}
|
|
||||||
await super.run(parserOutput);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,104 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright 2016-2020 Balena Ltd.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { flags } from '@oclif/command';
|
|
||||||
import type { Output as ParserOutput } from '@oclif/parser';
|
|
||||||
|
|
||||||
import Command from '../../command';
|
|
||||||
import * as cf from '../../utils/common-flags';
|
|
||||||
import * as ca from '../../utils/common-args';
|
|
||||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
|
||||||
import {
|
|
||||||
applicationIdInfo,
|
|
||||||
appToFleetCmdMsg,
|
|
||||||
warnify,
|
|
||||||
} from '../../utils/messages';
|
|
||||||
|
|
||||||
interface FlagsDef {
|
|
||||||
help: void;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ArgsDef {
|
|
||||||
fleet: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class FleetRestartCmd extends Command {
|
|
||||||
public static description = stripIndent`
|
|
||||||
Restart a fleet.
|
|
||||||
|
|
||||||
Restart all devices belonging to a fleet.
|
|
||||||
|
|
||||||
${applicationIdInfo.split('\n').join('\n\t\t')}
|
|
||||||
`;
|
|
||||||
|
|
||||||
public static examples = [
|
|
||||||
'$ balena fleet restart MyFleet',
|
|
||||||
'$ balena fleet restart myorg/myfleet',
|
|
||||||
];
|
|
||||||
|
|
||||||
public static args = [ca.fleetRequired];
|
|
||||||
|
|
||||||
public static usage = 'fleet restart <fleet>';
|
|
||||||
|
|
||||||
public static flags: flags.Input<FlagsDef> = {
|
|
||||||
help: cf.help,
|
|
||||||
};
|
|
||||||
|
|
||||||
public static authenticated = true;
|
|
||||||
|
|
||||||
public async run(parserOutput?: ParserOutput<FlagsDef, ArgsDef>) {
|
|
||||||
const { args: params } =
|
|
||||||
parserOutput || this.parse<FlagsDef, ArgsDef>(FleetRestartCmd);
|
|
||||||
|
|
||||||
const { getApplication } = await import('../../utils/sdk');
|
|
||||||
|
|
||||||
const balena = getBalenaSdk();
|
|
||||||
|
|
||||||
// Disambiguate application (if is a number, it could either be an ID or a numerical name)
|
|
||||||
const application = await getApplication(balena, params.fleet);
|
|
||||||
|
|
||||||
await balena.models.application.restart(application.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class AppRestartCmd extends FleetRestartCmd {
|
|
||||||
public static description = stripIndent`
|
|
||||||
DEPRECATED alias for the 'fleet restart' command
|
|
||||||
|
|
||||||
${appToFleetCmdMsg
|
|
||||||
.split('\n')
|
|
||||||
.map((l) => `\t\t${l}`)
|
|
||||||
.join('\n')}
|
|
||||||
|
|
||||||
For command usage, see 'balena help fleet restart'
|
|
||||||
`;
|
|
||||||
public static examples = [];
|
|
||||||
public static usage = 'app restart <fleet>';
|
|
||||||
public static args = FleetRestartCmd.args;
|
|
||||||
public static flags = FleetRestartCmd.flags;
|
|
||||||
public static authenticated = FleetRestartCmd.authenticated;
|
|
||||||
public static primary = FleetRestartCmd.primary;
|
|
||||||
|
|
||||||
public async run() {
|
|
||||||
// call this.parse() before deprecation message to parse '-h'
|
|
||||||
const parserOutput = this.parse<FlagsDef, ArgsDef>(AppRestartCmd);
|
|
||||||
if (process.stderr.isTTY) {
|
|
||||||
console.error(warnify(appToFleetCmdMsg));
|
|
||||||
}
|
|
||||||
await super.run(parserOutput);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,116 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright 2016-2020 Balena Ltd.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { flags } from '@oclif/command';
|
|
||||||
import type { Output as ParserOutput } from '@oclif/parser';
|
|
||||||
|
|
||||||
import Command from '../../command';
|
|
||||||
import * as cf from '../../utils/common-flags';
|
|
||||||
import * as ca from '../../utils/common-args';
|
|
||||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
|
||||||
import {
|
|
||||||
applicationIdInfo,
|
|
||||||
appToFleetCmdMsg,
|
|
||||||
warnify,
|
|
||||||
} from '../../utils/messages';
|
|
||||||
|
|
||||||
interface FlagsDef {
|
|
||||||
yes: boolean;
|
|
||||||
help: void;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ArgsDef {
|
|
||||||
fleet: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class FleetRmCmd extends Command {
|
|
||||||
public static description = stripIndent`
|
|
||||||
Remove a fleet.
|
|
||||||
|
|
||||||
Permanently remove a fleet.
|
|
||||||
|
|
||||||
The --yes option may be used to avoid interactive confirmation.
|
|
||||||
|
|
||||||
${applicationIdInfo.split('\n').join('\n\t\t')}
|
|
||||||
`;
|
|
||||||
|
|
||||||
public static examples = [
|
|
||||||
'$ balena fleet rm MyFleet',
|
|
||||||
'$ balena fleet rm MyFleet --yes',
|
|
||||||
'$ balena fleet rm myorg/myfleet',
|
|
||||||
];
|
|
||||||
|
|
||||||
public static args = [ca.fleetRequired];
|
|
||||||
|
|
||||||
public static usage = 'fleet rm <fleet>';
|
|
||||||
|
|
||||||
public static flags: flags.Input<FlagsDef> = {
|
|
||||||
yes: cf.yes,
|
|
||||||
help: cf.help,
|
|
||||||
};
|
|
||||||
|
|
||||||
public static authenticated = true;
|
|
||||||
|
|
||||||
public async run(parserOutput?: ParserOutput<FlagsDef, ArgsDef>) {
|
|
||||||
const { args: params, flags: options } =
|
|
||||||
parserOutput || this.parse<FlagsDef, ArgsDef>(FleetRmCmd);
|
|
||||||
|
|
||||||
const { confirm } = await import('../../utils/patterns');
|
|
||||||
const { getApplication } = await import('../../utils/sdk');
|
|
||||||
const balena = getBalenaSdk();
|
|
||||||
|
|
||||||
// Confirm
|
|
||||||
await confirm(
|
|
||||||
options.yes ?? false,
|
|
||||||
`Are you sure you want to delete fleet ${params.fleet}?`,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Disambiguate application (if is a number, it could either be an ID or a numerical name)
|
|
||||||
const application = await getApplication(balena, params.fleet);
|
|
||||||
|
|
||||||
// Remove
|
|
||||||
await balena.models.application.remove(application.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class AppRmCmd extends FleetRmCmd {
|
|
||||||
public static description = stripIndent`
|
|
||||||
DEPRECATED alias for the 'fleet rm' command
|
|
||||||
|
|
||||||
${appToFleetCmdMsg
|
|
||||||
.split('\n')
|
|
||||||
.map((l) => `\t\t${l}`)
|
|
||||||
.join('\n')}
|
|
||||||
|
|
||||||
For command usage, see 'balena help fleet rm'
|
|
||||||
`;
|
|
||||||
public static examples = [];
|
|
||||||
public static usage = 'app rm <fleet>';
|
|
||||||
public static args = FleetRmCmd.args;
|
|
||||||
public static flags = FleetRmCmd.flags;
|
|
||||||
public static authenticated = FleetRmCmd.authenticated;
|
|
||||||
public static primary = FleetRmCmd.primary;
|
|
||||||
|
|
||||||
public async run() {
|
|
||||||
// call this.parse() before deprecation message to parse '-h'
|
|
||||||
const parserOutput = this.parse<FlagsDef, ArgsDef>(AppRmCmd);
|
|
||||||
if (process.stderr.isTTY) {
|
|
||||||
console.error(warnify(appToFleetCmdMsg));
|
|
||||||
}
|
|
||||||
await super.run(parserOutput);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,152 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright 2016-2021 Balena Ltd.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { flags } from '@oclif/command';
|
|
||||||
import type { Output as ParserOutput } from '@oclif/parser';
|
|
||||||
import Command from '../command';
|
|
||||||
import * as cf from '../utils/common-flags';
|
|
||||||
import { getBalenaSdk, getVisuals, stripIndent } from '../utils/lazy';
|
|
||||||
import { appToFleetCmdMsg, warnify } from '../utils/messages';
|
|
||||||
import { isV13 } from '../utils/version';
|
|
||||||
import type { DataSetOutputOptions } from '../framework';
|
|
||||||
|
|
||||||
interface ExtendedApplication extends ApplicationWithDeviceType {
|
|
||||||
device_count: number;
|
|
||||||
online_devices: number;
|
|
||||||
device_type?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface FlagsDef extends DataSetOutputOptions {
|
|
||||||
help: void;
|
|
||||||
verbose?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class FleetsCmd extends Command {
|
|
||||||
public static description = stripIndent`
|
|
||||||
List all fleets.
|
|
||||||
|
|
||||||
List all your balena fleets.
|
|
||||||
|
|
||||||
For detailed information on a particular fleet, use
|
|
||||||
\`balena fleet <fleet>\`
|
|
||||||
`;
|
|
||||||
|
|
||||||
public static examples = ['$ balena fleets'];
|
|
||||||
|
|
||||||
public static usage = 'fleets';
|
|
||||||
|
|
||||||
public static flags: flags.Input<FlagsDef> = {
|
|
||||||
...(isV13()
|
|
||||||
? {}
|
|
||||||
: {
|
|
||||||
verbose: flags.boolean({
|
|
||||||
default: false,
|
|
||||||
char: 'v',
|
|
||||||
description: 'No-op since release v12.0.0',
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
...(isV13() ? cf.dataSetOutputFlags : {}),
|
|
||||||
help: cf.help,
|
|
||||||
};
|
|
||||||
|
|
||||||
public static authenticated = true;
|
|
||||||
public static primary = true;
|
|
||||||
|
|
||||||
protected useAppWord = false;
|
|
||||||
|
|
||||||
public async run(_parserOutput?: ParserOutput<FlagsDef, {}>) {
|
|
||||||
_parserOutput ||= this.parse<FlagsDef, {}>(FleetsCmd);
|
|
||||||
|
|
||||||
const balena = getBalenaSdk();
|
|
||||||
|
|
||||||
// Get applications
|
|
||||||
const applications = (await balena.models.application.getAll({
|
|
||||||
$select: ['id', 'app_name', 'slug'],
|
|
||||||
$expand: {
|
|
||||||
is_for__device_type: { $select: 'slug' },
|
|
||||||
owns__device: { $select: 'is_online' },
|
|
||||||
},
|
|
||||||
})) as ExtendedApplication[];
|
|
||||||
|
|
||||||
// Add extended properties
|
|
||||||
applications.forEach((application) => {
|
|
||||||
application.device_count = application.owns__device?.length ?? 0;
|
|
||||||
application.online_devices =
|
|
||||||
application.owns__device?.filter((d) => d.is_online).length || 0;
|
|
||||||
application.device_type = application.is_for__device_type[0].slug;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isV13()) {
|
|
||||||
await this.outputData(
|
|
||||||
applications,
|
|
||||||
[
|
|
||||||
'id',
|
|
||||||
'app_name',
|
|
||||||
'slug',
|
|
||||||
'device_type',
|
|
||||||
'device_count',
|
|
||||||
'online_devices',
|
|
||||||
],
|
|
||||||
_parserOutput.flags,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
console.log(
|
|
||||||
getVisuals().table.horizontal(applications, [
|
|
||||||
'id',
|
|
||||||
this.useAppWord ? 'app_name' : 'app_name => NAME',
|
|
||||||
'slug',
|
|
||||||
'device_type',
|
|
||||||
'online_devices',
|
|
||||||
'device_count',
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const appsToFleetsRenameMsg = appToFleetCmdMsg
|
|
||||||
.replace(/'app'/g, "'apps'")
|
|
||||||
.replace(/'fleet'/g, "'fleets'");
|
|
||||||
|
|
||||||
export default class AppsCmd extends FleetsCmd {
|
|
||||||
public static description = stripIndent`
|
|
||||||
DEPRECATED alias for the 'fleets' command
|
|
||||||
|
|
||||||
${appsToFleetsRenameMsg
|
|
||||||
.split('\n')
|
|
||||||
.map((l) => `\t\t${l}`)
|
|
||||||
.join('\n')}
|
|
||||||
|
|
||||||
For command usage, see 'balena help fleets'
|
|
||||||
`;
|
|
||||||
public static examples = [];
|
|
||||||
public static usage = 'apps';
|
|
||||||
public static args = FleetsCmd.args;
|
|
||||||
public static flags = FleetsCmd.flags;
|
|
||||||
public static authenticated = FleetsCmd.authenticated;
|
|
||||||
public static primary = FleetsCmd.primary;
|
|
||||||
|
|
||||||
public async run() {
|
|
||||||
// call this.parse() before deprecation message to parse '-h'
|
|
||||||
const parserOutput = this.parse<FlagsDef, {}>(AppsCmd);
|
|
||||||
if (process.stderr.isTTY) {
|
|
||||||
console.error(warnify(appsToFleetsRenameMsg));
|
|
||||||
}
|
|
||||||
this.useAppWord = true;
|
|
||||||
await super.run(parserOutput);
|
|
||||||
}
|
|
||||||
}
|
|
@ -22,22 +22,18 @@ import * as cf from '../utils/common-flags';
|
|||||||
import * as compose from '../utils/compose';
|
import * as compose from '../utils/compose';
|
||||||
import type { Application, ApplicationType, BalenaSDK } from 'balena-sdk';
|
import type { Application, ApplicationType, BalenaSDK } from 'balena-sdk';
|
||||||
import {
|
import {
|
||||||
appToFleetFlagMsg,
|
|
||||||
buildArgDeprecation,
|
buildArgDeprecation,
|
||||||
dockerignoreHelp,
|
dockerignoreHelp,
|
||||||
registrySecretsHelp,
|
registrySecretsHelp,
|
||||||
warnify,
|
|
||||||
} from '../utils/messages';
|
} from '../utils/messages';
|
||||||
import type { ComposeCliFlags, ComposeOpts } from '../utils/compose-types';
|
import type { ComposeCliFlags, ComposeOpts } from '../utils/compose-types';
|
||||||
import { buildProject, composeCliFlags } from '../utils/compose_ts';
|
import { buildProject, composeCliFlags } from '../utils/compose_ts';
|
||||||
import type { BuildOpts, DockerCliFlags } from '../utils/docker';
|
import type { BuildOpts, DockerCliFlags } from '../utils/docker';
|
||||||
import { dockerCliFlags } from '../utils/docker';
|
import { dockerCliFlags } from '../utils/docker';
|
||||||
import { isV13 } from '../utils/version';
|
|
||||||
|
|
||||||
interface FlagsDef extends ComposeCliFlags, DockerCliFlags {
|
interface FlagsDef extends ComposeCliFlags, DockerCliFlags {
|
||||||
arch?: string;
|
arch?: string;
|
||||||
deviceType?: string;
|
deviceType?: string;
|
||||||
application?: string;
|
|
||||||
fleet?: string;
|
fleet?: string;
|
||||||
source?: string; // Not part of command profile - source param copied here.
|
source?: string; // Not part of command profile - source param copied here.
|
||||||
help: void;
|
help: void;
|
||||||
@ -96,7 +92,6 @@ ${dockerignoreHelp}
|
|||||||
description: 'the type of device this build is for',
|
description: 'the type of device this build is for',
|
||||||
char: 'd',
|
char: 'd',
|
||||||
}),
|
}),
|
||||||
...(isV13() ? {} : { application: cf.application }),
|
|
||||||
fleet: cf.fleet,
|
fleet: cf.fleet,
|
||||||
...composeCliFlags,
|
...composeCliFlags,
|
||||||
...dockerCliFlags,
|
...dockerCliFlags,
|
||||||
@ -112,12 +107,7 @@ ${dockerignoreHelp}
|
|||||||
BuildCmd,
|
BuildCmd,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (options.application && process.stderr.isTTY) {
|
await Command.checkLoggedInIf(!!options.fleet);
|
||||||
console.error(warnify(appToFleetFlagMsg));
|
|
||||||
}
|
|
||||||
options.application ||= options.fleet;
|
|
||||||
|
|
||||||
await Command.checkLoggedInIf(!!options.application);
|
|
||||||
|
|
||||||
(await import('events')).defaultMaxListeners = 1000;
|
(await import('events')).defaultMaxListeners = 1000;
|
||||||
|
|
||||||
@ -161,10 +151,8 @@ ${dockerignoreHelp}
|
|||||||
protected async validateOptions(opts: FlagsDef, sdk: BalenaSDK) {
|
protected async validateOptions(opts: FlagsDef, sdk: BalenaSDK) {
|
||||||
// Validate option combinations
|
// Validate option combinations
|
||||||
if (
|
if (
|
||||||
(opts.application == null &&
|
(opts.fleet == null && (opts.arch == null || opts.deviceType == null)) ||
|
||||||
(opts.arch == null || opts.deviceType == null)) ||
|
(opts.fleet != null && (opts.arch != null || opts.deviceType != null))
|
||||||
(opts.application != null &&
|
|
||||||
(opts.arch != null || opts.deviceType != null))
|
|
||||||
) {
|
) {
|
||||||
const { ExpectedError } = await import('../errors');
|
const { ExpectedError } = await import('../errors');
|
||||||
throw new ExpectedError(
|
throw new ExpectedError(
|
||||||
@ -189,9 +177,9 @@ ${dockerignoreHelp}
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected async getAppAndResolveArch(opts: FlagsDef) {
|
protected async getAppAndResolveArch(opts: FlagsDef) {
|
||||||
if (opts.application) {
|
if (opts.fleet) {
|
||||||
const { getAppWithArch } = await import('../utils/helpers');
|
const { getAppWithArch } = await import('../utils/helpers');
|
||||||
const app = await getAppWithArch(opts.application);
|
const app = await getAppWithArch(opts.fleet);
|
||||||
opts.arch = app.arch;
|
opts.arch = app.arch;
|
||||||
opts.deviceType = app.is_for__device_type[0].slug;
|
opts.deviceType = app.is_for__device_type[0].slug;
|
||||||
return app;
|
return app;
|
||||||
@ -271,7 +259,6 @@ ${dockerignoreHelp}
|
|||||||
inlineLogs: composeOpts.inlineLogs,
|
inlineLogs: composeOpts.inlineLogs,
|
||||||
convertEol: composeOpts.convertEol,
|
convertEol: composeOpts.convertEol,
|
||||||
dockerfilePath: composeOpts.dockerfilePath,
|
dockerfilePath: composeOpts.dockerfilePath,
|
||||||
nogitignore: composeOpts.nogitignore, // v13: delete this line
|
|
||||||
multiDockerignore: composeOpts.multiDockerignore,
|
multiDockerignore: composeOpts.multiDockerignore,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -19,19 +19,13 @@ import { flags } from '@oclif/command';
|
|||||||
import Command from '../../command';
|
import Command from '../../command';
|
||||||
import * as cf from '../../utils/common-flags';
|
import * as cf from '../../utils/common-flags';
|
||||||
import { getBalenaSdk, getCliForm, stripIndent } from '../../utils/lazy';
|
import { getBalenaSdk, getCliForm, stripIndent } from '../../utils/lazy';
|
||||||
import {
|
import { applicationIdInfo, devModeInfo } from '../../utils/messages';
|
||||||
applicationIdInfo,
|
|
||||||
appToFleetFlagMsg,
|
|
||||||
warnify,
|
|
||||||
} from '../../utils/messages';
|
|
||||||
import { isV13 } from '../../utils/version';
|
|
||||||
import type { PineDeferred } from 'balena-sdk';
|
import type { PineDeferred } from 'balena-sdk';
|
||||||
|
|
||||||
interface FlagsDef {
|
interface FlagsDef {
|
||||||
version: string; // OS version
|
version: string; // OS version
|
||||||
application?: string;
|
|
||||||
app?: string; // application alias
|
|
||||||
fleet?: string;
|
fleet?: string;
|
||||||
|
dev?: boolean; // balenaOS development variant
|
||||||
device?: string;
|
device?: string;
|
||||||
deviceApiKey?: string;
|
deviceApiKey?: string;
|
||||||
deviceType?: string;
|
deviceType?: string;
|
||||||
@ -54,6 +48,8 @@ export default class ConfigGenerateCmd extends Command {
|
|||||||
|
|
||||||
The target balenaOS version must be specified with the --version option.
|
The target balenaOS version must be specified with the --version option.
|
||||||
|
|
||||||
|
${devModeInfo.split('\n').join('\n\t\t')}
|
||||||
|
|
||||||
To configure an image for a fleet of mixed device types, use the --fleet option
|
To configure an image for a fleet of mixed device types, use the --fleet option
|
||||||
alongside the --deviceType option to specify the target device type.
|
alongside the --deviceType option to specify the target device type.
|
||||||
|
|
||||||
@ -68,11 +64,10 @@ export default class ConfigGenerateCmd extends Command {
|
|||||||
'$ balena config generate --device 7cf02a6 --version 2.12.7 --generate-device-api-key',
|
'$ balena config generate --device 7cf02a6 --version 2.12.7 --generate-device-api-key',
|
||||||
'$ balena config generate --device 7cf02a6 --version 2.12.7 --deviceApiKey <existingDeviceKey>',
|
'$ balena config generate --device 7cf02a6 --version 2.12.7 --deviceApiKey <existingDeviceKey>',
|
||||||
'$ balena config generate --device 7cf02a6 --version 2.12.7 --output config.json',
|
'$ balena config generate --device 7cf02a6 --version 2.12.7 --output config.json',
|
||||||
'$ balena config generate --fleet MyFleet --version 2.12.7',
|
'$ balena config generate --fleet myorg/fleet --version 2.12.7 --dev',
|
||||||
'$ balena config generate --fleet myorg/myfleet --version 2.12.7',
|
'$ balena config generate --fleet myorg/fleet --version 2.12.7 --deviceType fincm3',
|
||||||
'$ balena config generate --fleet MyFleet --version 2.12.7 --deviceType fincm3',
|
'$ balena config generate --fleet myorg/fleet --version 2.12.7 --output config.json',
|
||||||
'$ balena config generate --fleet MyFleet --version 2.12.7 --output config.json',
|
'$ balena config generate --fleet myorg/fleet --version 2.12.7 --network wifi --wifiSsid mySsid --wifiKey abcdefgh --appUpdatePollInterval 15',
|
||||||
'$ balena config generate --fleet MyFleet --version 2.12.7 --network wifi --wifiSsid mySsid --wifiKey abcdefgh --appUpdatePollInterval 15',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
public static usage = 'config generate';
|
public static usage = 'config generate';
|
||||||
@ -82,22 +77,11 @@ export default class ConfigGenerateCmd extends Command {
|
|||||||
description: 'a balenaOS version',
|
description: 'a balenaOS version',
|
||||||
required: true,
|
required: true,
|
||||||
}),
|
}),
|
||||||
...(isV13()
|
fleet: { ...cf.fleet, exclusive: ['device'] },
|
||||||
? {}
|
dev: cf.dev,
|
||||||
: {
|
|
||||||
application: {
|
|
||||||
...cf.application,
|
|
||||||
exclusive: ['app', 'fleet', 'device'],
|
|
||||||
},
|
|
||||||
app: { ...cf.app, exclusive: ['application', 'fleet', 'device'] },
|
|
||||||
appUpdatePollInterval: flags.string({
|
|
||||||
description: 'DEPRECATED alias for --updatePollInterval',
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
fleet: { ...cf.fleet, exclusive: ['application', 'app', 'device'] },
|
|
||||||
device: {
|
device: {
|
||||||
...cf.device,
|
...cf.device,
|
||||||
exclusive: ['application', 'app', 'fleet', 'provisioning-key-name'],
|
exclusive: ['fleet', 'provisioning-key-name'],
|
||||||
},
|
},
|
||||||
deviceApiKey: flags.string({
|
deviceApiKey: flags.string({
|
||||||
description:
|
description:
|
||||||
@ -130,7 +114,7 @@ export default class ConfigGenerateCmd extends Command {
|
|||||||
}),
|
}),
|
||||||
appUpdatePollInterval: flags.string({
|
appUpdatePollInterval: flags.string({
|
||||||
description:
|
description:
|
||||||
'supervisor cloud polling interval in minutes (e.g. for variable updates)',
|
'supervisor cloud polling interval in minutes (e.g. for device variables)',
|
||||||
}),
|
}),
|
||||||
'provisioning-key-name': flags.string({
|
'provisioning-key-name': flags.string({
|
||||||
description: 'custom key name assigned to generated provisioning api key',
|
description: 'custom key name assigned to generated provisioning api key',
|
||||||
@ -173,7 +157,7 @@ export default class ConfigGenerateCmd extends Command {
|
|||||||
resourceDeviceType = device.is_of__device_type[0].slug;
|
resourceDeviceType = device.is_of__device_type[0].slug;
|
||||||
} else {
|
} else {
|
||||||
// Disambiguate application (if is a number, it could either be an ID or a numerical name)
|
// Disambiguate application (if is a number, it could either be an ID or a numerical name)
|
||||||
application = (await getApplication(balena, options.application!, {
|
application = (await getApplication(balena, options.fleet!, {
|
||||||
$expand: {
|
$expand: {
|
||||||
is_for__device_type: { $select: 'slug' },
|
is_for__device_type: { $select: 'slug' },
|
||||||
},
|
},
|
||||||
@ -188,7 +172,7 @@ export default class ConfigGenerateCmd extends Command {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Check compatibility if application and deviceType provided
|
// Check compatibility if application and deviceType provided
|
||||||
if (options.application && options.deviceType) {
|
if (options.fleet && options.deviceType) {
|
||||||
const appDeviceManifest = await balena.models.device.getManifestBySlug(
|
const appDeviceManifest = await balena.models.device.getManifestBySlug(
|
||||||
resourceDeviceType,
|
resourceDeviceType,
|
||||||
);
|
);
|
||||||
@ -198,7 +182,7 @@ export default class ConfigGenerateCmd extends Command {
|
|||||||
!helpers.areDeviceTypesCompatible(appDeviceManifest, deviceManifest)
|
!helpers.areDeviceTypesCompatible(appDeviceManifest, deviceManifest)
|
||||||
) {
|
) {
|
||||||
throw new balena.errors.BalenaInvalidDeviceType(
|
throw new balena.errors.BalenaInvalidDeviceType(
|
||||||
`Device type ${options.deviceType} is incompatible with fleet ${options.application}`,
|
`Device type ${options.deviceType} is incompatible with fleet ${options.fleet}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -207,9 +191,10 @@ export default class ConfigGenerateCmd extends Command {
|
|||||||
// Pass params as an override: if there is any param with exactly the same name as a
|
// Pass params as an override: if there is any param with exactly the same name as a
|
||||||
// required option, that value is used (and the corresponding question is not asked)
|
// required option, that value is used (and the corresponding question is not asked)
|
||||||
const answers = await getCliForm().run(deviceManifest.options, {
|
const answers = await getCliForm().run(deviceManifest.options, {
|
||||||
override: options,
|
override: { ...options, app: options.fleet, application: options.fleet },
|
||||||
});
|
});
|
||||||
answers.version = options.version;
|
answers.version = options.version;
|
||||||
|
answers.developmentMode = options.dev;
|
||||||
answers.provisioningKeyName = options['provisioning-key-name'];
|
answers.provisioningKeyName = options['provisioning-key-name'];
|
||||||
|
|
||||||
// Generate config
|
// Generate config
|
||||||
@ -253,19 +238,14 @@ export default class ConfigGenerateCmd extends Command {
|
|||||||
protected async validateOptions(options: FlagsDef) {
|
protected async validateOptions(options: FlagsDef) {
|
||||||
const { ExpectedError } = await import('../../errors');
|
const { ExpectedError } = await import('../../errors');
|
||||||
|
|
||||||
if ((options.application || options.app) && process.stderr.isTTY) {
|
if (options.device == null && options.fleet == null) {
|
||||||
console.error(warnify(appToFleetFlagMsg));
|
|
||||||
}
|
|
||||||
options.application ||= options.app || options.fleet;
|
|
||||||
// Prefer options.application over options.app
|
|
||||||
delete options.app;
|
|
||||||
|
|
||||||
if (options.device == null && options.application == null) {
|
|
||||||
throw new ExpectedError(this.missingDeviceOrAppMessage);
|
throw new ExpectedError(this.missingDeviceOrAppMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!options.application && options.deviceType) {
|
if (!options.fleet && options.deviceType) {
|
||||||
throw new ExpectedError(this.deviceTypeNotAllowedMessage);
|
throw new ExpectedError(this.deviceTypeNotAllowedMessage);
|
||||||
}
|
}
|
||||||
|
const { validateDevOptionAndWarn } = await import('../../utils/config');
|
||||||
|
await validateDevOptionAndWarn(options.dev, options.version);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@ export default class ConfigInjectCmd extends Command {
|
|||||||
public static usage = 'config inject <file>';
|
public static usage = 'config inject <file>';
|
||||||
|
|
||||||
public static flags: flags.Input<FlagsDef> = {
|
public static flags: flags.Input<FlagsDef> = {
|
||||||
type: cf.deviceTypeIgnored,
|
...cf.deviceTypeIgnored,
|
||||||
drive: cf.driveOrImg,
|
drive: cf.driveOrImg,
|
||||||
help: cf.help,
|
help: cf.help,
|
||||||
};
|
};
|
||||||
|
@ -47,7 +47,7 @@ export default class ConfigReadCmd extends Command {
|
|||||||
public static usage = 'config read';
|
public static usage = 'config read';
|
||||||
|
|
||||||
public static flags: flags.Input<FlagsDef> = {
|
public static flags: flags.Input<FlagsDef> = {
|
||||||
type: cf.deviceTypeIgnored,
|
...cf.deviceTypeIgnored,
|
||||||
drive: cf.driveOrImg,
|
drive: cf.driveOrImg,
|
||||||
help: cf.help,
|
help: cf.help,
|
||||||
json: cf.json,
|
json: cf.json,
|
||||||
|
@ -50,7 +50,7 @@ export default class ConfigReconfigureCmd extends Command {
|
|||||||
public static usage = 'config reconfigure';
|
public static usage = 'config reconfigure';
|
||||||
|
|
||||||
public static flags: flags.Input<FlagsDef> = {
|
public static flags: flags.Input<FlagsDef> = {
|
||||||
type: cf.deviceTypeIgnored,
|
...cf.deviceTypeIgnored,
|
||||||
drive: cf.driveOrImg,
|
drive: cf.driveOrImg,
|
||||||
advanced: flags.boolean({
|
advanced: flags.boolean({
|
||||||
description: 'show advanced commands',
|
description: 'show advanced commands',
|
||||||
|
@ -64,7 +64,7 @@ export default class ConfigWriteCmd extends Command {
|
|||||||
public static usage = 'config write <key> <value>';
|
public static usage = 'config write <key> <value>';
|
||||||
|
|
||||||
public static flags: flags.Input<FlagsDef> = {
|
public static flags: flags.Input<FlagsDef> = {
|
||||||
type: cf.deviceTypeIgnored,
|
...cf.deviceTypeIgnored,
|
||||||
drive: cf.driveOrImg,
|
drive: cf.driveOrImg,
|
||||||
help: cf.help,
|
help: cf.help,
|
||||||
};
|
};
|
||||||
|
@ -319,7 +319,6 @@ ${dockerignoreHelp}
|
|||||||
inlineLogs: composeOpts.inlineLogs,
|
inlineLogs: composeOpts.inlineLogs,
|
||||||
convertEol: composeOpts.convertEol,
|
convertEol: composeOpts.convertEol,
|
||||||
dockerfilePath: composeOpts.dockerfilePath,
|
dockerfilePath: composeOpts.dockerfilePath,
|
||||||
nogitignore: composeOpts.nogitignore, // v13: delete this line
|
|
||||||
multiDockerignore: composeOpts.multiDockerignore,
|
multiDockerignore: composeOpts.multiDockerignore,
|
||||||
});
|
});
|
||||||
builtImagesByService = _.keyBy(builtImages, 'serviceName');
|
builtImagesByService = _.keyBy(builtImages, 'serviceName');
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* @license
|
* @license
|
||||||
* Copyright 2016-2020 Balena Ltd.
|
* Copyright 2016 Balena Ltd.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -21,15 +21,15 @@ import Command from '../../command';
|
|||||||
import * as cf from '../../utils/common-flags';
|
import * as cf from '../../utils/common-flags';
|
||||||
import { expandForAppName } from '../../utils/helpers';
|
import { expandForAppName } from '../../utils/helpers';
|
||||||
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
|
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
|
||||||
import { appToFleetOutputMsg, warnify } from '../../utils/messages';
|
|
||||||
import { tryAsInteger } from '../../utils/validation';
|
import { tryAsInteger } from '../../utils/validation';
|
||||||
import { isV13 } from '../../utils/version';
|
|
||||||
|
|
||||||
import type { Application, Release } from 'balena-sdk';
|
import type { Application, Release } from 'balena-sdk';
|
||||||
|
import type { DataOutputOptions } from '../../framework';
|
||||||
|
|
||||||
|
import { isV14 } from '../../utils/version';
|
||||||
|
|
||||||
interface ExtendedDevice extends DeviceWithDeviceType {
|
interface ExtendedDevice extends DeviceWithDeviceType {
|
||||||
dashboard_url?: string;
|
dashboard_url?: string;
|
||||||
application_name?: string;
|
fleet: string; // 'org/name' slug
|
||||||
device_type?: string;
|
device_type?: string;
|
||||||
commit?: string;
|
commit?: string;
|
||||||
last_seen?: string;
|
last_seen?: string;
|
||||||
@ -44,9 +44,8 @@ interface ExtendedDevice extends DeviceWithDeviceType {
|
|||||||
undervoltage_detected?: boolean;
|
undervoltage_detected?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FlagsDef {
|
interface FlagsDef extends DataOutputOptions {
|
||||||
help: void;
|
help: void;
|
||||||
v13: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ArgsDef {
|
interface ArgsDef {
|
||||||
@ -74,7 +73,7 @@ export default class DeviceCmd extends Command {
|
|||||||
|
|
||||||
public static flags: flags.Input<FlagsDef> = {
|
public static flags: flags.Input<FlagsDef> = {
|
||||||
help: cf.help,
|
help: cf.help,
|
||||||
v13: cf.v13,
|
...(isV14() ? cf.dataOutputFlags : {}),
|
||||||
};
|
};
|
||||||
|
|
||||||
public static authenticated = true;
|
public static authenticated = true;
|
||||||
@ -84,7 +83,6 @@ export default class DeviceCmd extends Command {
|
|||||||
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
|
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
|
||||||
DeviceCmd,
|
DeviceCmd,
|
||||||
);
|
);
|
||||||
const useAppWord = !options.v13 && !isV13();
|
|
||||||
|
|
||||||
const balena = getBalenaSdk();
|
const balena = getBalenaSdk();
|
||||||
|
|
||||||
@ -121,8 +119,8 @@ export default class DeviceCmd extends Command {
|
|||||||
|
|
||||||
const belongsToApplication =
|
const belongsToApplication =
|
||||||
device.belongs_to__application as Application[];
|
device.belongs_to__application as Application[];
|
||||||
device.application_name = belongsToApplication?.[0]
|
device.fleet = belongsToApplication?.[0]
|
||||||
? belongsToApplication[0].app_name
|
? belongsToApplication[0].slug
|
||||||
: 'N/a';
|
: 'N/a';
|
||||||
|
|
||||||
device.device_type = device.is_of__device_type[0].slug;
|
device.device_type = device.is_of__device_type[0].slug;
|
||||||
@ -170,41 +168,52 @@ export default class DeviceCmd extends Command {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (useAppWord && process.stderr.isTTY) {
|
const outputFields = [
|
||||||
console.error(warnify(appToFleetOutputMsg));
|
'device_name',
|
||||||
}
|
'id',
|
||||||
|
'device_type',
|
||||||
|
'status',
|
||||||
|
'is_online',
|
||||||
|
'ip_address',
|
||||||
|
'public_address',
|
||||||
|
'mac_address',
|
||||||
|
'fleet',
|
||||||
|
'last_seen',
|
||||||
|
'uuid',
|
||||||
|
'commit',
|
||||||
|
'supervisor_version',
|
||||||
|
'is_web_accessible',
|
||||||
|
'note',
|
||||||
|
'os_version',
|
||||||
|
'dashboard_url',
|
||||||
|
'cpu_usage_percent',
|
||||||
|
'cpu_temp_c',
|
||||||
|
'cpu_id',
|
||||||
|
'memory_usage_mb',
|
||||||
|
'memory_total_mb',
|
||||||
|
'memory_usage_percent',
|
||||||
|
'storage_block_device',
|
||||||
|
'storage_usage_mb',
|
||||||
|
'storage_total_mb',
|
||||||
|
'storage_usage_percent',
|
||||||
|
'undervoltage_detected',
|
||||||
|
];
|
||||||
|
|
||||||
console.log(
|
if (isV14()) {
|
||||||
getVisuals().table.vertical(device, [
|
await this.outputData(device, outputFields, {
|
||||||
`$${device.device_name}$`,
|
...options,
|
||||||
'id',
|
hideNullOrUndefinedValues: true,
|
||||||
'device_type',
|
titleField: 'device_name',
|
||||||
'status',
|
});
|
||||||
'is_online',
|
} else {
|
||||||
'ip_address',
|
// Old output implementation
|
||||||
'public_address',
|
outputFields.unshift(`$${device.device_name}$`);
|
||||||
'mac_address',
|
console.log(
|
||||||
useAppWord ? 'application_name' : 'application_name => FLEET',
|
getVisuals().table.vertical(
|
||||||
'last_seen',
|
device,
|
||||||
'uuid',
|
outputFields.filter((f) => f !== 'device_name'),
|
||||||
'commit',
|
),
|
||||||
'supervisor_version',
|
);
|
||||||
'is_web_accessible',
|
}
|
||||||
'note',
|
|
||||||
'os_version',
|
|
||||||
'dashboard_url',
|
|
||||||
'cpu_usage_percent',
|
|
||||||
'cpu_temp_c',
|
|
||||||
'cpu_id',
|
|
||||||
'memory_usage_mb',
|
|
||||||
'memory_total_mb',
|
|
||||||
'memory_usage_percent',
|
|
||||||
'storage_block_device',
|
|
||||||
'storage_usage_mb',
|
|
||||||
'storage_total_mb',
|
|
||||||
'storage_usage_percent',
|
|
||||||
'undervoltage_detected',
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,17 +19,10 @@ import { flags } from '@oclif/command';
|
|||||||
import Command from '../../command';
|
import Command from '../../command';
|
||||||
import * as cf from '../../utils/common-flags';
|
import * as cf from '../../utils/common-flags';
|
||||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
||||||
import {
|
import { applicationIdInfo } from '../../utils/messages';
|
||||||
applicationIdInfo,
|
|
||||||
appToFleetFlagMsg,
|
|
||||||
warnify,
|
|
||||||
} from '../../utils/messages';
|
|
||||||
import { runCommand } from '../../utils/helpers';
|
import { runCommand } from '../../utils/helpers';
|
||||||
import { isV13 } from '../../utils/version';
|
|
||||||
|
|
||||||
interface FlagsDef {
|
interface FlagsDef {
|
||||||
application?: string;
|
|
||||||
app?: string;
|
|
||||||
fleet?: string;
|
fleet?: string;
|
||||||
yes: boolean;
|
yes: boolean;
|
||||||
advanced: boolean;
|
advanced: boolean;
|
||||||
@ -82,12 +75,6 @@ export default class DeviceInitCmd extends Command {
|
|||||||
public static usage = 'device init';
|
public static usage = 'device init';
|
||||||
|
|
||||||
public static flags: flags.Input<FlagsDef> = {
|
public static flags: flags.Input<FlagsDef> = {
|
||||||
...(isV13()
|
|
||||||
? {}
|
|
||||||
: {
|
|
||||||
application: cf.application,
|
|
||||||
app: cf.app,
|
|
||||||
}),
|
|
||||||
fleet: cf.fleet,
|
fleet: cf.fleet,
|
||||||
yes: cf.yes,
|
yes: cf.yes,
|
||||||
advanced: flags.boolean({
|
advanced: flags.boolean({
|
||||||
@ -130,17 +117,10 @@ export default class DeviceInitCmd extends Command {
|
|||||||
const logger = await Command.getLogger();
|
const logger = await Command.getLogger();
|
||||||
const balena = getBalenaSdk();
|
const balena = getBalenaSdk();
|
||||||
|
|
||||||
if ((options.application || options.app) && process.stderr.isTTY) {
|
|
||||||
console.error(warnify(appToFleetFlagMsg));
|
|
||||||
}
|
|
||||||
// Consolidate application options
|
|
||||||
options.application ||= options.app || options.fleet;
|
|
||||||
delete options.app;
|
|
||||||
|
|
||||||
// Get application and
|
// Get application and
|
||||||
const application = (await getApplication(
|
const application = (await getApplication(
|
||||||
balena,
|
balena,
|
||||||
options['application'] ||
|
options.fleet ||
|
||||||
(
|
(
|
||||||
await (await import('../../utils/patterns')).selectApplication()
|
await (await import('../../utils/patterns')).selectApplication()
|
||||||
).id,
|
).id,
|
||||||
@ -155,7 +135,7 @@ export default class DeviceInitCmd extends Command {
|
|||||||
|
|
||||||
// Register new device
|
// Register new device
|
||||||
const deviceUuid = balena.models.device.generateUniqueKey();
|
const deviceUuid = balena.models.device.generateUniqueKey();
|
||||||
console.info(`Registering to ${application.app_name}: ${deviceUuid}`);
|
console.info(`Registering to ${application.slug}: ${deviceUuid}`);
|
||||||
await balena.models.device.register(application.id, deviceUuid);
|
await balena.models.device.register(application.id, deviceUuid);
|
||||||
const device = await balena.models.device.get(deviceUuid);
|
const device = await balena.models.device.get(deviceUuid);
|
||||||
|
|
||||||
|
@ -27,12 +27,7 @@ import Command from '../../command';
|
|||||||
import * as cf from '../../utils/common-flags';
|
import * as cf from '../../utils/common-flags';
|
||||||
import { ExpectedError } from '../../errors';
|
import { ExpectedError } from '../../errors';
|
||||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
||||||
import {
|
import { applicationIdInfo } from '../../utils/messages';
|
||||||
applicationIdInfo,
|
|
||||||
appToFleetFlagMsg,
|
|
||||||
warnify,
|
|
||||||
} from '../../utils/messages';
|
|
||||||
import { isV13 } from '../../utils/version';
|
|
||||||
|
|
||||||
type ExtendedDevice = PineTypedResult<
|
type ExtendedDevice = PineTypedResult<
|
||||||
Device,
|
Device,
|
||||||
@ -42,8 +37,6 @@ type ExtendedDevice = PineTypedResult<
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface FlagsDef {
|
interface FlagsDef {
|
||||||
application?: string;
|
|
||||||
app?: string;
|
|
||||||
fleet?: string;
|
fleet?: string;
|
||||||
help: void;
|
help: void;
|
||||||
}
|
}
|
||||||
@ -82,7 +75,6 @@ export default class DeviceMoveCmd extends Command {
|
|||||||
public static usage = 'device move <uuid(s)>';
|
public static usage = 'device move <uuid(s)>';
|
||||||
|
|
||||||
public static flags: flags.Input<FlagsDef> = {
|
public static flags: flags.Input<FlagsDef> = {
|
||||||
...(isV13() ? {} : { app: cf.app, application: cf.application }),
|
|
||||||
fleet: cf.fleet,
|
fleet: cf.fleet,
|
||||||
help: cf.help,
|
help: cf.help,
|
||||||
};
|
};
|
||||||
@ -94,11 +86,6 @@ export default class DeviceMoveCmd extends Command {
|
|||||||
DeviceMoveCmd,
|
DeviceMoveCmd,
|
||||||
);
|
);
|
||||||
|
|
||||||
if ((options.application || options.app) && process.stderr.isTTY) {
|
|
||||||
console.error(warnify(appToFleetFlagMsg));
|
|
||||||
}
|
|
||||||
options.application ||= options.app || options.fleet;
|
|
||||||
|
|
||||||
const balena = getBalenaSdk();
|
const balena = getBalenaSdk();
|
||||||
|
|
||||||
const { tryAsInteger } = await import('../../utils/validation');
|
const { tryAsInteger } = await import('../../utils/validation');
|
||||||
@ -132,8 +119,8 @@ export default class DeviceMoveCmd extends Command {
|
|||||||
const { getApplication } = await import('../../utils/sdk');
|
const { getApplication } = await import('../../utils/sdk');
|
||||||
|
|
||||||
// Get destination application
|
// Get destination application
|
||||||
const application = options.application
|
const application = options.fleet
|
||||||
? await getApplication(balena, options.application)
|
? await getApplication(balena, options.fleet)
|
||||||
: await this.interactivelySelectApplication(balena, devices);
|
: await this.interactivelySelectApplication(balena, devices);
|
||||||
|
|
||||||
// Move each device
|
// Move each device
|
||||||
|
@ -75,7 +75,7 @@ export default class DeviceRegisterCmd extends Command {
|
|||||||
const application = await getApplication(balena, params.fleet);
|
const application = await getApplication(balena, params.fleet);
|
||||||
const uuid = options.uuid ?? balena.models.device.generateUniqueKey();
|
const uuid = options.uuid ?? balena.models.device.generateUniqueKey();
|
||||||
|
|
||||||
console.info(`Registering to ${application.app_name}: ${uuid}`);
|
console.info(`Registering to ${application.slug}: ${uuid}`);
|
||||||
|
|
||||||
const result = await balena.models.device.register(application.id, uuid);
|
const result = await balena.models.device.register(application.id, uuid);
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* @license
|
* @license
|
||||||
* Copyright 2016-2020 Balena Ltd.
|
* Copyright 2016 Balena Ltd.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -20,30 +20,22 @@ import Command from '../../command';
|
|||||||
import * as cf from '../../utils/common-flags';
|
import * as cf from '../../utils/common-flags';
|
||||||
import { expandForAppName } from '../../utils/helpers';
|
import { expandForAppName } from '../../utils/helpers';
|
||||||
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
|
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
|
||||||
import {
|
import { applicationIdInfo, jsonInfo } from '../../utils/messages';
|
||||||
applicationIdInfo,
|
|
||||||
appToFleetFlagMsg,
|
|
||||||
appToFleetOutputMsg,
|
|
||||||
jsonInfo,
|
|
||||||
warnify,
|
|
||||||
} from '../../utils/messages';
|
|
||||||
import { isV13 } from '../../utils/version';
|
|
||||||
|
|
||||||
import type { Application } from 'balena-sdk';
|
import type { Application } from 'balena-sdk';
|
||||||
|
import type { DataSetOutputOptions } from '../../framework';
|
||||||
|
|
||||||
|
import { isV14 } from '../../utils/version';
|
||||||
|
|
||||||
interface ExtendedDevice extends DeviceWithDeviceType {
|
interface ExtendedDevice extends DeviceWithDeviceType {
|
||||||
dashboard_url?: string;
|
dashboard_url?: string;
|
||||||
application_name?: string | null;
|
fleet?: string | null; // 'org/name' slug
|
||||||
device_type?: string | null;
|
device_type?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FlagsDef {
|
interface FlagsDef extends DataSetOutputOptions {
|
||||||
application?: string;
|
|
||||||
app?: string;
|
|
||||||
fleet?: string;
|
fleet?: string;
|
||||||
help: void;
|
help: void;
|
||||||
json: boolean;
|
json?: boolean;
|
||||||
v13: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class DevicesCmd extends Command {
|
export default class DevicesCmd extends Command {
|
||||||
@ -67,50 +59,24 @@ export default class DevicesCmd extends Command {
|
|||||||
public static usage = 'devices';
|
public static usage = 'devices';
|
||||||
|
|
||||||
public static flags: flags.Input<FlagsDef> = {
|
public static flags: flags.Input<FlagsDef> = {
|
||||||
...(isV13()
|
fleet: cf.fleet,
|
||||||
? {}
|
...(isV14() ? cf.dataSetOutputFlags : { json: cf.json }),
|
||||||
: {
|
|
||||||
application: {
|
|
||||||
...cf.application,
|
|
||||||
exclusive: ['app', 'fleet', 'v13'],
|
|
||||||
},
|
|
||||||
app: { ...cf.app, exclusive: ['application', 'fleet', 'v13'] },
|
|
||||||
}),
|
|
||||||
fleet: { ...cf.fleet, exclusive: ['app', 'application'] },
|
|
||||||
json: cf.json,
|
|
||||||
help: cf.help,
|
help: cf.help,
|
||||||
v13: cf.v13,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public static primary = true;
|
public static primary = true;
|
||||||
|
|
||||||
public static authenticated = true;
|
public static authenticated = true;
|
||||||
|
|
||||||
protected useAppWord = false;
|
|
||||||
protected hasWarned = false;
|
|
||||||
|
|
||||||
public async run() {
|
public async run() {
|
||||||
const { flags: options } = this.parse<FlagsDef, {}>(DevicesCmd);
|
const { flags: options } = this.parse<FlagsDef, {}>(DevicesCmd);
|
||||||
this.useAppWord = !options.fleet && !options.v13 && !isV13();
|
|
||||||
|
|
||||||
const balena = getBalenaSdk();
|
const balena = getBalenaSdk();
|
||||||
|
|
||||||
if (
|
|
||||||
(options.application || options.app) &&
|
|
||||||
!options.json &&
|
|
||||||
process.stderr.isTTY
|
|
||||||
) {
|
|
||||||
this.hasWarned = true;
|
|
||||||
console.error(warnify(appToFleetFlagMsg));
|
|
||||||
}
|
|
||||||
// Consolidate application options
|
|
||||||
options.application ||= options.app || options.fleet;
|
|
||||||
|
|
||||||
let devices;
|
let devices;
|
||||||
|
|
||||||
if (options.application != null) {
|
if (options.fleet != null) {
|
||||||
const { getApplication } = await import('../../utils/sdk');
|
const { getApplication } = await import('../../utils/sdk');
|
||||||
const application = await getApplication(balena, options.application);
|
const application = await getApplication(balena, options.fleet);
|
||||||
devices = (await balena.models.device.getAllByApplication(
|
devices = (await balena.models.device.getAllByApplication(
|
||||||
application.id,
|
application.id,
|
||||||
expandForAppName,
|
expandForAppName,
|
||||||
@ -126,7 +92,7 @@ export default class DevicesCmd extends Command {
|
|||||||
|
|
||||||
const belongsToApplication =
|
const belongsToApplication =
|
||||||
device.belongs_to__application as Application[];
|
device.belongs_to__application as Application[];
|
||||||
device.application_name = belongsToApplication?.[0]?.app_name || null;
|
device.fleet = belongsToApplication?.[0]?.slug || null;
|
||||||
|
|
||||||
device.uuid = options.json ? device.uuid : device.uuid.slice(0, 7);
|
device.uuid = options.json ? device.uuid : device.uuid.slice(0, 7);
|
||||||
|
|
||||||
@ -134,38 +100,52 @@ export default class DevicesCmd extends Command {
|
|||||||
return device;
|
return device;
|
||||||
});
|
});
|
||||||
|
|
||||||
const jName = this.useAppWord ? 'application_name' : 'fleet_name';
|
if (isV14()) {
|
||||||
const tName = this.useAppWord ? 'APPLICATION NAME' : 'FLEET';
|
const outputFields = [
|
||||||
const fields = [
|
'id',
|
||||||
'id',
|
'uuid',
|
||||||
'uuid',
|
'device_name',
|
||||||
'device_name',
|
'device_type',
|
||||||
'device_type',
|
'fleet',
|
||||||
options.json
|
'status',
|
||||||
? `application_name => ${jName}`
|
'is_online',
|
||||||
: `application_name => ${tName}`,
|
'supervisor_version',
|
||||||
'status',
|
'os_version',
|
||||||
'is_online',
|
'dashboard_url',
|
||||||
'supervisor_version',
|
];
|
||||||
'os_version',
|
|
||||||
'dashboard_url',
|
|
||||||
];
|
|
||||||
|
|
||||||
if (options.json) {
|
await this.outputData(devices, outputFields, {
|
||||||
const { pickAndRename } = await import('../../utils/helpers');
|
...options,
|
||||||
const mapped = devices.map((device) => pickAndRename(device, fields));
|
displayNullValuesAs: 'N/a',
|
||||||
console.log(JSON.stringify(mapped, null, 4));
|
});
|
||||||
} else {
|
} else {
|
||||||
if (!this.hasWarned && this.useAppWord && process.stderr.isTTY) {
|
// Old output implementation
|
||||||
console.error(warnify(appToFleetOutputMsg));
|
const fields = [
|
||||||
|
'id',
|
||||||
|
'uuid',
|
||||||
|
'device_name',
|
||||||
|
'device_type',
|
||||||
|
'fleet',
|
||||||
|
'status',
|
||||||
|
'is_online',
|
||||||
|
'supervisor_version',
|
||||||
|
'os_version',
|
||||||
|
'dashboard_url',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (options.json) {
|
||||||
|
const { pickAndRename } = await import('../../utils/helpers');
|
||||||
|
const mapped = devices.map((device) => pickAndRename(device, fields));
|
||||||
|
console.log(JSON.stringify(mapped, null, 4));
|
||||||
|
} else {
|
||||||
|
const _ = await import('lodash');
|
||||||
|
console.log(
|
||||||
|
getVisuals().table.horizontal(
|
||||||
|
devices.map((dev) => _.mapValues(dev, (val) => val ?? 'N/a')),
|
||||||
|
fields,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
const _ = await import('lodash');
|
|
||||||
console.log(
|
|
||||||
getVisuals().table.horizontal(
|
|
||||||
devices.map((dev) => _.mapValues(dev, (val) => val ?? 'N/a')),
|
|
||||||
fields,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* @license
|
* @license
|
||||||
* Copyright 2016-2021 Balena Ltd.
|
* Copyright 2016 Balena Ltd.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -17,37 +17,24 @@
|
|||||||
import { flags } from '@oclif/command';
|
import { flags } from '@oclif/command';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
import Command from '../../command';
|
import Command from '../../command';
|
||||||
|
|
||||||
import * as cf from '../../utils/common-flags';
|
import * as cf from '../../utils/common-flags';
|
||||||
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
|
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
|
||||||
import { CommandHelp } from '../../utils/oclif-utils';
|
import { CommandHelp } from '../../utils/oclif-utils';
|
||||||
import { isV13 } from '../../utils/version';
|
import type { DataSetOutputOptions } from '../../framework';
|
||||||
|
|
||||||
interface FlagsDef {
|
import { isV14 } from '../../utils/version';
|
||||||
discontinued: boolean;
|
|
||||||
|
interface FlagsDef extends DataSetOutputOptions {
|
||||||
help: void;
|
help: void;
|
||||||
json?: boolean;
|
json?: boolean;
|
||||||
verbose?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const deprecatedInfo = isV13()
|
|
||||||
? ''
|
|
||||||
: `
|
|
||||||
The --verbose option may add extra columns/fields to the output. Currently
|
|
||||||
this includes the "STATE" column which is DEPRECATED and whose values are one
|
|
||||||
of 'new', 'released' or 'discontinued'. However, 'discontinued' device types
|
|
||||||
are only listed if the '--discontinued' option is also used, and this option
|
|
||||||
is also DEPRECATED.
|
|
||||||
`
|
|
||||||
.split('\n')
|
|
||||||
.join(`\n\t\t`);
|
|
||||||
|
|
||||||
export default class DevicesSupportedCmd extends Command {
|
export default class DevicesSupportedCmd extends Command {
|
||||||
public static description = stripIndent`
|
public static description = stripIndent`
|
||||||
List the supported device types (like 'raspberrypi3' or 'intel-nuc').
|
List the supported device types (like 'raspberrypi3' or 'intel-nuc').
|
||||||
|
|
||||||
List the supported device types (like 'raspberrypi3' or 'intel-nuc').
|
List the supported device types (like 'raspberrypi3' or 'intel-nuc').
|
||||||
${deprecatedInfo}
|
|
||||||
The --json option is recommended when scripting the output of this command,
|
The --json option is recommended when scripting the output of this command,
|
||||||
because the JSON format is less likely to change and it better represents data
|
because the JSON format is less likely to change and it better represents data
|
||||||
types like lists and empty strings (for example, the ALIASES column contains a
|
types like lists and empty strings (for example, the ALIASES column contains a
|
||||||
@ -56,8 +43,7 @@ export default class DevicesSupportedCmd extends Command {
|
|||||||
`;
|
`;
|
||||||
public static examples = [
|
public static examples = [
|
||||||
'$ balena devices supported',
|
'$ balena devices supported',
|
||||||
'$ balena devices supported --verbose',
|
'$ balena devices supported --json',
|
||||||
'$ balena devices supported -vj',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
public static usage = (
|
public static usage = (
|
||||||
@ -66,22 +52,8 @@ export default class DevicesSupportedCmd extends Command {
|
|||||||
).trim();
|
).trim();
|
||||||
|
|
||||||
public static flags: flags.Input<FlagsDef> = {
|
public static flags: flags.Input<FlagsDef> = {
|
||||||
discontinued: flags.boolean({
|
|
||||||
description: isV13()
|
|
||||||
? 'No effect (DEPRECATED)'
|
|
||||||
: 'include "discontinued" device types (DEPRECATED)',
|
|
||||||
}),
|
|
||||||
help: cf.help,
|
help: cf.help,
|
||||||
json: flags.boolean({
|
...(isV14() ? cf.dataSetOutputFlags : { json: cf.json }),
|
||||||
char: 'j',
|
|
||||||
description: 'produce JSON output instead of tabular output',
|
|
||||||
}),
|
|
||||||
verbose: flags.boolean({
|
|
||||||
char: 'v',
|
|
||||||
description: isV13()
|
|
||||||
? 'No effect (DEPRECATED)'
|
|
||||||
: 'add extra columns in the tabular output (DEPRECATED)',
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public async run() {
|
public async run() {
|
||||||
@ -95,60 +67,41 @@ export default class DevicesSupportedCmd extends Command {
|
|||||||
]);
|
]);
|
||||||
const dtsBySlug = _.keyBy(dts, (dt) => dt.slug);
|
const dtsBySlug = _.keyBy(dts, (dt) => dt.slug);
|
||||||
const configDTsBySlug = _.keyBy(configDTs, (dt) => dt.slug);
|
const configDTsBySlug = _.keyBy(configDTs, (dt) => dt.slug);
|
||||||
const discontinuedDTs = isV13()
|
|
||||||
? []
|
|
||||||
: configDTs.filter((dt) => dt.state === 'DISCONTINUED');
|
|
||||||
const discontinuedDTsBySlug = _.keyBy(discontinuedDTs, (dt) => dt.slug);
|
|
||||||
// set of slugs from models.deviceType.getAllSupported() plus slugs of
|
|
||||||
// discontinued device types as per models.config.getDeviceTypes()
|
|
||||||
const slugsOfInterest = new Set([
|
|
||||||
...Object.keys(dtsBySlug),
|
|
||||||
...Object.keys(discontinuedDTsBySlug),
|
|
||||||
]);
|
|
||||||
interface DT {
|
interface DT {
|
||||||
slug: string;
|
slug: string;
|
||||||
aliases: string[];
|
aliases: string[] | string;
|
||||||
arch: string;
|
arch: string;
|
||||||
state?: string; // to be removed in CLI v13
|
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
let deviceTypes: DT[] = [];
|
let deviceTypes: DT[] = [];
|
||||||
for (const slug of slugsOfInterest) {
|
for (const slug of Object.keys(dtsBySlug)) {
|
||||||
const configDT: Partial<typeof configDTs[0]> =
|
const configDT: Partial<typeof configDTs[0]> =
|
||||||
configDTsBySlug[slug] || {};
|
configDTsBySlug[slug] || {};
|
||||||
if (configDT.state === 'DISCONTINUED' && !options.discontinued) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const dt: Partial<typeof dts[0]> = dtsBySlug[slug] || {};
|
|
||||||
const aliases = (configDT.aliases || []).filter(
|
const aliases = (configDT.aliases || []).filter(
|
||||||
(alias) => alias !== slug,
|
(alias) => alias !== slug,
|
||||||
);
|
);
|
||||||
|
const dt: Partial<typeof dts[0]> = dtsBySlug[slug] || {};
|
||||||
deviceTypes.push({
|
deviceTypes.push({
|
||||||
slug,
|
slug,
|
||||||
aliases: options.json ? aliases : [aliases.join(', ')],
|
aliases: options.json ? aliases : aliases.join(', '),
|
||||||
arch:
|
arch: (dt.is_of__cpu_architecture as any)?.[0]?.slug || 'n/a',
|
||||||
(dt.is_of__cpu_architecture as any)?.[0]?.slug ||
|
name: dt.name || 'N/A',
|
||||||
configDT.arch ||
|
|
||||||
'n/a',
|
|
||||||
// 'BETA' renamed to 'NEW'
|
|
||||||
// https://www.flowdock.com/app/rulemotion/i-cli/threads/1svvyaf8FAZeSdG4dPJc4kHOvJU
|
|
||||||
state: isV13()
|
|
||||||
? undefined
|
|
||||||
: (configDT.state || 'NEW').replace('BETA', 'NEW'),
|
|
||||||
name: dt.name || configDT.name || 'N/A',
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const fields =
|
const fields = ['slug', 'aliases', 'arch', 'name'];
|
||||||
options.verbose && !isV13()
|
|
||||||
? ['slug', 'aliases', 'arch', 'state', 'name']
|
|
||||||
: ['slug', 'aliases', 'arch', 'name'];
|
|
||||||
deviceTypes = _.sortBy(deviceTypes, fields);
|
deviceTypes = _.sortBy(deviceTypes, fields);
|
||||||
if (options.json) {
|
|
||||||
console.log(JSON.stringify(deviceTypes, null, 4));
|
if (isV14()) {
|
||||||
|
await this.outputData(deviceTypes, fields, options);
|
||||||
} else {
|
} else {
|
||||||
const visuals = getVisuals();
|
// Old output implementation
|
||||||
const output = await visuals.table.horizontal(deviceTypes, fields);
|
if (options.json) {
|
||||||
console.log(output);
|
console.log(JSON.stringify(deviceTypes, null, 4));
|
||||||
|
} else {
|
||||||
|
const visuals = getVisuals();
|
||||||
|
const output = await visuals.table.horizontal(deviceTypes, fields);
|
||||||
|
console.log(output);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
32
lib/commands/env/add.ts
vendored
32
lib/commands/env/add.ts
vendored
@ -21,15 +21,9 @@ import Command from '../../command';
|
|||||||
import { ExpectedError } from '../../errors';
|
import { ExpectedError } from '../../errors';
|
||||||
import * as cf from '../../utils/common-flags';
|
import * as cf from '../../utils/common-flags';
|
||||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
||||||
import {
|
import { applicationIdInfo } from '../../utils/messages';
|
||||||
applicationIdInfo,
|
|
||||||
appToFleetFlagMsg,
|
|
||||||
warnify,
|
|
||||||
} from '../../utils/messages';
|
|
||||||
import { isV13 } from '../../utils/version';
|
|
||||||
|
|
||||||
interface FlagsDef {
|
interface FlagsDef {
|
||||||
application?: string;
|
|
||||||
fleet?: string;
|
fleet?: string;
|
||||||
device?: string; // device UUID
|
device?: string; // device UUID
|
||||||
help: void;
|
help: void;
|
||||||
@ -101,11 +95,8 @@ export default class EnvAddCmd extends Command {
|
|||||||
public static usage = 'env add <name> [value]';
|
public static usage = 'env add <name> [value]';
|
||||||
|
|
||||||
public static flags: flags.Input<FlagsDef> = {
|
public static flags: flags.Input<FlagsDef> = {
|
||||||
...(isV13()
|
fleet: { ...cf.fleet, exclusive: ['device'] },
|
||||||
? {}
|
device: { ...cf.device, exclusive: ['fleet'] },
|
||||||
: { application: { ...cf.application, exclusive: ['fleet', 'device'] } }),
|
|
||||||
fleet: { ...cf.fleet, exclusive: ['application', 'device'] },
|
|
||||||
device: { ...cf.device, exclusive: ['application', 'fleet'] },
|
|
||||||
help: cf.help,
|
help: cf.help,
|
||||||
quiet: cf.quiet,
|
quiet: cf.quiet,
|
||||||
service: cf.service,
|
service: cf.service,
|
||||||
@ -117,11 +108,7 @@ export default class EnvAddCmd extends Command {
|
|||||||
);
|
);
|
||||||
const cmd = this;
|
const cmd = this;
|
||||||
|
|
||||||
if (options.application && process.stderr.isTTY) {
|
if (!options.fleet && !options.device) {
|
||||||
console.error(warnify(appToFleetFlagMsg));
|
|
||||||
}
|
|
||||||
options.application ||= options.fleet;
|
|
||||||
if (!options.application && !options.device) {
|
|
||||||
throw new ExpectedError(
|
throw new ExpectedError(
|
||||||
'Either the --fleet or the --device option must be specified',
|
'Either the --fleet or the --device option must be specified',
|
||||||
);
|
);
|
||||||
@ -163,11 +150,12 @@ export default class EnvAddCmd extends Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const varType = isConfigVar ? 'configVar' : 'envVar';
|
const varType = isConfigVar ? 'configVar' : 'envVar';
|
||||||
if (options.application) {
|
if (options.fleet) {
|
||||||
for (const app of options.application.split(',')) {
|
const { getFleetSlug } = await import('../../utils/sdk');
|
||||||
|
for (const app of options.fleet.split(',')) {
|
||||||
try {
|
try {
|
||||||
await balena.models.application[varType].set(
|
await balena.models.application[varType].set(
|
||||||
app,
|
await getFleetSlug(balena, app),
|
||||||
params.name,
|
params.name,
|
||||||
params.value,
|
params.value,
|
||||||
);
|
);
|
||||||
@ -201,8 +189,8 @@ async function setServiceVars(
|
|||||||
params: ArgsDef,
|
params: ArgsDef,
|
||||||
options: FlagsDef,
|
options: FlagsDef,
|
||||||
) {
|
) {
|
||||||
if (options.application) {
|
if (options.fleet) {
|
||||||
for (const app of options.application.split(',')) {
|
for (const app of options.fleet.split(',')) {
|
||||||
for (const service of options.service!.split(',')) {
|
for (const service of options.service!.split(',')) {
|
||||||
try {
|
try {
|
||||||
const serviceId = await getServiceIdForApp(sdk, app, service);
|
const serviceId = await getServiceIdForApp(sdk, app, service);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* @license
|
* @license
|
||||||
* Copyright 2016-2021 Balena Ltd.
|
* Copyright 2016 Balena Ltd.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -21,42 +21,36 @@ import Command from '../command';
|
|||||||
import { ExpectedError } from '../errors';
|
import { ExpectedError } from '../errors';
|
||||||
import * as cf from '../utils/common-flags';
|
import * as cf from '../utils/common-flags';
|
||||||
import { getBalenaSdk, getVisuals, stripIndent } from '../utils/lazy';
|
import { getBalenaSdk, getVisuals, stripIndent } from '../utils/lazy';
|
||||||
import {
|
import { applicationIdInfo } from '../utils/messages';
|
||||||
applicationIdInfo,
|
import type { DataSetOutputOptions } from '../framework';
|
||||||
appToFleetFlagMsg,
|
|
||||||
appToFleetOutputMsg,
|
|
||||||
warnify,
|
|
||||||
} from '../utils/messages';
|
|
||||||
import { isV13 } from '../utils/version';
|
|
||||||
|
|
||||||
interface FlagsDef {
|
import { isV14 } from '../utils/version';
|
||||||
application?: string;
|
|
||||||
|
interface FlagsDef extends DataSetOutputOptions {
|
||||||
fleet?: string;
|
fleet?: string;
|
||||||
config: boolean;
|
config: boolean;
|
||||||
device?: string; // device UUID
|
device?: string; // device UUID
|
||||||
json: boolean;
|
json?: boolean;
|
||||||
help: void;
|
help: void;
|
||||||
service?: string; // service name
|
service?: string; // service name
|
||||||
verbose: boolean;
|
|
||||||
v13: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface EnvironmentVariableInfo extends SDK.EnvironmentVariableBase {
|
interface EnvironmentVariableInfo extends SDK.EnvironmentVariableBase {
|
||||||
appName?: string | null; // application name
|
fleet?: string | null; // fleet slug
|
||||||
deviceUUID?: string; // device UUID
|
deviceUUID?: string; // device UUID
|
||||||
serviceName?: string; // service name
|
serviceName?: string; // service name
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DeviceServiceEnvironmentVariableInfo
|
interface DeviceServiceEnvironmentVariableInfo
|
||||||
extends SDK.DeviceServiceEnvironmentVariable {
|
extends SDK.DeviceServiceEnvironmentVariable {
|
||||||
appName?: string; // application name
|
fleet?: string; // fleet slug
|
||||||
deviceUUID?: string; // device UUID
|
deviceUUID?: string; // device UUID
|
||||||
serviceName?: string; // service name
|
serviceName?: string; // service name
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ServiceEnvironmentVariableInfo
|
interface ServiceEnvironmentVariableInfo
|
||||||
extends SDK.ServiceEnvironmentVariable {
|
extends SDK.ServiceEnvironmentVariable {
|
||||||
appName?: string; // application name
|
fleet?: string; // fleet slug
|
||||||
deviceUUID?: string; // device UUID
|
deviceUUID?: string; // device UUID
|
||||||
serviceName?: string; // service name
|
serviceName?: string; // service name
|
||||||
}
|
}
|
||||||
@ -96,8 +90,6 @@ export default class EnvsCmd extends Command {
|
|||||||
in case the current user was removed from the fleet by the fleet's owner).
|
in case the current user was removed from the fleet by the fleet's owner).
|
||||||
|
|
||||||
${applicationIdInfo.split('\n').join('\n\t\t')}
|
${applicationIdInfo.split('\n').join('\n\t\t')}
|
||||||
|
|
||||||
${appToFleetOutputMsg.split('\n').join('\n\t\t')}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
public static examples = [
|
public static examples = [
|
||||||
@ -115,57 +107,35 @@ export default class EnvsCmd extends Command {
|
|||||||
public static usage = 'envs';
|
public static usage = 'envs';
|
||||||
|
|
||||||
public static flags: flags.Input<FlagsDef> = {
|
public static flags: flags.Input<FlagsDef> = {
|
||||||
...(isV13()
|
fleet: { ...cf.fleet, exclusive: ['device'] },
|
||||||
? {}
|
|
||||||
: {
|
|
||||||
all: flags.boolean({
|
|
||||||
default: false,
|
|
||||||
description: 'No-op since balena CLI v12.0.0.',
|
|
||||||
hidden: true,
|
|
||||||
}),
|
|
||||||
application: {
|
|
||||||
exclusive: ['device', 'fleet', 'v13'],
|
|
||||||
...cf.application,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
fleet: { exclusive: ['device', 'application'], ...cf.fleet },
|
|
||||||
config: flags.boolean({
|
config: flags.boolean({
|
||||||
default: false,
|
default: false,
|
||||||
char: 'c',
|
char: 'c',
|
||||||
description: 'show configuration variables only',
|
description: 'show configuration variables only',
|
||||||
exclusive: ['service'],
|
exclusive: ['service'],
|
||||||
}),
|
}),
|
||||||
device: { exclusive: ['fleet', 'application'], ...cf.device },
|
device: { ...cf.device, exclusive: ['fleet'] },
|
||||||
help: cf.help,
|
help: cf.help,
|
||||||
json: cf.json,
|
...(isV14() ? cf.dataSetOutputFlags : { json: cf.json }),
|
||||||
verbose: cf.verbose,
|
service: { ...cf.service, exclusive: ['config'] },
|
||||||
service: { exclusive: ['config'], ...cf.service },
|
|
||||||
v13: cf.v13,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
protected useAppWord = false;
|
|
||||||
protected hasWarned = false;
|
|
||||||
|
|
||||||
public async run() {
|
public async run() {
|
||||||
const { flags: options } = this.parse<FlagsDef, {}>(EnvsCmd);
|
const { flags: options } = this.parse<FlagsDef, {}>(EnvsCmd);
|
||||||
this.useAppWord = !options.fleet && !options.v13 && !isV13();
|
|
||||||
|
|
||||||
const variables: EnvironmentVariableInfo[] = [];
|
const variables: EnvironmentVariableInfo[] = [];
|
||||||
|
|
||||||
await Command.checkLoggedIn();
|
await Command.checkLoggedIn();
|
||||||
|
|
||||||
if (options.application && !options.json && process.stderr.isTTY) {
|
if (!options.fleet && !options.device) {
|
||||||
this.hasWarned = true;
|
|
||||||
console.error(warnify(appToFleetFlagMsg));
|
|
||||||
}
|
|
||||||
options.application ||= options.fleet;
|
|
||||||
if (!options.application && !options.device) {
|
|
||||||
throw new ExpectedError('Missing --fleet or --device option');
|
throw new ExpectedError('Missing --fleet or --device option');
|
||||||
}
|
}
|
||||||
|
|
||||||
const balena = getBalenaSdk();
|
const balena = getBalenaSdk();
|
||||||
|
|
||||||
let appNameOrSlug = options.application;
|
let fleetSlug: string | undefined = options.fleet
|
||||||
|
? await (await import('../utils/sdk')).getFleetSlug(balena, options.fleet)
|
||||||
|
: undefined;
|
||||||
let fullUUID: string | undefined; // as oppposed to the short, 7-char UUID
|
let fullUUID: string | undefined; // as oppposed to the short, 7-char UUID
|
||||||
|
|
||||||
if (options.device) {
|
if (options.device) {
|
||||||
@ -178,23 +148,23 @@ export default class EnvsCmd extends Command {
|
|||||||
);
|
);
|
||||||
fullUUID = device.uuid;
|
fullUUID = device.uuid;
|
||||||
if (app) {
|
if (app) {
|
||||||
appNameOrSlug = app.slug;
|
fleetSlug = app.slug;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (appNameOrSlug && options.service) {
|
if (fleetSlug && options.service) {
|
||||||
await validateServiceName(balena, options.service, appNameOrSlug);
|
await validateServiceName(balena, options.service, fleetSlug);
|
||||||
}
|
}
|
||||||
variables.push(...(await getAppVars(balena, appNameOrSlug, options)));
|
variables.push(...(await getAppVars(balena, fleetSlug, options)));
|
||||||
if (fullUUID) {
|
if (fullUUID) {
|
||||||
variables.push(
|
variables.push(
|
||||||
...(await getDeviceVars(balena, fullUUID, appNameOrSlug, options)),
|
...(await getDeviceVars(balena, fullUUID, fleetSlug, options)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (!options.json && variables.length === 0) {
|
if (!options.json && variables.length === 0) {
|
||||||
const target =
|
const target =
|
||||||
(options.service ? `service "${options.service}" of ` : '') +
|
(options.service ? `service "${options.service}" of ` : '') +
|
||||||
(options.application
|
(options.fleet
|
||||||
? `fleet "${options.application}"`
|
? `fleet "${options.fleet}"`
|
||||||
: `device "${options.device}"`);
|
: `device "${options.device}"`);
|
||||||
throw new ExpectedError(`No environment variables found for ${target}`);
|
throw new ExpectedError(`No environment variables found for ${target}`);
|
||||||
}
|
}
|
||||||
@ -206,45 +176,67 @@ export default class EnvsCmd extends Command {
|
|||||||
varArray: EnvironmentVariableInfo[],
|
varArray: EnvironmentVariableInfo[],
|
||||||
options: FlagsDef,
|
options: FlagsDef,
|
||||||
) {
|
) {
|
||||||
const fields = ['id', 'name', 'value'];
|
const fields = ['id', 'name', 'value', 'fleet'];
|
||||||
|
|
||||||
// Replace undefined app names with 'N/A' or null
|
// Replace undefined app names with 'N/A' or null
|
||||||
varArray = varArray.map((i: EnvironmentVariableInfo) => {
|
varArray = varArray.map((i: EnvironmentVariableInfo) => {
|
||||||
if (i.appName) {
|
i.fleet ||= options.json ? null : 'N/A';
|
||||||
// use slug in v13, app name in v12 for compatibility
|
|
||||||
i.appName = isV13()
|
|
||||||
? i.appName
|
|
||||||
: i.appName.substring(i.appName.indexOf('/') + 1);
|
|
||||||
} else {
|
|
||||||
i.appName = options.json ? null : 'N/A';
|
|
||||||
}
|
|
||||||
return i;
|
return i;
|
||||||
});
|
});
|
||||||
|
|
||||||
const jName = this.useAppWord ? 'appName' : 'fleetName';
|
if (isV14()) {
|
||||||
const tName = this.useAppWord ? 'APPLICATION' : 'FLEET';
|
const results = [...varArray] as any;
|
||||||
fields.push(options.json ? `appName => ${jName}` : `appName => ${tName}`);
|
|
||||||
if (options.device) {
|
|
||||||
fields.push(options.json ? 'deviceUUID' : 'deviceUUID => DEVICE');
|
|
||||||
}
|
|
||||||
if (!options.config) {
|
|
||||||
fields.push(options.json ? 'serviceName' : 'serviceName => SERVICE');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.json) {
|
// Rename fields
|
||||||
const { pickAndRename } = await import('../utils/helpers');
|
if (options.device) {
|
||||||
const mapped = varArray.map((o) => pickAndRename(o, fields));
|
if (options.json) {
|
||||||
this.log(JSON.stringify(mapped, null, 4));
|
fields.push('deviceUUID');
|
||||||
} else {
|
} else {
|
||||||
if (!this.hasWarned && this.useAppWord && process.stderr.isTTY) {
|
results.forEach((r: any) => {
|
||||||
console.error(warnify(appToFleetOutputMsg));
|
r.device = r.deviceUUID;
|
||||||
|
delete r.deviceUUID;
|
||||||
|
});
|
||||||
|
|
||||||
|
fields.push('device');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!options.config) {
|
||||||
|
if (options.json) {
|
||||||
|
fields.push('serviceName');
|
||||||
|
} else {
|
||||||
|
results.forEach((r: any) => {
|
||||||
|
r.service = r.serviceName;
|
||||||
|
delete r.serviceName;
|
||||||
|
});
|
||||||
|
fields.push('service');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.outputData(results, fields, {
|
||||||
|
...options,
|
||||||
|
sort: options.sort || 'name',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Old output implementation
|
||||||
|
if (options.device) {
|
||||||
|
fields.push(options.json ? 'deviceUUID' : 'deviceUUID => DEVICE');
|
||||||
|
}
|
||||||
|
if (!options.config) {
|
||||||
|
fields.push(options.json ? 'serviceName' : 'serviceName => SERVICE');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.json) {
|
||||||
|
const { pickAndRename } = await import('../utils/helpers');
|
||||||
|
const mapped = varArray.map((o) => pickAndRename(o, fields));
|
||||||
|
this.log(JSON.stringify(mapped, null, 4));
|
||||||
|
} else {
|
||||||
|
this.log(
|
||||||
|
getVisuals().table.horizontal(
|
||||||
|
_.sortBy(varArray, (v: SDK.EnvironmentVariableBase) => v.name),
|
||||||
|
fields,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
this.log(
|
|
||||||
getVisuals().table.horizontal(
|
|
||||||
_.sortBy(varArray, (v: SDK.EnvironmentVariableBase) => v.name),
|
|
||||||
fields,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -252,14 +244,14 @@ export default class EnvsCmd extends Command {
|
|||||||
async function validateServiceName(
|
async function validateServiceName(
|
||||||
sdk: SDK.BalenaSDK,
|
sdk: SDK.BalenaSDK,
|
||||||
serviceName: string,
|
serviceName: string,
|
||||||
appName: string,
|
fleetSlug: string,
|
||||||
) {
|
) {
|
||||||
const services = await sdk.models.service.getAllByApplication(appName, {
|
const services = await sdk.models.service.getAllByApplication(fleetSlug, {
|
||||||
$filter: { service_name: serviceName },
|
$filter: { service_name: serviceName },
|
||||||
});
|
});
|
||||||
if (services.length === 0) {
|
if (services.length === 0) {
|
||||||
throw new ExpectedError(
|
throw new ExpectedError(
|
||||||
`Service "${serviceName}" not found for fleet "${appName}"`,
|
`Service "${serviceName}" not found for fleet "${fleetSlug}"`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -273,17 +265,17 @@ async function validateServiceName(
|
|||||||
*/
|
*/
|
||||||
async function getAppVars(
|
async function getAppVars(
|
||||||
sdk: SDK.BalenaSDK,
|
sdk: SDK.BalenaSDK,
|
||||||
appNameOrSlug: string | undefined,
|
fleetSlug: string | undefined,
|
||||||
options: FlagsDef,
|
options: FlagsDef,
|
||||||
): Promise<EnvironmentVariableInfo[]> {
|
): Promise<EnvironmentVariableInfo[]> {
|
||||||
const appVars: EnvironmentVariableInfo[] = [];
|
const appVars: EnvironmentVariableInfo[] = [];
|
||||||
if (!appNameOrSlug) {
|
if (!fleetSlug) {
|
||||||
return appVars;
|
return appVars;
|
||||||
}
|
}
|
||||||
const vars = await sdk.models.application[
|
const vars = await sdk.models.application[
|
||||||
options.config ? 'configVar' : 'envVar'
|
options.config ? 'configVar' : 'envVar'
|
||||||
].getAllByApplication(appNameOrSlug);
|
].getAllByApplication(fleetSlug);
|
||||||
fillInInfoFields(vars, appNameOrSlug);
|
fillInInfoFields(vars, fleetSlug);
|
||||||
appVars.push(...vars);
|
appVars.push(...vars);
|
||||||
if (!options.config) {
|
if (!options.config) {
|
||||||
const pineOpts: SDK.PineOptions<SDK.ServiceEnvironmentVariable> = {
|
const pineOpts: SDK.PineOptions<SDK.ServiceEnvironmentVariable> = {
|
||||||
@ -299,10 +291,10 @@ async function getAppVars(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
const serviceVars = await sdk.models.service.var.getAllByApplication(
|
const serviceVars = await sdk.models.service.var.getAllByApplication(
|
||||||
appNameOrSlug,
|
fleetSlug,
|
||||||
pineOpts,
|
pineOpts,
|
||||||
);
|
);
|
||||||
fillInInfoFields(serviceVars, appNameOrSlug);
|
fillInInfoFields(serviceVars, fleetSlug);
|
||||||
appVars.push(...serviceVars);
|
appVars.push(...serviceVars);
|
||||||
}
|
}
|
||||||
return appVars;
|
return appVars;
|
||||||
@ -315,7 +307,7 @@ async function getAppVars(
|
|||||||
async function getDeviceVars(
|
async function getDeviceVars(
|
||||||
sdk: SDK.BalenaSDK,
|
sdk: SDK.BalenaSDK,
|
||||||
fullUUID: string,
|
fullUUID: string,
|
||||||
appNameOrSlug: string | undefined,
|
fleetSlug: string | undefined,
|
||||||
options: FlagsDef,
|
options: FlagsDef,
|
||||||
): Promise<EnvironmentVariableInfo[]> {
|
): Promise<EnvironmentVariableInfo[]> {
|
||||||
const printedUUID = options.json ? fullUUID : options.device!;
|
const printedUUID = options.json ? fullUUID : options.device!;
|
||||||
@ -324,7 +316,7 @@ async function getDeviceVars(
|
|||||||
const deviceConfigVars = await sdk.models.device.configVar.getAllByDevice(
|
const deviceConfigVars = await sdk.models.device.configVar.getAllByDevice(
|
||||||
fullUUID,
|
fullUUID,
|
||||||
);
|
);
|
||||||
fillInInfoFields(deviceConfigVars, appNameOrSlug, printedUUID);
|
fillInInfoFields(deviceConfigVars, fleetSlug, printedUUID);
|
||||||
deviceVars.push(...deviceConfigVars);
|
deviceVars.push(...deviceConfigVars);
|
||||||
} else {
|
} else {
|
||||||
const pineOpts: SDK.PineOptions<SDK.DeviceServiceEnvironmentVariable> = {
|
const pineOpts: SDK.PineOptions<SDK.DeviceServiceEnvironmentVariable> = {
|
||||||
@ -345,13 +337,13 @@ async function getDeviceVars(
|
|||||||
fullUUID,
|
fullUUID,
|
||||||
pineOpts,
|
pineOpts,
|
||||||
);
|
);
|
||||||
fillInInfoFields(deviceServiceVars, appNameOrSlug, printedUUID);
|
fillInInfoFields(deviceServiceVars, fleetSlug, printedUUID);
|
||||||
deviceVars.push(...deviceServiceVars);
|
deviceVars.push(...deviceServiceVars);
|
||||||
|
|
||||||
const deviceEnvVars = await sdk.models.device.envVar.getAllByDevice(
|
const deviceEnvVars = await sdk.models.device.envVar.getAllByDevice(
|
||||||
fullUUID,
|
fullUUID,
|
||||||
);
|
);
|
||||||
fillInInfoFields(deviceEnvVars, appNameOrSlug, printedUUID);
|
fillInInfoFields(deviceEnvVars, fleetSlug, printedUUID);
|
||||||
deviceVars.push(...deviceEnvVars);
|
deviceVars.push(...deviceEnvVars);
|
||||||
}
|
}
|
||||||
return deviceVars;
|
return deviceVars;
|
||||||
@ -367,7 +359,7 @@ function fillInInfoFields(
|
|||||||
| EnvironmentVariableInfo[]
|
| EnvironmentVariableInfo[]
|
||||||
| DeviceServiceEnvironmentVariableInfo[]
|
| DeviceServiceEnvironmentVariableInfo[]
|
||||||
| ServiceEnvironmentVariableInfo[],
|
| ServiceEnvironmentVariableInfo[],
|
||||||
appNameOrSlug?: string,
|
fleetSlug?: string,
|
||||||
deviceUUID?: string,
|
deviceUUID?: string,
|
||||||
) {
|
) {
|
||||||
for (const envVar of varArray) {
|
for (const envVar of varArray) {
|
||||||
@ -381,7 +373,7 @@ function fillInInfoFields(
|
|||||||
?.installs__service as SDK.Service[]
|
?.installs__service as SDK.Service[]
|
||||||
)[0]?.service_name;
|
)[0]?.service_name;
|
||||||
}
|
}
|
||||||
envVar.appName = appNameOrSlug;
|
envVar.fleet = fleetSlug;
|
||||||
envVar.serviceName = envVar.serviceName || '*';
|
envVar.serviceName = envVar.serviceName || '*';
|
||||||
envVar.deviceUUID = deviceUUID || '*';
|
envVar.deviceUUID = deviceUUID || '*';
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* @license
|
* @license
|
||||||
* Copyright 2021 Balena Ltd.
|
* Copyright 2016-2021 Balena Ltd.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -15,6 +15,135 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { FleetCreateCmd } from '../app/create';
|
import { flags } from '@oclif/command';
|
||||||
|
import type { Application } from 'balena-sdk';
|
||||||
|
|
||||||
export default FleetCreateCmd;
|
import Command from '../../command';
|
||||||
|
import { ExpectedError } from '../../errors';
|
||||||
|
import * as cf from '../../utils/common-flags';
|
||||||
|
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
||||||
|
|
||||||
|
interface FlagsDef {
|
||||||
|
organization?: string;
|
||||||
|
type?: string; // application device type
|
||||||
|
help: void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ArgsDef {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class FleetCreateCmd extends Command {
|
||||||
|
public static description = stripIndent`
|
||||||
|
Create a fleet.
|
||||||
|
|
||||||
|
Create a new balena fleet.
|
||||||
|
|
||||||
|
You can specify the organization the fleet should belong to using
|
||||||
|
the \`--organization\` option. The organization's handle, not its name,
|
||||||
|
should be provided. Organization handles can be listed with the
|
||||||
|
\`balena orgs\` command.
|
||||||
|
|
||||||
|
The fleet's default device type is specified with the \`--type\` option.
|
||||||
|
The \`balena devices supported\` command can be used to list the available
|
||||||
|
device types.
|
||||||
|
|
||||||
|
Interactive dropdowns will be shown for selection if no device type or
|
||||||
|
organization is specified and there are multiple options to choose from.
|
||||||
|
If there is a single option to choose from, it will be chosen automatically.
|
||||||
|
This interactive behavior can be disabled by explicitly specifying a device
|
||||||
|
type and organization.
|
||||||
|
`;
|
||||||
|
|
||||||
|
public static examples = [
|
||||||
|
'$ balena fleet create MyFleet',
|
||||||
|
'$ balena fleet create MyFleet --organization mmyorg',
|
||||||
|
'$ balena fleet create MyFleet -o myorg --type raspberry-pi',
|
||||||
|
];
|
||||||
|
|
||||||
|
public static args = [
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
description: 'fleet name',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
public static usage = 'fleet create <name>';
|
||||||
|
|
||||||
|
public static flags: flags.Input<FlagsDef> = {
|
||||||
|
organization: flags.string({
|
||||||
|
char: 'o',
|
||||||
|
description: 'handle of the organization the fleet should belong to',
|
||||||
|
}),
|
||||||
|
type: flags.string({
|
||||||
|
char: 't',
|
||||||
|
description:
|
||||||
|
'fleet device type (Check available types with `balena devices supported`)',
|
||||||
|
}),
|
||||||
|
help: cf.help,
|
||||||
|
};
|
||||||
|
|
||||||
|
public static authenticated = true;
|
||||||
|
|
||||||
|
public async run() {
|
||||||
|
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
|
||||||
|
FleetCreateCmd,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Ascertain device type
|
||||||
|
const deviceType =
|
||||||
|
options.type ||
|
||||||
|
(await (await import('../../utils/patterns')).selectDeviceType());
|
||||||
|
|
||||||
|
// Ascertain organization
|
||||||
|
const organization =
|
||||||
|
options.organization?.toLowerCase() || (await this.getOrganization());
|
||||||
|
|
||||||
|
// Create application
|
||||||
|
let application: Application;
|
||||||
|
try {
|
||||||
|
application = await getBalenaSdk().models.application.create({
|
||||||
|
name: params.name,
|
||||||
|
deviceType,
|
||||||
|
organization,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
if ((err.message || '').toLowerCase().includes('unique')) {
|
||||||
|
// BalenaRequestError: Request error: "organization" and "app_name" must be unique.
|
||||||
|
throw new ExpectedError(
|
||||||
|
`Error: fleet "${params.name}" already exists in organization "${organization}".`,
|
||||||
|
);
|
||||||
|
} else if ((err.message || '').toLowerCase().includes('unauthorized')) {
|
||||||
|
// BalenaRequestError: Request error: Unauthorized
|
||||||
|
throw new ExpectedError(
|
||||||
|
`Error: You are not authorized to create fleets in organization "${organization}".`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output
|
||||||
|
console.log(
|
||||||
|
`Fleet created: slug "${application.slug}", device type "${deviceType}"`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getOrganization() {
|
||||||
|
const { getOwnOrganizations } = await import('../../utils/sdk');
|
||||||
|
const organizations = await getOwnOrganizations(getBalenaSdk());
|
||||||
|
|
||||||
|
if (organizations.length === 0) {
|
||||||
|
// User is not a member of any organizations (should not happen).
|
||||||
|
throw new Error('This account is not a member of any organizations');
|
||||||
|
} else if (organizations.length === 1) {
|
||||||
|
// User is a member of only one organization - use this.
|
||||||
|
return organizations[0].handle;
|
||||||
|
} else {
|
||||||
|
// User is a member of multiple organizations -
|
||||||
|
const { selectOrganization } = await import('../../utils/patterns');
|
||||||
|
return selectOrganization(organizations);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* @license
|
* @license
|
||||||
* Copyright 2021 Balena Ltd.
|
* Copyright 2016-2021 Balena Ltd.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -15,6 +15,89 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { FleetCmd } from '../app';
|
import type { flags } from '@oclif/command';
|
||||||
|
import type { Release } from 'balena-sdk';
|
||||||
|
|
||||||
export default FleetCmd;
|
import Command from '../../command';
|
||||||
|
import * as cf from '../../utils/common-flags';
|
||||||
|
import * as ca from '../../utils/common-args';
|
||||||
|
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
|
||||||
|
import { applicationIdInfo } from '../../utils/messages';
|
||||||
|
import { isV14 } from '../../utils/version';
|
||||||
|
import type { DataOutputOptions } from '../../framework';
|
||||||
|
|
||||||
|
interface FlagsDef extends DataOutputOptions {
|
||||||
|
help: void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ArgsDef {
|
||||||
|
fleet: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class FleetCmd extends Command {
|
||||||
|
public static description = stripIndent`
|
||||||
|
Display information about a single fleet.
|
||||||
|
|
||||||
|
Display detailed information about a single fleet.
|
||||||
|
|
||||||
|
${applicationIdInfo.split('\n').join('\n\t\t')}
|
||||||
|
`;
|
||||||
|
public static examples = [
|
||||||
|
'$ balena fleet MyFleet',
|
||||||
|
'$ balena fleet myorg/myfleet',
|
||||||
|
];
|
||||||
|
|
||||||
|
public static args = [ca.fleetRequired];
|
||||||
|
|
||||||
|
public static usage = 'fleet <fleet>';
|
||||||
|
|
||||||
|
public static flags: flags.Input<FlagsDef> = {
|
||||||
|
help: cf.help,
|
||||||
|
...(isV14() ? cf.dataOutputFlags : {}),
|
||||||
|
};
|
||||||
|
|
||||||
|
public static authenticated = true;
|
||||||
|
public static primary = true;
|
||||||
|
|
||||||
|
public async run() {
|
||||||
|
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
|
||||||
|
FleetCmd,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { getApplication } = await import('../../utils/sdk');
|
||||||
|
|
||||||
|
const application = (await getApplication(getBalenaSdk(), params.fleet, {
|
||||||
|
$expand: {
|
||||||
|
is_for__device_type: { $select: 'slug' },
|
||||||
|
should_be_running__release: { $select: 'commit' },
|
||||||
|
},
|
||||||
|
})) as ApplicationWithDeviceType & {
|
||||||
|
should_be_running__release: [Release?];
|
||||||
|
// For display purposes:
|
||||||
|
device_type: string;
|
||||||
|
commit?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
application.device_type = application.is_for__device_type[0].slug;
|
||||||
|
application.commit = application.should_be_running__release[0]?.commit;
|
||||||
|
|
||||||
|
if (isV14()) {
|
||||||
|
await this.outputData(
|
||||||
|
application,
|
||||||
|
['app_name', 'id', 'device_type', 'slug', 'commit'],
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Emulate table.vertical title output, but avoid uppercasing and inserting spaces
|
||||||
|
console.log(`== ${application.slug}`);
|
||||||
|
console.log(
|
||||||
|
getVisuals().table.vertical(application, [
|
||||||
|
'id',
|
||||||
|
'device_type',
|
||||||
|
'slug',
|
||||||
|
'commit',
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* @license
|
* @license
|
||||||
* Copyright 2021 Balena Ltd.
|
* Copyright 2016-2020 Balena Ltd.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -15,6 +15,67 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { FleetPurgeCmd } from '../app/purge';
|
import type { flags } from '@oclif/command';
|
||||||
|
|
||||||
export default FleetPurgeCmd;
|
import Command from '../../command';
|
||||||
|
import * as cf from '../../utils/common-flags';
|
||||||
|
import * as ca from '../../utils/common-args';
|
||||||
|
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
||||||
|
import { applicationIdInfo } from '../../utils/messages';
|
||||||
|
|
||||||
|
interface FlagsDef {
|
||||||
|
help: void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ArgsDef {
|
||||||
|
fleet: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class FleetPurgeCmd extends Command {
|
||||||
|
public static description = stripIndent`
|
||||||
|
Purge data from a fleet.
|
||||||
|
|
||||||
|
Purge data from all devices belonging to a fleet.
|
||||||
|
This will clear the fleet's '/data' directory.
|
||||||
|
|
||||||
|
${applicationIdInfo.split('\n').join('\n\t\t')}
|
||||||
|
`;
|
||||||
|
|
||||||
|
public static examples = [
|
||||||
|
'$ balena fleet purge MyFleet',
|
||||||
|
'$ balena fleet purge myorg/myfleet',
|
||||||
|
];
|
||||||
|
|
||||||
|
public static args = [ca.fleetRequired];
|
||||||
|
|
||||||
|
public static usage = 'fleet purge <fleet>';
|
||||||
|
|
||||||
|
public static flags: flags.Input<FlagsDef> = {
|
||||||
|
help: cf.help,
|
||||||
|
};
|
||||||
|
|
||||||
|
public static authenticated = true;
|
||||||
|
|
||||||
|
public async run() {
|
||||||
|
const { args: params } = this.parse<FlagsDef, ArgsDef>(FleetPurgeCmd);
|
||||||
|
|
||||||
|
const { getApplication } = await import('../../utils/sdk');
|
||||||
|
|
||||||
|
const balena = getBalenaSdk();
|
||||||
|
|
||||||
|
// balena.models.application.purge only accepts a numeric id
|
||||||
|
// so we must first fetch the app to get it's id,
|
||||||
|
const application = await getApplication(balena, params.fleet);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await balena.models.application.purge(application.id);
|
||||||
|
} catch (e) {
|
||||||
|
if (e.message.toLowerCase().includes('no online device(s) found')) {
|
||||||
|
// application.purge throws an error if no devices are online
|
||||||
|
// ignore in this case.
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* @license
|
* @license
|
||||||
* Copyright 2021 Balena Ltd.
|
* Copyright 2016-2020 Balena Ltd.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -15,6 +15,135 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { FleetRenameCmd } from '../app/rename';
|
import type { flags } from '@oclif/command';
|
||||||
|
import type { ApplicationType } from 'balena-sdk';
|
||||||
|
|
||||||
export default FleetRenameCmd;
|
import Command from '../../command';
|
||||||
|
import * as cf from '../../utils/common-flags';
|
||||||
|
import * as ca from '../../utils/common-args';
|
||||||
|
import { getBalenaSdk, stripIndent, getCliForm } from '../../utils/lazy';
|
||||||
|
import { applicationIdInfo } from '../../utils/messages';
|
||||||
|
|
||||||
|
interface FlagsDef {
|
||||||
|
help: void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ArgsDef {
|
||||||
|
fleet: string;
|
||||||
|
newName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class FleetRenameCmd extends Command {
|
||||||
|
public static description = stripIndent`
|
||||||
|
Rename a fleet.
|
||||||
|
|
||||||
|
Rename a fleet.
|
||||||
|
|
||||||
|
Note, if the \`newName\` parameter is omitted, it will be
|
||||||
|
prompted for interactively.
|
||||||
|
|
||||||
|
${applicationIdInfo.split('\n').join('\n\t\t')}
|
||||||
|
`;
|
||||||
|
|
||||||
|
public static examples = [
|
||||||
|
'$ balena fleet rename OldName',
|
||||||
|
'$ balena fleet rename OldName NewName',
|
||||||
|
'$ balena fleet rename myorg/oldname NewName',
|
||||||
|
];
|
||||||
|
|
||||||
|
public static args = [
|
||||||
|
ca.fleetRequired,
|
||||||
|
{
|
||||||
|
name: 'newName',
|
||||||
|
description: 'the new name for the fleet',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
public static usage = 'fleet rename <fleet> [newName]';
|
||||||
|
|
||||||
|
public static flags: flags.Input<FlagsDef> = {
|
||||||
|
help: cf.help,
|
||||||
|
};
|
||||||
|
|
||||||
|
public static authenticated = true;
|
||||||
|
|
||||||
|
public async run() {
|
||||||
|
const { args: params } = this.parse<FlagsDef, ArgsDef>(FleetRenameCmd);
|
||||||
|
|
||||||
|
const { validateApplicationName } = await import('../../utils/validation');
|
||||||
|
const { ExpectedError } = await import('../../errors');
|
||||||
|
|
||||||
|
const balena = getBalenaSdk();
|
||||||
|
|
||||||
|
// Disambiguate target application (if params.params is a number, it could either be an ID or a numerical name)
|
||||||
|
const { getApplication } = await import('../../utils/sdk');
|
||||||
|
const application = await getApplication(balena, params.fleet, {
|
||||||
|
$expand: {
|
||||||
|
application_type: {
|
||||||
|
$select: ['is_legacy'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check app exists
|
||||||
|
if (!application) {
|
||||||
|
throw new ExpectedError(`Error: fleet ${params.fleet} not found.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check app supports renaming
|
||||||
|
const appType = (application.application_type as ApplicationType[])?.[0];
|
||||||
|
if (appType.is_legacy) {
|
||||||
|
throw new ExpectedError(
|
||||||
|
`Fleet ${params.fleet} is of 'legacy' type, and cannot be renamed.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ascertain new name
|
||||||
|
const newName =
|
||||||
|
params.newName ||
|
||||||
|
(await getCliForm().ask({
|
||||||
|
message: 'Please enter the new name for this fleet:',
|
||||||
|
type: 'input',
|
||||||
|
validate: validateApplicationName,
|
||||||
|
})) ||
|
||||||
|
'';
|
||||||
|
|
||||||
|
// Check they haven't used slug in new name
|
||||||
|
if (newName.includes('/')) {
|
||||||
|
throw new ExpectedError(
|
||||||
|
`New fleet name cannot include '/', please check that you are not specifying fleet slug.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rename
|
||||||
|
try {
|
||||||
|
await balena.models.application.rename(application.id, newName);
|
||||||
|
} catch (e) {
|
||||||
|
// BalenaRequestError: Request error: "organization" and "app_name" must be unique.
|
||||||
|
if ((e.message || '').toLowerCase().includes('unique')) {
|
||||||
|
throw new ExpectedError(`Error: fleet ${newName} already exists.`);
|
||||||
|
}
|
||||||
|
// BalenaRequestError: Request error: App name may only contain [a-zA-Z0-9_-].
|
||||||
|
if ((e.message || '').toLowerCase().includes('name may only contain')) {
|
||||||
|
throw new ExpectedError(
|
||||||
|
`Error: new fleet name may only include characters [a-zA-Z0-9_-].`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get application again, to be sure of results
|
||||||
|
const renamedApplication = await balena.models.application.get(
|
||||||
|
application.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Output result
|
||||||
|
console.log(`Fleet renamed`);
|
||||||
|
console.log('From:');
|
||||||
|
console.log(`\tname: ${application.app_name}`);
|
||||||
|
console.log(`\tslug: ${application.slug}`);
|
||||||
|
console.log('To:');
|
||||||
|
console.log(`\tname: ${renamedApplication.app_name}`);
|
||||||
|
console.log(`\tslug: ${renamedApplication.slug}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* @license
|
* @license
|
||||||
* Copyright 2021 Balena Ltd.
|
* Copyright 2016-2020 Balena Ltd.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -15,6 +15,56 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { FleetRestartCmd } from '../app/restart';
|
import type { flags } from '@oclif/command';
|
||||||
|
|
||||||
export default FleetRestartCmd;
|
import Command from '../../command';
|
||||||
|
import * as cf from '../../utils/common-flags';
|
||||||
|
import * as ca from '../../utils/common-args';
|
||||||
|
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
||||||
|
import { applicationIdInfo } from '../../utils/messages';
|
||||||
|
|
||||||
|
interface FlagsDef {
|
||||||
|
help: void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ArgsDef {
|
||||||
|
fleet: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class FleetRestartCmd extends Command {
|
||||||
|
public static description = stripIndent`
|
||||||
|
Restart a fleet.
|
||||||
|
|
||||||
|
Restart all devices belonging to a fleet.
|
||||||
|
|
||||||
|
${applicationIdInfo.split('\n').join('\n\t\t')}
|
||||||
|
`;
|
||||||
|
|
||||||
|
public static examples = [
|
||||||
|
'$ balena fleet restart MyFleet',
|
||||||
|
'$ balena fleet restart myorg/myfleet',
|
||||||
|
];
|
||||||
|
|
||||||
|
public static args = [ca.fleetRequired];
|
||||||
|
|
||||||
|
public static usage = 'fleet restart <fleet>';
|
||||||
|
|
||||||
|
public static flags: flags.Input<FlagsDef> = {
|
||||||
|
help: cf.help,
|
||||||
|
};
|
||||||
|
|
||||||
|
public static authenticated = true;
|
||||||
|
|
||||||
|
public async run() {
|
||||||
|
const { args: params } = this.parse<FlagsDef, ArgsDef>(FleetRestartCmd);
|
||||||
|
|
||||||
|
const { getApplication } = await import('../../utils/sdk');
|
||||||
|
|
||||||
|
const balena = getBalenaSdk();
|
||||||
|
|
||||||
|
// Disambiguate application (if is a number, it could either be an ID or a numerical name)
|
||||||
|
const application = await getApplication(balena, params.fleet);
|
||||||
|
|
||||||
|
await balena.models.application.restart(application.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* @license
|
* @license
|
||||||
* Copyright 2021 Balena Ltd.
|
* Copyright 2016-2020 Balena Ltd.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -15,6 +15,70 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { FleetRmCmd } from '../app/rm';
|
import type { flags } from '@oclif/command';
|
||||||
|
|
||||||
export default FleetRmCmd;
|
import Command from '../../command';
|
||||||
|
import * as cf from '../../utils/common-flags';
|
||||||
|
import * as ca from '../../utils/common-args';
|
||||||
|
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
||||||
|
import { applicationIdInfo } from '../../utils/messages';
|
||||||
|
|
||||||
|
interface FlagsDef {
|
||||||
|
yes: boolean;
|
||||||
|
help: void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ArgsDef {
|
||||||
|
fleet: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class FleetRmCmd extends Command {
|
||||||
|
public static description = stripIndent`
|
||||||
|
Remove a fleet.
|
||||||
|
|
||||||
|
Permanently remove a fleet.
|
||||||
|
|
||||||
|
The --yes option may be used to avoid interactive confirmation.
|
||||||
|
|
||||||
|
${applicationIdInfo.split('\n').join('\n\t\t')}
|
||||||
|
`;
|
||||||
|
|
||||||
|
public static examples = [
|
||||||
|
'$ balena fleet rm MyFleet',
|
||||||
|
'$ balena fleet rm MyFleet --yes',
|
||||||
|
'$ balena fleet rm myorg/myfleet',
|
||||||
|
];
|
||||||
|
|
||||||
|
public static args = [ca.fleetRequired];
|
||||||
|
|
||||||
|
public static usage = 'fleet rm <fleet>';
|
||||||
|
|
||||||
|
public static flags: flags.Input<FlagsDef> = {
|
||||||
|
yes: cf.yes,
|
||||||
|
help: cf.help,
|
||||||
|
};
|
||||||
|
|
||||||
|
public static authenticated = true;
|
||||||
|
|
||||||
|
public async run() {
|
||||||
|
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
|
||||||
|
FleetRmCmd,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { confirm } = await import('../../utils/patterns');
|
||||||
|
const { getApplication } = await import('../../utils/sdk');
|
||||||
|
const balena = getBalenaSdk();
|
||||||
|
|
||||||
|
// Confirm
|
||||||
|
await confirm(
|
||||||
|
options.yes ?? false,
|
||||||
|
`Are you sure you want to delete fleet ${params.fleet}?`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Disambiguate application (if is a number, it could either be an ID or a numerical name)
|
||||||
|
const application = await getApplication(balena, params.fleet);
|
||||||
|
|
||||||
|
// Remove
|
||||||
|
await balena.models.application.remove(application.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -15,6 +15,94 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { FleetsCmd } from './apps';
|
import { flags } from '@oclif/command';
|
||||||
|
|
||||||
export default FleetsCmd;
|
import Command from '../command';
|
||||||
|
import * as cf from '../utils/common-flags';
|
||||||
|
import { getBalenaSdk, getVisuals, stripIndent } from '../utils/lazy';
|
||||||
|
import { isV14 } from '../utils/version';
|
||||||
|
import type { DataSetOutputOptions } from '../framework';
|
||||||
|
|
||||||
|
interface ExtendedApplication extends ApplicationWithDeviceType {
|
||||||
|
device_count: number;
|
||||||
|
online_devices: number;
|
||||||
|
device_type?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FlagsDef extends DataSetOutputOptions {
|
||||||
|
help: void;
|
||||||
|
verbose?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class FleetsCmd extends Command {
|
||||||
|
public static description = stripIndent`
|
||||||
|
List all fleets.
|
||||||
|
|
||||||
|
List all your balena fleets.
|
||||||
|
|
||||||
|
For detailed information on a particular fleet, use
|
||||||
|
\`balena fleet <fleet>\`
|
||||||
|
`;
|
||||||
|
|
||||||
|
public static examples = ['$ balena fleets'];
|
||||||
|
|
||||||
|
public static usage = 'fleets';
|
||||||
|
|
||||||
|
public static flags: flags.Input<FlagsDef> = {
|
||||||
|
...(isV14() ? cf.dataSetOutputFlags : {}),
|
||||||
|
help: cf.help,
|
||||||
|
};
|
||||||
|
|
||||||
|
public static authenticated = true;
|
||||||
|
public static primary = true;
|
||||||
|
|
||||||
|
public async run() {
|
||||||
|
const { flags: options } = this.parse<FlagsDef, {}>(FleetsCmd);
|
||||||
|
|
||||||
|
const balena = getBalenaSdk();
|
||||||
|
|
||||||
|
// Get applications
|
||||||
|
const applications =
|
||||||
|
(await balena.models.application.getAllDirectlyAccessible({
|
||||||
|
$select: ['id', 'app_name', 'slug'],
|
||||||
|
$expand: {
|
||||||
|
is_for__device_type: { $select: 'slug' },
|
||||||
|
owns__device: { $select: 'is_online' },
|
||||||
|
},
|
||||||
|
})) as ExtendedApplication[];
|
||||||
|
|
||||||
|
// Add extended properties
|
||||||
|
applications.forEach((application) => {
|
||||||
|
application.device_count = application.owns__device?.length ?? 0;
|
||||||
|
application.online_devices =
|
||||||
|
application.owns__device?.filter((d) => d.is_online).length || 0;
|
||||||
|
application.device_type = application.is_for__device_type[0].slug;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isV14()) {
|
||||||
|
await this.outputData(
|
||||||
|
applications,
|
||||||
|
[
|
||||||
|
'id',
|
||||||
|
'app_name',
|
||||||
|
'slug',
|
||||||
|
'device_type',
|
||||||
|
'device_count',
|
||||||
|
'online_devices',
|
||||||
|
],
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
getVisuals().table.horizontal(applications, [
|
||||||
|
'id',
|
||||||
|
'app_name => NAME',
|
||||||
|
'slug',
|
||||||
|
'device_type',
|
||||||
|
'online_devices',
|
||||||
|
'device_count',
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -21,10 +21,8 @@ import * as cf from '../utils/common-flags';
|
|||||||
import { getBalenaSdk, stripIndent } from '../utils/lazy';
|
import { getBalenaSdk, stripIndent } from '../utils/lazy';
|
||||||
import { applicationIdInfo } from '../utils/messages';
|
import { applicationIdInfo } from '../utils/messages';
|
||||||
import { parseAsLocalHostnameOrIp } from '../utils/validation';
|
import { parseAsLocalHostnameOrIp } from '../utils/validation';
|
||||||
import { isV13 } from '../utils/version';
|
|
||||||
|
|
||||||
interface FlagsDef {
|
interface FlagsDef {
|
||||||
application?: string;
|
|
||||||
fleet?: string;
|
fleet?: string;
|
||||||
pollInterval?: number;
|
pollInterval?: number;
|
||||||
help?: void;
|
help?: void;
|
||||||
@ -77,7 +75,6 @@ export default class JoinCmd extends Command {
|
|||||||
public static usage = 'join [deviceIpOrHostname]';
|
public static usage = 'join [deviceIpOrHostname]';
|
||||||
|
|
||||||
public static flags: flags.Input<FlagsDef> = {
|
public static flags: flags.Input<FlagsDef> = {
|
||||||
...(isV13() ? {} : { application: cf.application }),
|
|
||||||
fleet: cf.fleet,
|
fleet: cf.fleet,
|
||||||
pollInterval: flags.integer({
|
pollInterval: flags.integer({
|
||||||
description: 'the interval in minutes to check for updates',
|
description: 'the interval in minutes to check for updates',
|
||||||
@ -101,7 +98,7 @@ export default class JoinCmd extends Command {
|
|||||||
logger,
|
logger,
|
||||||
sdk,
|
sdk,
|
||||||
params.deviceIpOrHostname,
|
params.deviceIpOrHostname,
|
||||||
options.application || options.fleet,
|
options.fleet,
|
||||||
options.pollInterval,
|
options.pollInterval,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* @license
|
* @license
|
||||||
* Copyright 2016-2020 Balena Ltd.
|
* Copyright 2016 Balena Ltd.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -20,10 +20,13 @@ import Command from '../../command';
|
|||||||
import * as cf from '../../utils/common-flags';
|
import * as cf from '../../utils/common-flags';
|
||||||
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
|
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
|
||||||
import { parseAsInteger } from '../../utils/validation';
|
import { parseAsInteger } from '../../utils/validation';
|
||||||
|
import type { DataOutputOptions } from '../../framework';
|
||||||
|
|
||||||
|
import { isV14 } from '../../utils/version';
|
||||||
|
|
||||||
type IArg<T> = import('@oclif/parser').args.IArg<T>;
|
type IArg<T> = import('@oclif/parser').args.IArg<T>;
|
||||||
|
|
||||||
interface FlagsDef {
|
interface FlagsDef extends DataOutputOptions {
|
||||||
help: void;
|
help: void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,27 +55,52 @@ export default class KeyCmd extends Command {
|
|||||||
public static usage = 'key <id>';
|
public static usage = 'key <id>';
|
||||||
|
|
||||||
public static flags: flags.Input<FlagsDef> = {
|
public static flags: flags.Input<FlagsDef> = {
|
||||||
|
...(isV14() ? cf.dataOutputFlags : {}),
|
||||||
help: cf.help,
|
help: cf.help,
|
||||||
};
|
};
|
||||||
|
|
||||||
public static authenticated = true;
|
public static authenticated = true;
|
||||||
|
|
||||||
public async run() {
|
public async run() {
|
||||||
const { args: params } = this.parse<{}, ArgsDef>(KeyCmd);
|
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
|
||||||
|
KeyCmd,
|
||||||
|
);
|
||||||
|
|
||||||
const key = await getBalenaSdk().models.key.get(params.id);
|
const key = await getBalenaSdk().models.key.get(params.id);
|
||||||
|
|
||||||
// Use 'name' instead of 'title' to match dashboard.
|
if (isV14()) {
|
||||||
const displayKey = {
|
// Use 'name' instead of 'title' to match dashboard.
|
||||||
id: key.id,
|
const displayKey = {
|
||||||
name: key.title,
|
id: key.id,
|
||||||
};
|
name: key.title,
|
||||||
|
public_key: key.public_key,
|
||||||
|
};
|
||||||
|
|
||||||
console.log(getVisuals().table.vertical(displayKey, ['id', 'name']));
|
if (!options.json) {
|
||||||
|
// Id is redundant, since user must have provided it in command call
|
||||||
|
this.printTitle(displayKey.name);
|
||||||
|
this.outputMessage(displayKey.public_key);
|
||||||
|
} else {
|
||||||
|
await this.outputData(
|
||||||
|
displayKey,
|
||||||
|
['id', 'name', 'public_key'],
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Old output implementation
|
||||||
|
// Use 'name' instead of 'title' to match dashboard.
|
||||||
|
const displayKey = {
|
||||||
|
id: key.id,
|
||||||
|
name: key.title,
|
||||||
|
};
|
||||||
|
|
||||||
// Since the public key string is long, it might
|
console.log(getVisuals().table.vertical(displayKey, ['id', 'name']));
|
||||||
// wrap to lines below, causing the table layout to break.
|
|
||||||
// See https://github.com/balena-io/balena-cli/issues/151
|
// Since the public key string is long, it might
|
||||||
console.log('\n' + key.public_key);
|
// wrap to lines below, causing the table layout to break.
|
||||||
|
// See https://github.com/balena-io/balena-cli/issues/151
|
||||||
|
console.log('\n' + key.public_key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* @license
|
* @license
|
||||||
* Copyright 2016-2020 Balena Ltd.
|
* Copyright 2016-2022 Balena Ltd.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -19,8 +19,11 @@ import { flags } from '@oclif/command';
|
|||||||
import Command from '../command';
|
import Command from '../command';
|
||||||
import * as cf from '../utils/common-flags';
|
import * as cf from '../utils/common-flags';
|
||||||
import { getBalenaSdk, getVisuals, stripIndent } from '../utils/lazy';
|
import { getBalenaSdk, getVisuals, stripIndent } from '../utils/lazy';
|
||||||
|
import type { DataSetOutputOptions } from '../framework';
|
||||||
|
|
||||||
interface FlagsDef {
|
import { isV14 } from '../utils/version';
|
||||||
|
|
||||||
|
interface FlagsDef extends DataSetOutputOptions {
|
||||||
help: void;
|
help: void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,13 +38,14 @@ export default class KeysCmd extends Command {
|
|||||||
public static usage = 'keys';
|
public static usage = 'keys';
|
||||||
|
|
||||||
public static flags: flags.Input<FlagsDef> = {
|
public static flags: flags.Input<FlagsDef> = {
|
||||||
|
...(isV14() ? cf.dataSetOutputFlags : {}),
|
||||||
help: cf.help,
|
help: cf.help,
|
||||||
};
|
};
|
||||||
|
|
||||||
public static authenticated = true;
|
public static authenticated = true;
|
||||||
|
|
||||||
public async run() {
|
public async run() {
|
||||||
this.parse<FlagsDef, {}>(KeysCmd);
|
const { flags: options } = this.parse<FlagsDef, {}>(KeysCmd);
|
||||||
|
|
||||||
const keys = await getBalenaSdk().models.key.getAll();
|
const keys = await getBalenaSdk().models.key.getAll();
|
||||||
|
|
||||||
@ -50,6 +54,12 @@ export default class KeysCmd extends Command {
|
|||||||
return { id: k.id, name: k.title };
|
return { id: k.id, name: k.title };
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(getVisuals().table.horizontal(displayKeys, ['id', 'name']));
|
// Display
|
||||||
|
if (isV14()) {
|
||||||
|
await this.outputData(displayKeys, ['id', 'name'], options);
|
||||||
|
} else {
|
||||||
|
// Old output implementation
|
||||||
|
console.log(getVisuals().table.horizontal(displayKeys, ['id', 'name']));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -113,6 +113,12 @@ export default class LocalConfigureCmd extends Command {
|
|||||||
},
|
},
|
||||||
domain: [['config_json', 'hostname']],
|
domain: [['config_json', 'hostname']],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
template: {
|
||||||
|
developmentMode: '{{developmentMode}}',
|
||||||
|
},
|
||||||
|
domain: [['config_json', 'developmentMode']],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
template: {
|
template: {
|
||||||
wifi: {
|
wifi: {
|
||||||
@ -163,6 +169,13 @@ export default class LocalConfigureCmd extends Command {
|
|||||||
name: 'networkKey',
|
name: 'networkKey',
|
||||||
default: data.networkKey,
|
default: data.networkKey,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
message:
|
||||||
|
'Enable development mode? (Open ports and root access - Not for production!)',
|
||||||
|
type: 'confirm',
|
||||||
|
name: 'developmentMode',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
message: 'Do you want to set advanced settings?',
|
message: 'Do you want to set advanced settings?',
|
||||||
type: 'confirm',
|
type: 'confirm',
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* @license
|
* @license
|
||||||
* Copyright 2016-2020 Balena Ltd.
|
* Copyright 2016-2022 Balena Ltd.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -19,8 +19,11 @@ import { flags } from '@oclif/command';
|
|||||||
import Command from '../command';
|
import Command from '../command';
|
||||||
import * as cf from '../utils/common-flags';
|
import * as cf from '../utils/common-flags';
|
||||||
import { getBalenaSdk, getVisuals, stripIndent } from '../utils/lazy';
|
import { getBalenaSdk, getVisuals, stripIndent } from '../utils/lazy';
|
||||||
|
import type { DataSetOutputOptions } from '../framework';
|
||||||
|
|
||||||
interface FlagsDef {
|
import { isV14 } from '../utils/version';
|
||||||
|
|
||||||
|
interface FlagsDef extends DataSetOutputOptions {
|
||||||
help: void;
|
help: void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,12 +39,13 @@ export default class OrgsCmd extends Command {
|
|||||||
|
|
||||||
public static flags: flags.Input<FlagsDef> = {
|
public static flags: flags.Input<FlagsDef> = {
|
||||||
help: cf.help,
|
help: cf.help,
|
||||||
|
...(isV14() ? cf.dataSetOutputFlags : {}),
|
||||||
};
|
};
|
||||||
|
|
||||||
public static authenticated = true;
|
public static authenticated = true;
|
||||||
|
|
||||||
public async run() {
|
public async run() {
|
||||||
this.parse<FlagsDef, {}>(OrgsCmd);
|
const { flags: options } = this.parse<FlagsDef, {}>(OrgsCmd);
|
||||||
|
|
||||||
const { getOwnOrganizations } = await import('../utils/sdk');
|
const { getOwnOrganizations } = await import('../utils/sdk');
|
||||||
|
|
||||||
@ -49,8 +53,13 @@ export default class OrgsCmd extends Command {
|
|||||||
const organizations = await getOwnOrganizations(getBalenaSdk());
|
const organizations = await getOwnOrganizations(getBalenaSdk());
|
||||||
|
|
||||||
// Display
|
// Display
|
||||||
console.log(
|
if (isV14()) {
|
||||||
getVisuals().table.horizontal(organizations, ['name', 'handle']),
|
await this.outputData(organizations, ['name', 'handle'], options);
|
||||||
);
|
} else {
|
||||||
|
// Old output implementation
|
||||||
|
console.log(
|
||||||
|
getVisuals().table.horizontal(organizations, ['name', 'handle']),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,10 +35,10 @@ interface ArgsDef {
|
|||||||
|
|
||||||
export default class OsBuildConfigCmd extends Command {
|
export default class OsBuildConfigCmd extends Command {
|
||||||
public static description = stripIndent`
|
public static description = stripIndent`
|
||||||
Build an OS config and save it to a JSON file.
|
Prepare a configuration file for use by the 'os configure' command.
|
||||||
|
|
||||||
Interactively generate an OS config once, so that the generated config
|
Interactively generate a configuration file that can then be used as
|
||||||
file can be used in \`balena os configure\`, skipping the interactive part.
|
non-interactive input by the 'balena os configure' command.
|
||||||
`;
|
`;
|
||||||
|
|
||||||
public static examples = [
|
public static examples = [
|
||||||
|
@ -23,27 +23,20 @@ import Command from '../../command';
|
|||||||
import { ExpectedError } from '../../errors';
|
import { ExpectedError } from '../../errors';
|
||||||
import * as cf from '../../utils/common-flags';
|
import * as cf from '../../utils/common-flags';
|
||||||
import { getBalenaSdk, stripIndent, getCliForm } from '../../utils/lazy';
|
import { getBalenaSdk, stripIndent, getCliForm } from '../../utils/lazy';
|
||||||
import {
|
import { applicationIdInfo, devModeInfo } from '../../utils/messages';
|
||||||
applicationIdInfo,
|
|
||||||
appToFleetFlagMsg,
|
|
||||||
warnify,
|
|
||||||
} from '../../utils/messages';
|
|
||||||
import { isV13 } from '../../utils/version';
|
|
||||||
|
|
||||||
const CONNECTIONS_FOLDER = '/system-connections';
|
const CONNECTIONS_FOLDER = '/system-connections';
|
||||||
|
|
||||||
interface FlagsDef {
|
interface FlagsDef {
|
||||||
advanced?: boolean;
|
advanced?: boolean;
|
||||||
application?: string;
|
|
||||||
app?: string;
|
|
||||||
fleet?: string;
|
fleet?: string;
|
||||||
config?: string;
|
config?: string;
|
||||||
'config-app-update-poll-interval'?: number;
|
'config-app-update-poll-interval'?: number;
|
||||||
'config-network'?: string;
|
'config-network'?: string;
|
||||||
'config-wifi-key'?: string;
|
'config-wifi-key'?: string;
|
||||||
'config-wifi-ssid'?: string;
|
'config-wifi-ssid'?: string;
|
||||||
|
dev?: boolean; // balenaOS development variant
|
||||||
device?: string; // device UUID
|
device?: string; // device UUID
|
||||||
'device-api-key'?: string;
|
|
||||||
'device-type'?: string;
|
'device-type'?: string;
|
||||||
help?: void;
|
help?: void;
|
||||||
version?: string;
|
version?: string;
|
||||||
@ -58,6 +51,7 @@ interface ArgsDef {
|
|||||||
|
|
||||||
interface Answers {
|
interface Answers {
|
||||||
appUpdatePollInterval: number; // in minutes
|
appUpdatePollInterval: number; // in minutes
|
||||||
|
developmentMode?: boolean; // balenaOS development variant
|
||||||
deviceType: string; // e.g. "raspberrypi3"
|
deviceType: string; // e.g. "raspberrypi3"
|
||||||
network: 'ethernet' | 'wifi';
|
network: 'ethernet' | 'wifi';
|
||||||
version: string; // e.g. "2.32.0+rev1"
|
version: string; // e.g. "2.32.0+rev1"
|
||||||
@ -66,10 +60,6 @@ interface Answers {
|
|||||||
provisioningKeyName?: string;
|
provisioningKeyName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const deviceApiKeyDeprecationMsg = stripIndent`
|
|
||||||
The --device-api-key option is deprecated and will be removed in a future release.
|
|
||||||
A suitable key is automatically generated or fetched if this option is omitted.`;
|
|
||||||
|
|
||||||
export default class OsConfigureCmd extends Command {
|
export default class OsConfigureCmd extends Command {
|
||||||
public static description = stripIndent`
|
public static description = stripIndent`
|
||||||
Configure a previously downloaded balenaOS image.
|
Configure a previously downloaded balenaOS image.
|
||||||
@ -83,18 +73,18 @@ export default class OsConfigureCmd extends Command {
|
|||||||
2. A given \`config.json\` file specified with the \`--config\` option.
|
2. A given \`config.json\` file specified with the \`--config\` option.
|
||||||
3. User input through interactive prompts (text menus).
|
3. User input through interactive prompts (text menus).
|
||||||
|
|
||||||
The --device-type option may be used to override the fleet's default device
|
The --device-type option is used to override the fleet's default device type,
|
||||||
type, in case of a fleet with mixed device types.
|
in case of a fleet with mixed device types.
|
||||||
|
|
||||||
The --system-connection (-c) option can be used to inject NetworkManager connection
|
${devModeInfo.split('\n').join('\n\t\t')}
|
||||||
|
|
||||||
|
The --system-connection (-c) option is used to inject NetworkManager connection
|
||||||
profiles for additional network interfaces, such as cellular/GSM or additional
|
profiles for additional network interfaces, such as cellular/GSM or additional
|
||||||
WiFi or ethernet connections. This option may be passed multiple times in case there
|
WiFi or ethernet connections. This option may be passed multiple times in case there
|
||||||
are multiple files to inject. See connection profile examples and reference at:
|
are multiple files to inject. See connection profile examples and reference at:
|
||||||
https://www.balena.io/docs/reference/OS/network/2.x/
|
https://www.balena.io/docs/reference/OS/network/2.x/
|
||||||
https://developer.gnome.org/NetworkManager/stable/ref-settings.html
|
https://developer.gnome.org/NetworkManager/stable/ref-settings.html
|
||||||
|
|
||||||
${deviceApiKeyDeprecationMsg.split('\n').join('\n\t\t')}
|
|
||||||
|
|
||||||
${applicationIdInfo.split('\n').join('\n\t\t')}
|
${applicationIdInfo.split('\n').join('\n\t\t')}
|
||||||
|
|
||||||
Note: This command is currently not supported on Windows natively. Windows users
|
Note: This command is currently not supported on Windows natively. Windows users
|
||||||
@ -105,7 +95,6 @@ export default class OsConfigureCmd extends Command {
|
|||||||
|
|
||||||
public static examples = [
|
public static examples = [
|
||||||
'$ balena os configure ../path/rpi3.img --device 7cf02a6',
|
'$ balena os configure ../path/rpi3.img --device 7cf02a6',
|
||||||
'$ balena os configure ../path/rpi3.img --device 7cf02a6 --device-api-key <existingDeviceKey>',
|
|
||||||
'$ balena os configure ../path/rpi3.img --fleet myorg/myfleet',
|
'$ balena os configure ../path/rpi3.img --fleet myorg/myfleet',
|
||||||
'$ balena os configure ../path/rpi3.img --fleet MyFleet --version 2.12.7',
|
'$ balena os configure ../path/rpi3.img --fleet MyFleet --version 2.12.7',
|
||||||
'$ balena os configure ../path/rpi3.img -f MyFinFleet --device-type raspberrypi3',
|
'$ balena os configure ../path/rpi3.img -f MyFinFleet --device-type raspberrypi3',
|
||||||
@ -128,22 +117,7 @@ export default class OsConfigureCmd extends Command {
|
|||||||
description:
|
description:
|
||||||
'ask advanced configuration questions (when in interactive mode)',
|
'ask advanced configuration questions (when in interactive mode)',
|
||||||
}),
|
}),
|
||||||
...(isV13()
|
fleet: { ...cf.fleet, exclusive: ['device'] },
|
||||||
? {}
|
|
||||||
: {
|
|
||||||
application: {
|
|
||||||
...cf.application,
|
|
||||||
exclusive: ['app', 'fleet', 'device'],
|
|
||||||
},
|
|
||||||
app: {
|
|
||||||
...cf.app,
|
|
||||||
exclusive: ['application', 'fleet', 'device'],
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
fleet: {
|
|
||||||
...cf.fleet,
|
|
||||||
exclusive: ['app', 'application', 'device'],
|
|
||||||
},
|
|
||||||
config: flags.string({
|
config: flags.string({
|
||||||
description:
|
description:
|
||||||
'path to a pre-generated config.json file to be injected in the OS image',
|
'path to a pre-generated config.json file to be injected in the OS image',
|
||||||
@ -163,15 +137,8 @@ export default class OsConfigureCmd extends Command {
|
|||||||
'config-wifi-ssid': flags.string({
|
'config-wifi-ssid': flags.string({
|
||||||
description: 'WiFi SSID (network name) (non-interactive configuration)',
|
description: 'WiFi SSID (network name) (non-interactive configuration)',
|
||||||
}),
|
}),
|
||||||
device: {
|
dev: cf.dev,
|
||||||
exclusive: ['app', 'application', 'fleet', 'provisioning-key-name'],
|
device: { ...cf.device, exclusive: ['fleet', 'provisioning-key-name'] },
|
||||||
...cf.device,
|
|
||||||
},
|
|
||||||
'device-api-key': flags.string({
|
|
||||||
char: 'k',
|
|
||||||
description:
|
|
||||||
'custom device API key (DEPRECATED and only supported with balenaOS 2.0.3+)',
|
|
||||||
}),
|
|
||||||
'device-type': flags.string({
|
'device-type': flags.string({
|
||||||
description:
|
description:
|
||||||
'device type slug (e.g. "raspberrypi3") to override the fleet device type',
|
'device type slug (e.g. "raspberrypi3") to override the fleet device type',
|
||||||
@ -203,10 +170,6 @@ export default class OsConfigureCmd extends Command {
|
|||||||
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
|
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
|
||||||
OsConfigureCmd,
|
OsConfigureCmd,
|
||||||
);
|
);
|
||||||
if ((options.application || options.app) && process.stderr.isTTY) {
|
|
||||||
console.error(warnify(appToFleetFlagMsg));
|
|
||||||
}
|
|
||||||
options.application ||= options.app || options.fleet;
|
|
||||||
|
|
||||||
await validateOptions(options);
|
await validateOptions(options);
|
||||||
|
|
||||||
@ -233,7 +196,7 @@ export default class OsConfigureCmd extends Command {
|
|||||||
};
|
};
|
||||||
deviceTypeSlug = device.is_of__device_type[0].slug;
|
deviceTypeSlug = device.is_of__device_type[0].slug;
|
||||||
} else {
|
} else {
|
||||||
app = (await getApplication(balena, options.application!, {
|
app = (await getApplication(balena, options.fleet!, {
|
||||||
$expand: {
|
$expand: {
|
||||||
is_for__device_type: { $select: 'slug' },
|
is_for__device_type: { $select: 'slug' },
|
||||||
},
|
},
|
||||||
@ -254,27 +217,28 @@ export default class OsConfigureCmd extends Command {
|
|||||||
configJson = JSON.parse(rawConfig);
|
configJson = JSON.parse(rawConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const osVersion =
|
||||||
|
options.version ||
|
||||||
|
(await getOsVersionFromImage(params.image, deviceTypeManifest, devInit));
|
||||||
|
|
||||||
|
const { validateDevOptionAndWarn } = await import('../../utils/config');
|
||||||
|
await validateDevOptionAndWarn(options.dev, osVersion);
|
||||||
|
|
||||||
const answers: Answers = await askQuestionsForDeviceType(
|
const answers: Answers = await askQuestionsForDeviceType(
|
||||||
deviceTypeManifest,
|
deviceTypeManifest,
|
||||||
options,
|
options,
|
||||||
configJson,
|
configJson,
|
||||||
);
|
);
|
||||||
if (options.application) {
|
if (options.fleet) {
|
||||||
answers.deviceType = deviceTypeSlug;
|
answers.deviceType = deviceTypeSlug;
|
||||||
}
|
}
|
||||||
answers.version =
|
answers.version = osVersion;
|
||||||
options.version ||
|
answers.developmentMode = options.dev;
|
||||||
(await getOsVersionFromImage(params.image, deviceTypeManifest, devInit));
|
|
||||||
|
|
||||||
answers.provisioningKeyName = options['provisioning-key-name'];
|
answers.provisioningKeyName = options['provisioning-key-name'];
|
||||||
|
|
||||||
if (_.isEmpty(configJson)) {
|
if (_.isEmpty(configJson)) {
|
||||||
if (device) {
|
if (device) {
|
||||||
configJson = await generateDeviceConfig(
|
configJson = await generateDeviceConfig(device, undefined, answers);
|
||||||
device,
|
|
||||||
options['device-api-key'],
|
|
||||||
answers,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
configJson = await generateApplicationConfig(app!, answers);
|
configJson = await generateApplicationConfig(app!, answers);
|
||||||
}
|
}
|
||||||
@ -335,23 +299,16 @@ export default class OsConfigureCmd extends Command {
|
|||||||
async function validateOptions(options: FlagsDef) {
|
async function validateOptions(options: FlagsDef) {
|
||||||
// The 'device' and 'application' options are declared "exclusive" in the oclif
|
// The 'device' and 'application' options are declared "exclusive" in the oclif
|
||||||
// flag definitions above, so oclif will enforce that they are not both used together.
|
// flag definitions above, so oclif will enforce that they are not both used together.
|
||||||
if (!options.device && !options.application) {
|
if (!options.device && !options.fleet) {
|
||||||
throw new ExpectedError(
|
throw new ExpectedError(
|
||||||
"Either the '--device' or the '--fleet' option must be provided",
|
"Either the '--device' or the '--fleet' option must be provided",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (!options.application && options['device-type']) {
|
if (!options.fleet && options['device-type']) {
|
||||||
throw new ExpectedError(
|
throw new ExpectedError(
|
||||||
"The '--device-type' option can only be used in conjunction with the '--fleet' option",
|
"The '--device-type' option can only be used in conjunction with the '--fleet' option",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (options['device-api-key']) {
|
|
||||||
console.error(stripIndent`
|
|
||||||
-------------------------------------------------------------------------------------------
|
|
||||||
Warning: ${deviceApiKeyDeprecationMsg.split('\n').join('\n\t\t\t')}
|
|
||||||
-------------------------------------------------------------------------------------------
|
|
||||||
`);
|
|
||||||
}
|
|
||||||
|
|
||||||
await Command.checkLoggedIn();
|
await Command.checkLoggedIn();
|
||||||
}
|
}
|
||||||
@ -401,7 +358,7 @@ async function checkDeviceTypeCompatibility(
|
|||||||
const helpers = await import('../../utils/helpers');
|
const helpers = await import('../../utils/helpers');
|
||||||
if (!helpers.areDeviceTypesCompatible(appDeviceType, optionDeviceType)) {
|
if (!helpers.areDeviceTypesCompatible(appDeviceType, optionDeviceType)) {
|
||||||
throw new ExpectedError(
|
throw new ExpectedError(
|
||||||
`Device type ${options['device-type']} is incompatible with fleet ${options.application}`,
|
`Device type ${options['device-type']} is incompatible with fleet ${options.fleet}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -426,7 +383,13 @@ async function askQuestionsForDeviceType(
|
|||||||
options: FlagsDef,
|
options: FlagsDef,
|
||||||
configJson?: import('../../utils/config').ImgConfig,
|
configJson?: import('../../utils/config').ImgConfig,
|
||||||
): Promise<Answers> {
|
): Promise<Answers> {
|
||||||
const answerSources: any[] = [camelifyConfigOptions(options)];
|
const answerSources: any[] = [
|
||||||
|
{
|
||||||
|
...camelifyConfigOptions(options),
|
||||||
|
app: options.fleet,
|
||||||
|
application: options.fleet,
|
||||||
|
},
|
||||||
|
];
|
||||||
const defaultAnswers: Partial<Answers> = {};
|
const defaultAnswers: Partial<Answers> = {};
|
||||||
const questions: any = deviceType.options;
|
const questions: any = deviceType.options;
|
||||||
let extraOpts: { override: object } | undefined;
|
let extraOpts: { override: object } | undefined;
|
||||||
|
@ -56,7 +56,7 @@ export default class OsDownloadCmd extends Command {
|
|||||||
'$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version 2.60.1+rev1',
|
'$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version 2.60.1+rev1',
|
||||||
'$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version 2.60.1+rev1.dev',
|
'$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version 2.60.1+rev1.dev',
|
||||||
'$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version ^2.60.0',
|
'$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version ^2.60.0',
|
||||||
'$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version 2021.10.1',
|
'$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version 2021.10.2.prod',
|
||||||
'$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version latest',
|
'$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version latest',
|
||||||
'$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version default',
|
'$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version default',
|
||||||
'$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version menu',
|
'$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version menu',
|
||||||
@ -124,6 +124,22 @@ export default class OsDownloadCmd extends Command {
|
|||||||
await downloadOSImage(params.type, options.output, options.version);
|
await downloadOSImage(params.type, options.output, options.version);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
e.deviceTypeSlug = params.type;
|
e.deviceTypeSlug = params.type;
|
||||||
|
e.message ||= '';
|
||||||
|
if (
|
||||||
|
e.code === 'BalenaRequestError' ||
|
||||||
|
e.message.toLowerCase().includes('no such version')
|
||||||
|
) {
|
||||||
|
const version = options.version || '';
|
||||||
|
if (
|
||||||
|
!version.endsWith('.dev') &&
|
||||||
|
!version.endsWith('.prod') &&
|
||||||
|
/^v?\d+\.\d+\.\d+/.test(version)
|
||||||
|
) {
|
||||||
|
e.message += `
|
||||||
|
** Hint: some OS releases require specifying the full OS version including
|
||||||
|
** the '.prod' or '.dev' suffix, e.g. '--version 2021.10.2.prod'`;
|
||||||
|
}
|
||||||
|
}
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,9 +65,11 @@ export default class OsVersionsCmd extends Command {
|
|||||||
OsVersionsCmd,
|
OsVersionsCmd,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { getFormattedOsVersions } = await import('../../utils/cloud');
|
const { formatOsVersion, getOsVersions } = await import(
|
||||||
const vs = await getFormattedOsVersions(params.type, !!options.esr);
|
'../../utils/cloud'
|
||||||
|
);
|
||||||
|
const vs = await getOsVersions(params.type, !!options.esr);
|
||||||
|
|
||||||
console.log(vs.map((v) => v.formattedVersion).join('\n'));
|
console.log(vs.map((v) => formatOsVersion(v)).join('\n'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,15 +24,10 @@ import {
|
|||||||
getVisuals,
|
getVisuals,
|
||||||
stripIndent,
|
stripIndent,
|
||||||
} from '../utils/lazy';
|
} from '../utils/lazy';
|
||||||
import {
|
import { applicationIdInfo } from '../utils/messages';
|
||||||
applicationIdInfo,
|
|
||||||
appToFleetFlagMsg,
|
|
||||||
warnify,
|
|
||||||
} from '../utils/messages';
|
|
||||||
import type { DockerConnectionCliFlags } from '../utils/docker';
|
import type { DockerConnectionCliFlags } from '../utils/docker';
|
||||||
import { dockerConnectionCliFlags } from '../utils/docker';
|
import { dockerConnectionCliFlags } from '../utils/docker';
|
||||||
import { parseAsInteger } from '../utils/validation';
|
import { parseAsInteger } from '../utils/validation';
|
||||||
import { isV13 } from '../utils/version';
|
|
||||||
|
|
||||||
import { flags } from '@oclif/command';
|
import { flags } from '@oclif/command';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
@ -40,7 +35,6 @@ import type { Application, BalenaSDK, PineExpand, Release } from 'balena-sdk';
|
|||||||
import type { Preloader } from 'balena-preload';
|
import type { Preloader } from 'balena-preload';
|
||||||
|
|
||||||
interface FlagsDef extends DockerConnectionCliFlags {
|
interface FlagsDef extends DockerConnectionCliFlags {
|
||||||
app?: string;
|
|
||||||
fleet?: string;
|
fleet?: string;
|
||||||
commit?: string;
|
commit?: string;
|
||||||
'splash-image'?: string;
|
'splash-image'?: string;
|
||||||
@ -99,7 +93,6 @@ export default class PreloadCmd extends Command {
|
|||||||
public static usage = 'preload <image>';
|
public static usage = 'preload <image>';
|
||||||
|
|
||||||
public static flags: flags.Input<FlagsDef> = {
|
public static flags: flags.Input<FlagsDef> = {
|
||||||
...(isV13() ? {} : { app: cf.application }),
|
|
||||||
fleet: cf.fleet,
|
fleet: cf.fleet,
|
||||||
commit: flags.string({
|
commit: flags.string({
|
||||||
description: `\
|
description: `\
|
||||||
@ -163,11 +156,6 @@ Can be repeated to add multiple certificates.\
|
|||||||
PreloadCmd,
|
PreloadCmd,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (options.app && process.stderr.isTTY) {
|
|
||||||
console.error(warnify(appToFleetFlagMsg));
|
|
||||||
}
|
|
||||||
options.app ||= options.fleet;
|
|
||||||
|
|
||||||
const balena = getBalenaSdk();
|
const balena = getBalenaSdk();
|
||||||
const balenaPreload = await import('balena-preload');
|
const balenaPreload = await import('balena-preload');
|
||||||
const visuals = getVisuals();
|
const visuals = getVisuals();
|
||||||
@ -194,15 +182,9 @@ Can be repeated to add multiple certificates.\
|
|||||||
|
|
||||||
// balena-preload currently does not work with numerical app IDs
|
// balena-preload currently does not work with numerical app IDs
|
||||||
// Load app here, and use app slug from hereon
|
// Load app here, and use app slug from hereon
|
||||||
if (options.app && !options.app.includes('/')) {
|
const fleetSlug: string | undefined = options.fleet
|
||||||
// Disambiguate application (if is a number, it could either be an ID or a numerical name)
|
? await (await import('../utils/sdk')).getFleetSlug(balena, options.fleet)
|
||||||
const { getApplication } = await import('../utils/sdk');
|
: undefined;
|
||||||
const application = await getApplication(balena, options.app);
|
|
||||||
if (!application) {
|
|
||||||
throw new ExpectedError(`Fleet not found: ${options.app}`);
|
|
||||||
}
|
|
||||||
options.app = application.slug;
|
|
||||||
}
|
|
||||||
|
|
||||||
const progressBars: {
|
const progressBars: {
|
||||||
[key: string]: ReturnType<typeof getVisuals>['Progress'];
|
[key: string]: ReturnType<typeof getVisuals>['Progress'];
|
||||||
@ -238,15 +220,12 @@ Can be repeated to add multiple certificates.\
|
|||||||
? 'latest'
|
? 'latest'
|
||||||
: options.commit;
|
: options.commit;
|
||||||
const image = params.image;
|
const image = params.image;
|
||||||
const appId = options.app;
|
|
||||||
|
|
||||||
const splashImage = options['splash-image'];
|
const splashImage = options['splash-image'];
|
||||||
const additionalSpace = options['additional-space'];
|
const additionalSpace = options['additional-space'];
|
||||||
|
|
||||||
const dontCheckArch = options['dont-check-arch'] || false;
|
const dontCheckArch = options['dont-check-arch'] || false;
|
||||||
const pinDevice = options['pin-device-to-release'] || false;
|
const pinDevice = options['pin-device-to-release'] || false;
|
||||||
|
|
||||||
if (dontCheckArch && !appId) {
|
if (dontCheckArch && !fleetSlug) {
|
||||||
throw new ExpectedError(
|
throw new ExpectedError(
|
||||||
'You need to specify a fleet if you disable the architecture check.',
|
'You need to specify a fleet if you disable the architecture check.',
|
||||||
);
|
);
|
||||||
@ -265,7 +244,7 @@ Can be repeated to add multiple certificates.\
|
|||||||
const preloader = new balenaPreload.Preloader(
|
const preloader = new balenaPreload.Preloader(
|
||||||
null,
|
null,
|
||||||
docker,
|
docker,
|
||||||
appId,
|
fleetSlug,
|
||||||
commit,
|
commit,
|
||||||
image,
|
image,
|
||||||
splashImage,
|
splashImage,
|
||||||
@ -309,7 +288,7 @@ Can be repeated to add multiple certificates.\
|
|||||||
preloader.on('error', reject);
|
preloader.on('error', reject);
|
||||||
resolve(
|
resolve(
|
||||||
this.prepareAndPreload(preloader, balena, {
|
this.prepareAndPreload(preloader, balena, {
|
||||||
appId,
|
appId: fleetSlug,
|
||||||
commit,
|
commit,
|
||||||
pinDevice,
|
pinDevice,
|
||||||
}),
|
}),
|
||||||
@ -364,8 +343,8 @@ Can be repeated to add multiple certificates.\
|
|||||||
} catch {
|
} catch {
|
||||||
throw new Error(`Device type "${deviceTypeSlug}" not found in API query`);
|
throw new Error(`Device type "${deviceTypeSlug}" not found in API query`);
|
||||||
}
|
}
|
||||||
return (await balena.models.application.getAll({
|
return (await balena.models.application.getAllDirectlyAccessible({
|
||||||
$select: ['id', 'app_name', 'should_track_latest_release'],
|
$select: ['id', 'slug', 'should_track_latest_release'],
|
||||||
$expand: this.applicationExpandOptions,
|
$expand: this.applicationExpandOptions,
|
||||||
$filter: {
|
$filter: {
|
||||||
// get the apps that are of the same arch as the device type of the image
|
// get the apps that are of the same arch as the device type of the image
|
||||||
@ -408,7 +387,7 @@ Can be repeated to add multiple certificates.\
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
$orderby: 'app_name asc',
|
$orderby: 'slug asc',
|
||||||
})) as Array<
|
})) as Array<
|
||||||
ApplicationWithDeviceType & {
|
ApplicationWithDeviceType & {
|
||||||
should_be_running__release: [Release?];
|
should_be_running__release: [Release?];
|
||||||
@ -437,7 +416,7 @@ Can be repeated to add multiple certificates.\
|
|||||||
message: 'Select a fleet',
|
message: 'Select a fleet',
|
||||||
type: 'list',
|
type: 'list',
|
||||||
choices: applications.map((app) => ({
|
choices: applications.map((app) => ({
|
||||||
name: app.app_name,
|
name: app.slug,
|
||||||
value: app,
|
value: app,
|
||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
@ -512,7 +491,7 @@ Would you like to disable automatic updates for this fleet now?\
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAppWithReleases(balenaSdk: BalenaSDK, appId: string | number) {
|
async getAppWithReleases(balenaSdk: BalenaSDK, appId: string) {
|
||||||
const { getApplication } = await import('../utils/sdk');
|
const { getApplication } = await import('../utils/sdk');
|
||||||
|
|
||||||
return (await getApplication(balenaSdk, appId, {
|
return (await getApplication(balenaSdk, appId, {
|
||||||
|
@ -22,7 +22,6 @@ import { getBalenaSdk, stripIndent } from '../utils/lazy';
|
|||||||
import { dockerignoreHelp, registrySecretsHelp } from '../utils/messages';
|
import { dockerignoreHelp, registrySecretsHelp } from '../utils/messages';
|
||||||
import type { BalenaSDK } from 'balena-sdk';
|
import type { BalenaSDK } from 'balena-sdk';
|
||||||
import { ExpectedError, instanceOf } from '../errors';
|
import { ExpectedError, instanceOf } from '../errors';
|
||||||
import { isV13 } from '../utils/version';
|
|
||||||
import { RegistrySecrets } from 'resin-multibuild';
|
import { RegistrySecrets } from 'resin-multibuild';
|
||||||
import { lowercaseIfSlug } from '../utils/normalization';
|
import { lowercaseIfSlug } from '../utils/normalization';
|
||||||
import {
|
import {
|
||||||
@ -47,14 +46,11 @@ interface FlagsDef {
|
|||||||
pull: boolean;
|
pull: boolean;
|
||||||
'noparent-check': boolean;
|
'noparent-check': boolean;
|
||||||
'registry-secrets'?: string;
|
'registry-secrets'?: string;
|
||||||
gitignore?: boolean; // v13: delete this flag
|
|
||||||
nogitignore?: boolean; // v13: delete this flag
|
|
||||||
nolive: boolean;
|
nolive: boolean;
|
||||||
detached: boolean;
|
detached: boolean;
|
||||||
service?: string[];
|
service?: string[];
|
||||||
system: boolean;
|
system: boolean;
|
||||||
env?: string[];
|
env?: string[];
|
||||||
'convert-eol'?: boolean;
|
|
||||||
'noconvert-eol': boolean;
|
'noconvert-eol': boolean;
|
||||||
'multi-dockerignore': boolean;
|
'multi-dockerignore': boolean;
|
||||||
'release-tag'?: string[];
|
'release-tag'?: string[];
|
||||||
@ -218,16 +214,6 @@ export default class PushCmd extends Command {
|
|||||||
`,
|
`,
|
||||||
multiple: true,
|
multiple: true,
|
||||||
}),
|
}),
|
||||||
...(isV13()
|
|
||||||
? {}
|
|
||||||
: {
|
|
||||||
'convert-eol': flags.boolean({
|
|
||||||
description: 'No-op and deprecated since balena CLI v12.0.0',
|
|
||||||
char: 'l',
|
|
||||||
hidden: true,
|
|
||||||
default: false,
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
'noconvert-eol': flags.boolean({
|
'noconvert-eol': flags.boolean({
|
||||||
description: `Don't convert line endings from CRLF (Windows format) to LF (Unix format).`,
|
description: `Don't convert line endings from CRLF (Windows format) to LF (Unix format).`,
|
||||||
default: false,
|
default: false,
|
||||||
@ -237,28 +223,7 @@ export default class PushCmd extends Command {
|
|||||||
'Have each service use its own .dockerignore file. See "balena help push".',
|
'Have each service use its own .dockerignore file. See "balena help push".',
|
||||||
char: 'm',
|
char: 'm',
|
||||||
default: false,
|
default: false,
|
||||||
exclusive: ['gitignore'], // v13: delete this line
|
|
||||||
}),
|
}),
|
||||||
...(isV13()
|
|
||||||
? {}
|
|
||||||
: {
|
|
||||||
gitignore: flags.boolean({
|
|
||||||
description: stripIndent`
|
|
||||||
Consider .gitignore files in addition to the .dockerignore file. This reverts
|
|
||||||
to the CLI v11 behavior/implementation (deprecated) if compatibility is
|
|
||||||
required until your project can be adapted.`,
|
|
||||||
char: 'g',
|
|
||||||
default: false,
|
|
||||||
exclusive: ['multi-dockerignore'],
|
|
||||||
}),
|
|
||||||
nogitignore: flags.boolean({
|
|
||||||
description:
|
|
||||||
'No-op (default behavior) since balena CLI v12.0.0. See "balena help push".',
|
|
||||||
char: 'G',
|
|
||||||
hidden: true,
|
|
||||||
default: false,
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
'release-tag': flags.string({
|
'release-tag': flags.string({
|
||||||
description: stripIndent`
|
description: stripIndent`
|
||||||
Set release tags if the image build is successful (balenaCloud only). Multiple
|
Set release tags if the image build is successful (balenaCloud only). Multiple
|
||||||
@ -378,7 +343,6 @@ export default class PushCmd extends Command {
|
|||||||
source: options.source,
|
source: options.source,
|
||||||
auth: token,
|
auth: token,
|
||||||
baseUrl,
|
baseUrl,
|
||||||
nogitignore: !options.gitignore, // v13: delete this line
|
|
||||||
sdk,
|
sdk,
|
||||||
opts,
|
opts,
|
||||||
};
|
};
|
||||||
@ -422,7 +386,6 @@ export default class PushCmd extends Command {
|
|||||||
multiDockerignore: options['multi-dockerignore'],
|
multiDockerignore: options['multi-dockerignore'],
|
||||||
nocache: options.nocache,
|
nocache: options.nocache,
|
||||||
pull: options.pull,
|
pull: options.pull,
|
||||||
nogitignore: !options.gitignore, // v13: delete this line
|
|
||||||
noParentCheck: options['noparent-check'],
|
noParentCheck: options['noparent-check'],
|
||||||
nolive: options.nolive,
|
nolive: options.nolive,
|
||||||
detached: options.detached,
|
detached: options.detached,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* @license
|
* @license
|
||||||
* Copyright 2016-2020 Balena Ltd.
|
* Copyright 2016 Balena Ltd.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -22,8 +22,11 @@ import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
|
|||||||
import type * as BalenaSdk from 'balena-sdk';
|
import type * as BalenaSdk from 'balena-sdk';
|
||||||
import jsyaml = require('js-yaml');
|
import jsyaml = require('js-yaml');
|
||||||
import { tryAsInteger } from '../../utils/validation';
|
import { tryAsInteger } from '../../utils/validation';
|
||||||
|
import type { DataOutputOptions } from '../../framework';
|
||||||
|
|
||||||
interface FlagsDef {
|
import { isV14 } from '../../utils/version';
|
||||||
|
|
||||||
|
interface FlagsDef extends DataOutputOptions {
|
||||||
help: void;
|
help: void;
|
||||||
composition?: boolean;
|
composition?: boolean;
|
||||||
}
|
}
|
||||||
@ -49,7 +52,9 @@ export default class ReleaseCmd extends Command {
|
|||||||
default: false,
|
default: false,
|
||||||
char: 'c',
|
char: 'c',
|
||||||
description: 'Return the release composition',
|
description: 'Return the release composition',
|
||||||
|
exclusive: ['json', 'fields'],
|
||||||
}),
|
}),
|
||||||
|
...(isV14() ? cf.dataOutputFlags : {}),
|
||||||
};
|
};
|
||||||
|
|
||||||
public static args = [
|
public static args = [
|
||||||
@ -68,29 +73,27 @@ export default class ReleaseCmd extends Command {
|
|||||||
ReleaseCmd,
|
ReleaseCmd,
|
||||||
);
|
);
|
||||||
|
|
||||||
const balena = getBalenaSdk();
|
|
||||||
if (options.composition) {
|
if (options.composition) {
|
||||||
await this.showComposition(params.commitOrId, balena);
|
await this.showComposition(params.commitOrId);
|
||||||
} else {
|
} else {
|
||||||
await this.showReleaseInfo(params.commitOrId, balena);
|
await this.showReleaseInfo(params.commitOrId, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async showComposition(
|
async showComposition(commitOrId: string | number) {
|
||||||
commitOrId: string | number,
|
const release = await getBalenaSdk().models.release.get(commitOrId, {
|
||||||
balena: BalenaSdk.BalenaSDK,
|
|
||||||
) {
|
|
||||||
const release = await balena.models.release.get(commitOrId, {
|
|
||||||
$select: 'composition',
|
$select: 'composition',
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(jsyaml.dump(release.composition));
|
if (isV14()) {
|
||||||
|
this.outputMessage(jsyaml.dump(release.composition));
|
||||||
|
} else {
|
||||||
|
// Old output implementation
|
||||||
|
console.log(jsyaml.dump(release.composition));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async showReleaseInfo(
|
async showReleaseInfo(commitOrId: string | number, options: FlagsDef) {
|
||||||
commitOrId: string | number,
|
|
||||||
balena: BalenaSdk.BalenaSDK,
|
|
||||||
) {
|
|
||||||
const fields: Array<keyof BalenaSdk.Release> = [
|
const fields: Array<keyof BalenaSdk.Release> = [
|
||||||
'id',
|
'id',
|
||||||
'commit',
|
'commit',
|
||||||
@ -103,7 +106,7 @@ export default class ReleaseCmd extends Command {
|
|||||||
'end_timestamp',
|
'end_timestamp',
|
||||||
];
|
];
|
||||||
|
|
||||||
const release = await balena.models.release.get(commitOrId, {
|
const release = await getBalenaSdk().models.release.get(commitOrId, {
|
||||||
$select: fields,
|
$select: fields,
|
||||||
$expand: {
|
$expand: {
|
||||||
release_tag: {
|
release_tag: {
|
||||||
@ -116,13 +119,28 @@ export default class ReleaseCmd extends Command {
|
|||||||
.release_tag!.map((t) => `${t.tag_key}=${t.value}`)
|
.release_tag!.map((t) => `${t.tag_key}=${t.value}`)
|
||||||
.join('\n');
|
.join('\n');
|
||||||
|
|
||||||
const _ = await import('lodash');
|
if (isV14()) {
|
||||||
const values = _.mapValues(
|
await this.outputData(
|
||||||
release,
|
{
|
||||||
(val) => val ?? 'N/a',
|
tags: tagStr,
|
||||||
) as Dictionary<string>;
|
...release,
|
||||||
values['tags'] = tagStr;
|
},
|
||||||
|
fields,
|
||||||
|
{
|
||||||
|
displayNullValuesAs: 'N/a',
|
||||||
|
...options,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Old output implementation
|
||||||
|
const _ = await import('lodash');
|
||||||
|
const values = _.mapValues(
|
||||||
|
release,
|
||||||
|
(val) => val ?? 'N/a',
|
||||||
|
) as Dictionary<string>;
|
||||||
|
values['tags'] = tagStr;
|
||||||
|
|
||||||
console.log(getVisuals().table.vertical(values, [...fields, 'tags']));
|
console.log(getVisuals().table.vertical(values, [...fields, 'tags']));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* @license
|
* @license
|
||||||
* Copyright 2016-2020 Balena Ltd.
|
* Copyright 2016 Balena Ltd.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -21,8 +21,11 @@ import * as cf from '../utils/common-flags';
|
|||||||
import { getBalenaSdk, getVisuals, stripIndent } from '../utils/lazy';
|
import { getBalenaSdk, getVisuals, stripIndent } from '../utils/lazy';
|
||||||
import { applicationNameNote } from '../utils/messages';
|
import { applicationNameNote } from '../utils/messages';
|
||||||
import type * as BalenaSdk from 'balena-sdk';
|
import type * as BalenaSdk from 'balena-sdk';
|
||||||
|
import type { DataSetOutputOptions } from '../framework';
|
||||||
|
|
||||||
interface FlagsDef {
|
import { isV14 } from '../utils/version';
|
||||||
|
|
||||||
|
interface FlagsDef extends DataSetOutputOptions {
|
||||||
help: void;
|
help: void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,13 +46,14 @@ export default class ReleasesCmd extends Command {
|
|||||||
public static usage = 'releases <fleet>';
|
public static usage = 'releases <fleet>';
|
||||||
|
|
||||||
public static flags: flags.Input<FlagsDef> = {
|
public static flags: flags.Input<FlagsDef> = {
|
||||||
|
...(isV14() ? cf.dataOutputFlags : {}),
|
||||||
help: cf.help,
|
help: cf.help,
|
||||||
};
|
};
|
||||||
|
|
||||||
public static args = [
|
public static args = [
|
||||||
{
|
{
|
||||||
name: 'fleet',
|
name: 'fleet',
|
||||||
description: 'fleet name or slug',
|
description: 'fleet name or slug (preferred)',
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@ -57,7 +61,9 @@ export default class ReleasesCmd extends Command {
|
|||||||
public static authenticated = true;
|
public static authenticated = true;
|
||||||
|
|
||||||
public async run() {
|
public async run() {
|
||||||
const { args: params } = this.parse<FlagsDef, ArgsDef>(ReleasesCmd);
|
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
|
||||||
|
ReleasesCmd,
|
||||||
|
);
|
||||||
|
|
||||||
const fields: Array<keyof BalenaSdk.Release> = [
|
const fields: Array<keyof BalenaSdk.Release> = [
|
||||||
'id',
|
'id',
|
||||||
@ -69,18 +75,27 @@ export default class ReleasesCmd extends Command {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const balena = getBalenaSdk();
|
const balena = getBalenaSdk();
|
||||||
|
const { getFleetSlug } = await import('../utils/sdk');
|
||||||
|
|
||||||
const releases = await balena.models.release.getAllByApplication(
|
const releases = await balena.models.release.getAllByApplication(
|
||||||
params.fleet,
|
await getFleetSlug(balena, params.fleet),
|
||||||
{ $select: fields },
|
{ $select: fields },
|
||||||
);
|
);
|
||||||
|
|
||||||
const _ = await import('lodash');
|
if (isV14()) {
|
||||||
console.log(
|
await this.outputData(releases, fields, {
|
||||||
getVisuals().table.horizontal(
|
displayNullValuesAs: 'N/a',
|
||||||
releases.map((rel) => _.mapValues(rel, (val) => val ?? 'N/a')),
|
...options,
|
||||||
fields,
|
});
|
||||||
),
|
} else {
|
||||||
);
|
// Old output implementation
|
||||||
|
const _ = await import('lodash');
|
||||||
|
console.log(
|
||||||
|
getVisuals().table.horizontal(
|
||||||
|
releases.map((rel) => _.mapValues(rel, (val) => val ?? 'N/a')),
|
||||||
|
fields,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* @license
|
* @license
|
||||||
* Copyright 2016-2020 Balena Ltd.
|
* Copyright 2016 Balena Ltd.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -19,8 +19,11 @@ import { flags } from '@oclif/command';
|
|||||||
import Command from '../command';
|
import Command from '../command';
|
||||||
import * as cf from '../utils/common-flags';
|
import * as cf from '../utils/common-flags';
|
||||||
import { getBalenaSdk, stripIndent } from '../utils/lazy';
|
import { getBalenaSdk, stripIndent } from '../utils/lazy';
|
||||||
|
import type { DataOutputOptions } from '../framework';
|
||||||
|
|
||||||
interface FlagsDef {
|
import { isV14 } from '../utils/version';
|
||||||
|
|
||||||
|
interface FlagsDef extends DataOutputOptions {
|
||||||
help: void;
|
help: void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,15 +38,27 @@ export default class SettingsCmd extends Command {
|
|||||||
public static usage = 'settings';
|
public static usage = 'settings';
|
||||||
|
|
||||||
public static flags: flags.Input<FlagsDef> = {
|
public static flags: flags.Input<FlagsDef> = {
|
||||||
|
...(isV14() ? cf.dataOutputFlags : {}),
|
||||||
help: cf.help,
|
help: cf.help,
|
||||||
};
|
};
|
||||||
|
|
||||||
public async run() {
|
public async run() {
|
||||||
this.parse<FlagsDef, {}>(SettingsCmd);
|
const { flags: options } = this.parse<FlagsDef, {}>(SettingsCmd);
|
||||||
|
|
||||||
const settings = await getBalenaSdk().settings.getAll();
|
const settings = await getBalenaSdk().settings.getAll();
|
||||||
|
|
||||||
const prettyjson = await import('prettyjson');
|
if (isV14()) {
|
||||||
console.log(prettyjson.render(settings));
|
// Select all available fields for display
|
||||||
|
const fields = Object.keys(settings);
|
||||||
|
|
||||||
|
await this.outputData(settings, fields, {
|
||||||
|
noCapitalizeKeys: true,
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Old output implementation
|
||||||
|
const prettyjson = await import('prettyjson');
|
||||||
|
console.log(prettyjson.render(settings));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,15 +20,9 @@ import Command from '../command';
|
|||||||
import { ExpectedError } from '../errors';
|
import { ExpectedError } from '../errors';
|
||||||
import * as cf from '../utils/common-flags';
|
import * as cf from '../utils/common-flags';
|
||||||
import { getBalenaSdk, getCliUx, stripIndent } from '../utils/lazy';
|
import { getBalenaSdk, getCliUx, stripIndent } from '../utils/lazy';
|
||||||
import {
|
import { applicationIdInfo } from '../utils/messages';
|
||||||
applicationIdInfo,
|
|
||||||
appToFleetFlagMsg,
|
|
||||||
warnify,
|
|
||||||
} from '../utils/messages';
|
|
||||||
import { isV13 } from '../utils/version';
|
|
||||||
|
|
||||||
interface FlagsDef {
|
interface FlagsDef {
|
||||||
application?: string;
|
|
||||||
fleet?: string;
|
fleet?: string;
|
||||||
device?: string;
|
device?: string;
|
||||||
duration?: string;
|
duration?: string;
|
||||||
@ -77,11 +71,10 @@ export default class SupportCmd extends Command {
|
|||||||
description: 'comma-separated list (no spaces) of device UUIDs',
|
description: 'comma-separated list (no spaces) of device UUIDs',
|
||||||
char: 'd',
|
char: 'd',
|
||||||
}),
|
}),
|
||||||
...(isV13() ? {} : { application: cf.application }),
|
|
||||||
fleet: {
|
fleet: {
|
||||||
...cf.fleet,
|
...cf.fleet,
|
||||||
description:
|
description:
|
||||||
'comma-separated list (no spaces) of fleet names or org/name slugs',
|
'comma-separated list (no spaces) of fleet names or slugs (preferred)',
|
||||||
},
|
},
|
||||||
duration: flags.string({
|
duration: flags.string({
|
||||||
description:
|
description:
|
||||||
@ -98,18 +91,13 @@ export default class SupportCmd extends Command {
|
|||||||
SupportCmd,
|
SupportCmd,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (options.application && process.stderr.isTTY) {
|
|
||||||
console.error(warnify(appToFleetFlagMsg));
|
|
||||||
}
|
|
||||||
options.application ||= options.fleet;
|
|
||||||
|
|
||||||
const balena = getBalenaSdk();
|
const balena = getBalenaSdk();
|
||||||
const ux = getCliUx();
|
const ux = getCliUx();
|
||||||
|
|
||||||
const enabling = params.action === 'enable';
|
const enabling = params.action === 'enable';
|
||||||
|
|
||||||
// Validation
|
// Validation
|
||||||
if (!options.device && !options.application) {
|
if (!options.device && !options.fleet) {
|
||||||
throw new ExpectedError('At least one device or fleet must be specified');
|
throw new ExpectedError('At least one device or fleet must be specified');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,7 +113,7 @@ export default class SupportCmd extends Command {
|
|||||||
const expiryTs = Date.now() + this.parseDuration(duration);
|
const expiryTs = Date.now() + this.parseDuration(duration);
|
||||||
|
|
||||||
const deviceUuids = options.device?.split(',') || [];
|
const deviceUuids = options.device?.split(',') || [];
|
||||||
const appNames = options.application?.split(',') || [];
|
const appNames = options.fleet?.split(',') || [];
|
||||||
|
|
||||||
const enablingMessage = 'Enabling support access for';
|
const enablingMessage = 'Enabling support access for';
|
||||||
const disablingMessage = 'Disabling support access for';
|
const disablingMessage = 'Disabling support access for';
|
||||||
@ -142,14 +130,17 @@ export default class SupportCmd extends Command {
|
|||||||
ux.action.stop();
|
ux.action.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { getFleetSlug } = await import('../utils/sdk');
|
||||||
|
|
||||||
// Process applications
|
// Process applications
|
||||||
for (const appName of appNames) {
|
for (const appName of appNames) {
|
||||||
|
const slug = await getFleetSlug(balena, appName);
|
||||||
if (enabling) {
|
if (enabling) {
|
||||||
ux.action.start(`${enablingMessage} fleet ${appName}`);
|
ux.action.start(`${enablingMessage} fleet ${slug}`);
|
||||||
await balena.models.application.grantSupportAccess(appName, expiryTs);
|
await balena.models.application.grantSupportAccess(slug, expiryTs);
|
||||||
} else if (params.action === 'disable') {
|
} else if (params.action === 'disable') {
|
||||||
ux.action.start(`${disablingMessage} fleet ${appName}`);
|
ux.action.start(`${disablingMessage} fleet ${slug}`);
|
||||||
await balena.models.application.revokeSupportAccess(appName);
|
await balena.models.application.revokeSupportAccess(slug);
|
||||||
}
|
}
|
||||||
ux.action.stop();
|
ux.action.stop();
|
||||||
}
|
}
|
||||||
|
@ -19,16 +19,9 @@ import { flags } from '@oclif/command';
|
|||||||
import Command from '../../command';
|
import Command from '../../command';
|
||||||
import * as cf from '../../utils/common-flags';
|
import * as cf from '../../utils/common-flags';
|
||||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
||||||
import {
|
import { applicationIdInfo } from '../../utils/messages';
|
||||||
applicationIdInfo,
|
|
||||||
appToFleetFlagMsg,
|
|
||||||
warnify,
|
|
||||||
} from '../../utils/messages';
|
|
||||||
import { isV13 } from '../../utils/version';
|
|
||||||
|
|
||||||
interface FlagsDef {
|
interface FlagsDef {
|
||||||
app?: string;
|
|
||||||
application?: string;
|
|
||||||
fleet?: string;
|
fleet?: string;
|
||||||
device?: string;
|
device?: string;
|
||||||
release?: string;
|
release?: string;
|
||||||
@ -67,29 +60,17 @@ export default class TagRmCmd extends Command {
|
|||||||
public static usage = 'tag rm <tagKey>';
|
public static usage = 'tag rm <tagKey>';
|
||||||
|
|
||||||
public static flags: flags.Input<FlagsDef> = {
|
public static flags: flags.Input<FlagsDef> = {
|
||||||
...(isV13()
|
|
||||||
? {}
|
|
||||||
: {
|
|
||||||
application: {
|
|
||||||
...cf.application,
|
|
||||||
exclusive: ['app', 'fleet', 'device', 'release'],
|
|
||||||
},
|
|
||||||
app: {
|
|
||||||
...cf.app,
|
|
||||||
exclusive: ['application', 'fleet', 'device', 'release'],
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
fleet: {
|
fleet: {
|
||||||
...cf.fleet,
|
...cf.fleet,
|
||||||
exclusive: ['app', 'application', 'device', 'release'],
|
exclusive: ['device', 'release'],
|
||||||
},
|
},
|
||||||
device: {
|
device: {
|
||||||
...cf.device,
|
...cf.device,
|
||||||
exclusive: ['app', 'application', 'fleet', 'release'],
|
exclusive: ['fleet', 'release'],
|
||||||
},
|
},
|
||||||
release: {
|
release: {
|
||||||
...cf.release,
|
...cf.release,
|
||||||
exclusive: ['app', 'application', 'fleet', 'device'],
|
exclusive: ['fleet', 'device'],
|
||||||
},
|
},
|
||||||
help: cf.help,
|
help: cf.help,
|
||||||
};
|
};
|
||||||
@ -101,25 +82,20 @@ export default class TagRmCmd extends Command {
|
|||||||
TagRmCmd,
|
TagRmCmd,
|
||||||
);
|
);
|
||||||
|
|
||||||
if ((options.application || options.app) && process.stderr.isTTY) {
|
|
||||||
console.error(warnify(appToFleetFlagMsg));
|
|
||||||
}
|
|
||||||
options.application ||= options.app || options.fleet;
|
|
||||||
|
|
||||||
const balena = getBalenaSdk();
|
const balena = getBalenaSdk();
|
||||||
|
|
||||||
// Check user has specified one of application/device/release
|
// Check user has specified one of application/device/release
|
||||||
if (!options.application && !options.device && !options.release) {
|
if (!options.fleet && !options.device && !options.release) {
|
||||||
const { ExpectedError } = await import('../../errors');
|
const { ExpectedError } = await import('../../errors');
|
||||||
throw new ExpectedError(TagRmCmd.missingResourceMessage);
|
throw new ExpectedError(TagRmCmd.missingResourceMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { tryAsInteger } = await import('../../utils/validation');
|
const { tryAsInteger } = await import('../../utils/validation');
|
||||||
|
|
||||||
if (options.application) {
|
if (options.fleet) {
|
||||||
const { getTypedApplicationIdentifier } = await import('../../utils/sdk');
|
const { getFleetSlug } = await import('../../utils/sdk');
|
||||||
return balena.models.application.tags.remove(
|
return balena.models.application.tags.remove(
|
||||||
await getTypedApplicationIdentifier(balena, options.application),
|
await getFleetSlug(balena, options.fleet),
|
||||||
params.tagKey,
|
params.tagKey,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -19,16 +19,9 @@ import { flags } from '@oclif/command';
|
|||||||
import Command from '../../command';
|
import Command from '../../command';
|
||||||
import * as cf from '../../utils/common-flags';
|
import * as cf from '../../utils/common-flags';
|
||||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
||||||
import {
|
import { applicationIdInfo } from '../../utils/messages';
|
||||||
applicationIdInfo,
|
|
||||||
appToFleetFlagMsg,
|
|
||||||
warnify,
|
|
||||||
} from '../../utils/messages';
|
|
||||||
import { isV13 } from '../../utils/version';
|
|
||||||
|
|
||||||
interface FlagsDef {
|
interface FlagsDef {
|
||||||
app?: string;
|
|
||||||
application?: string;
|
|
||||||
fleet?: string;
|
fleet?: string;
|
||||||
device?: string;
|
device?: string;
|
||||||
release?: string;
|
release?: string;
|
||||||
@ -80,29 +73,17 @@ export default class TagSetCmd extends Command {
|
|||||||
public static usage = 'tag set <tagKey> [value]';
|
public static usage = 'tag set <tagKey> [value]';
|
||||||
|
|
||||||
public static flags: flags.Input<FlagsDef> = {
|
public static flags: flags.Input<FlagsDef> = {
|
||||||
...(isV13()
|
|
||||||
? {}
|
|
||||||
: {
|
|
||||||
application: {
|
|
||||||
...cf.application,
|
|
||||||
exclusive: ['app', 'fleet', 'device', 'release'],
|
|
||||||
},
|
|
||||||
app: {
|
|
||||||
...cf.app,
|
|
||||||
exclusive: ['application', 'fleet', 'device', 'release'],
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
fleet: {
|
fleet: {
|
||||||
...cf.fleet,
|
...cf.fleet,
|
||||||
exclusive: ['app', 'application', 'device', 'release'],
|
exclusive: ['device', 'release'],
|
||||||
},
|
},
|
||||||
device: {
|
device: {
|
||||||
...cf.device,
|
...cf.device,
|
||||||
exclusive: ['app', 'application', 'fleet', 'release'],
|
exclusive: ['fleet', 'release'],
|
||||||
},
|
},
|
||||||
release: {
|
release: {
|
||||||
...cf.release,
|
...cf.release,
|
||||||
exclusive: ['app', 'application', 'fleet', 'device'],
|
exclusive: ['fleet', 'device'],
|
||||||
},
|
},
|
||||||
help: cf.help,
|
help: cf.help,
|
||||||
};
|
};
|
||||||
@ -114,15 +95,10 @@ export default class TagSetCmd extends Command {
|
|||||||
TagSetCmd,
|
TagSetCmd,
|
||||||
);
|
);
|
||||||
|
|
||||||
if ((options.application || options.app) && process.stderr.isTTY) {
|
|
||||||
console.error(warnify(appToFleetFlagMsg));
|
|
||||||
}
|
|
||||||
options.application ||= options.app || options.fleet;
|
|
||||||
|
|
||||||
const balena = getBalenaSdk();
|
const balena = getBalenaSdk();
|
||||||
|
|
||||||
// Check user has specified one of application/device/release
|
// Check user has specified one of application/device/release
|
||||||
if (!options.application && !options.device && !options.release) {
|
if (!options.fleet && !options.device && !options.release) {
|
||||||
const { ExpectedError } = await import('../../errors');
|
const { ExpectedError } = await import('../../errors');
|
||||||
throw new ExpectedError(TagSetCmd.missingResourceMessage);
|
throw new ExpectedError(TagSetCmd.missingResourceMessage);
|
||||||
}
|
}
|
||||||
@ -131,10 +107,10 @@ export default class TagSetCmd extends Command {
|
|||||||
|
|
||||||
const { tryAsInteger } = await import('../../utils/validation');
|
const { tryAsInteger } = await import('../../utils/validation');
|
||||||
|
|
||||||
if (options.application) {
|
if (options.fleet) {
|
||||||
const { getTypedApplicationIdentifier } = await import('../../utils/sdk');
|
const { getFleetSlug } = await import('../../utils/sdk');
|
||||||
return balena.models.application.tags.set(
|
return balena.models.application.tags.set(
|
||||||
await getTypedApplicationIdentifier(balena, options.application),
|
await getFleetSlug(balena, options.fleet),
|
||||||
params.tagKey,
|
params.tagKey,
|
||||||
params.value,
|
params.value,
|
||||||
);
|
);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* @license
|
* @license
|
||||||
* Copyright 2016-2020 Balena Ltd.
|
* Copyright 2016 Balena Ltd.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -20,16 +20,13 @@ import Command from '../command';
|
|||||||
import { ExpectedError } from '../errors';
|
import { ExpectedError } from '../errors';
|
||||||
import * as cf from '../utils/common-flags';
|
import * as cf from '../utils/common-flags';
|
||||||
import { getBalenaSdk, getVisuals, stripIndent } from '../utils/lazy';
|
import { getBalenaSdk, getVisuals, stripIndent } from '../utils/lazy';
|
||||||
import {
|
import { applicationIdInfo } from '../utils/messages';
|
||||||
applicationIdInfo,
|
import type { ApplicationTag, DeviceTag, ReleaseTag } from 'balena-sdk';
|
||||||
appToFleetFlagMsg,
|
import type { DataSetOutputOptions } from '../framework';
|
||||||
warnify,
|
|
||||||
} from '../utils/messages';
|
|
||||||
import { isV13 } from '../utils/version';
|
|
||||||
|
|
||||||
interface FlagsDef {
|
import { isV14 } from '../utils/version';
|
||||||
app?: string;
|
|
||||||
application?: string;
|
interface FlagsDef extends DataSetOutputOptions {
|
||||||
fleet?: string;
|
fleet?: string;
|
||||||
device?: string;
|
device?: string;
|
||||||
release?: string;
|
release?: string;
|
||||||
@ -56,30 +53,19 @@ export default class TagsCmd extends Command {
|
|||||||
public static usage = 'tags';
|
public static usage = 'tags';
|
||||||
|
|
||||||
public static flags: flags.Input<FlagsDef> = {
|
public static flags: flags.Input<FlagsDef> = {
|
||||||
...(isV13()
|
|
||||||
? {}
|
|
||||||
: {
|
|
||||||
application: {
|
|
||||||
...cf.application,
|
|
||||||
exclusive: ['app', 'fleet', 'device', 'release'],
|
|
||||||
},
|
|
||||||
app: {
|
|
||||||
...cf.app,
|
|
||||||
exclusive: ['application', 'fleet', 'device', 'release'],
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
fleet: {
|
fleet: {
|
||||||
...cf.fleet,
|
...cf.fleet,
|
||||||
exclusive: ['app', 'application', 'device', 'release'],
|
exclusive: ['device', 'release'],
|
||||||
},
|
},
|
||||||
device: {
|
device: {
|
||||||
...cf.device,
|
...cf.device,
|
||||||
exclusive: ['app', 'application', 'fleet', 'release'],
|
exclusive: ['fleet', 'release'],
|
||||||
},
|
},
|
||||||
release: {
|
release: {
|
||||||
...cf.release,
|
...cf.release,
|
||||||
exclusive: ['app', 'application', 'fleet', 'device'],
|
exclusive: ['fleet', 'device'],
|
||||||
},
|
},
|
||||||
|
...(isV14() ? cf.dataSetOutputFlags : {}),
|
||||||
help: cf.help,
|
help: cf.help,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -88,26 +74,21 @@ export default class TagsCmd extends Command {
|
|||||||
public async run() {
|
public async run() {
|
||||||
const { flags: options } = this.parse<FlagsDef, {}>(TagsCmd);
|
const { flags: options } = this.parse<FlagsDef, {}>(TagsCmd);
|
||||||
|
|
||||||
if ((options.application || options.app) && process.stderr.isTTY) {
|
|
||||||
console.error(warnify(appToFleetFlagMsg));
|
|
||||||
}
|
|
||||||
options.application ||= options.app || options.fleet;
|
|
||||||
|
|
||||||
const balena = getBalenaSdk();
|
const balena = getBalenaSdk();
|
||||||
|
|
||||||
// Check user has specified one of application/device/release
|
// Check user has specified one of application/device/release
|
||||||
if (!options.application && !options.device && !options.release) {
|
if (!options.fleet && !options.device && !options.release) {
|
||||||
throw new ExpectedError(this.missingResourceMessage);
|
throw new ExpectedError(this.missingResourceMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { tryAsInteger } = await import('../utils/validation');
|
const { tryAsInteger } = await import('../utils/validation');
|
||||||
|
|
||||||
let tags;
|
let tags: ApplicationTag[] | DeviceTag[] | ReleaseTag[] = [];
|
||||||
|
|
||||||
if (options.application) {
|
if (options.fleet) {
|
||||||
const { getTypedApplicationIdentifier } = await import('../utils/sdk');
|
const { getFleetSlug } = await import('../utils/sdk');
|
||||||
tags = await balena.models.application.tags.getAllByApplication(
|
tags = await balena.models.application.tags.getAllByApplication(
|
||||||
await getTypedApplicationIdentifier(balena, options.application),
|
await getFleetSlug(balena, options.fleet),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (options.device) {
|
if (options.device) {
|
||||||
@ -127,11 +108,17 @@ export default class TagsCmd extends Command {
|
|||||||
tags = await balena.models.release.tags.getAllByRelease(releaseParam);
|
tags = await balena.models.release.tags.getAllByRelease(releaseParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tags || tags.length === 0) {
|
if (tags.length === 0 && !options.json) {
|
||||||
|
// TODO: Later change to output message
|
||||||
throw new ExpectedError('No tags found');
|
throw new ExpectedError('No tags found');
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(getVisuals().table.horizontal(tags, ['tag_key', 'value']));
|
if (isV14()) {
|
||||||
|
await this.outputData(tags, ['tag_key', 'value'], options);
|
||||||
|
} else {
|
||||||
|
// Old output implementation
|
||||||
|
console.log(getVisuals().table.horizontal(tags, ['tag_key', 'value']));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected missingResourceMessage = stripIndent`
|
protected missingResourceMessage = stripIndent`
|
||||||
|
@ -27,14 +27,7 @@ import * as fs from 'fs';
|
|||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
// `@types/node` does not know about `options: { bigint?: boolean }`
|
const stat = process.pkg ? fs.statSync : fs.promises.stat;
|
||||||
type statT = (
|
|
||||||
fPath: string,
|
|
||||||
options: { bigint?: boolean },
|
|
||||||
) => fs.Stats | Promise<fs.Stats>;
|
|
||||||
|
|
||||||
// async stat does not work with pkg's internal `/snapshot` filesystem
|
|
||||||
const stat: statT = process.pkg ? fs.statSync : fs.promises.stat;
|
|
||||||
|
|
||||||
let fastBootStarted = false;
|
let fastBootStarted = false;
|
||||||
|
|
||||||
|
@ -1,26 +1,35 @@
|
|||||||
/*
|
/**
|
||||||
Copyright 2020 Balena
|
* @license
|
||||||
|
* Copyright 2020 Balena Ltd.
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
*
|
||||||
you may not use this file except in compliance with the License.
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
You may obtain a copy of the License at
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
Unless required by applicable law or agreed to in writing, software
|
*
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
See the License for the specific language governing permissions and
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
limitations under the License.
|
* See the License for the specific language governing permissions and
|
||||||
*/
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
import { getCliUx, getChalk } from '../utils/lazy';
|
import { getCliUx, getChalk } from '../utils/lazy';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to extend FlagsDef for commands that output single-record data.
|
||||||
|
* Exposed to user in command options.
|
||||||
|
*/
|
||||||
export interface DataOutputOptions {
|
export interface DataOutputOptions {
|
||||||
fields?: string;
|
fields?: string;
|
||||||
json?: boolean;
|
json?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to extend FlagsDef for commands that output multi-record data.
|
||||||
|
* Exposed to user in command options.
|
||||||
|
*/
|
||||||
export interface DataSetOutputOptions extends DataOutputOptions {
|
export interface DataSetOutputOptions extends DataOutputOptions {
|
||||||
filter?: string;
|
filter?: string;
|
||||||
'no-header'?: boolean;
|
'no-header'?: boolean;
|
||||||
@ -28,6 +37,14 @@ export interface DataSetOutputOptions extends DataOutputOptions {
|
|||||||
sort?: string;
|
sort?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Not exposed to user
|
||||||
|
export interface InternalOutputOptions {
|
||||||
|
displayNullValuesAs?: string;
|
||||||
|
hideNullOrUndefinedValues?: boolean;
|
||||||
|
titleField?: string;
|
||||||
|
noCapitalizeKeys?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Output message to STDERR
|
* Output message to STDERR
|
||||||
*/
|
*/
|
||||||
@ -49,7 +66,7 @@ export function outputMessage(msg: string) {
|
|||||||
export async function outputData(
|
export async function outputData(
|
||||||
data: any[] | {},
|
data: any[] | {},
|
||||||
fields: string[],
|
fields: string[],
|
||||||
options: DataOutputOptions | DataSetOutputOptions,
|
options: (DataOutputOptions | DataSetOutputOptions) & InternalOutputOptions,
|
||||||
) {
|
) {
|
||||||
if (Array.isArray(data)) {
|
if (Array.isArray(data)) {
|
||||||
await outputDataSet(data, fields, options as DataSetOutputOptions);
|
await outputDataSet(data, fields, options as DataSetOutputOptions);
|
||||||
@ -68,7 +85,7 @@ export async function outputData(
|
|||||||
async function outputDataSet(
|
async function outputDataSet(
|
||||||
data: any[],
|
data: any[],
|
||||||
fields: string[],
|
fields: string[],
|
||||||
options: DataSetOutputOptions,
|
options: DataSetOutputOptions & InternalOutputOptions,
|
||||||
) {
|
) {
|
||||||
// Oclif expects fields to be specified in the format used in table headers (though lowercase)
|
// Oclif expects fields to be specified in the format used in table headers (though lowercase)
|
||||||
// By replacing underscores with spaces here, we can support both header format and actual field name
|
// By replacing underscores with spaces here, we can support both header format and actual field name
|
||||||
@ -77,6 +94,12 @@ async function outputDataSet(
|
|||||||
options.filter = options.filter?.replace(/_/g, ' ');
|
options.filter = options.filter?.replace(/_/g, ' ');
|
||||||
options.sort = options.sort?.replace(/_/g, ' ');
|
options.sort = options.sort?.replace(/_/g, ' ');
|
||||||
|
|
||||||
|
if (!options.json) {
|
||||||
|
data = data.map((d) => {
|
||||||
|
return processNullValues(d, options);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
getCliUx().table(
|
getCliUx().table(
|
||||||
data,
|
data,
|
||||||
// Convert fields array to column object keys
|
// Convert fields array to column object keys
|
||||||
@ -97,7 +120,7 @@ async function outputDataSet(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Outputs a single data object (like `resin-cli-visuals table.vertical`),
|
* Outputs a single data object (similar to `resin-cli-visuals table.vertical`),
|
||||||
* but supporting a subset of options from `cli-ux table` (--json and --fields)
|
* but supporting a subset of options from `cli-ux table` (--json and --fields)
|
||||||
*
|
*
|
||||||
* @param data Array of data objects to output
|
* @param data Array of data objects to output
|
||||||
@ -107,9 +130,9 @@ async function outputDataSet(
|
|||||||
async function outputDataItem(
|
async function outputDataItem(
|
||||||
data: any,
|
data: any,
|
||||||
fields: string[],
|
fields: string[],
|
||||||
options: DataOutputOptions,
|
options: DataOutputOptions & InternalOutputOptions,
|
||||||
) {
|
) {
|
||||||
const outData: typeof data = {};
|
let outData: typeof data = {};
|
||||||
|
|
||||||
// Convert comma separated list of fields in `options.fields` to array of correct format.
|
// Convert comma separated list of fields in `options.fields` to array of correct format.
|
||||||
// Note, user may have specified the true field name (e.g. `some_field`),
|
// Note, user may have specified the true field name (e.g. `some_field`),
|
||||||
@ -125,30 +148,83 @@ async function outputDataItem(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (
|
||||||
|
(options.displayNullValuesAs || options.hideNullOrUndefinedValues) &&
|
||||||
|
!options.json
|
||||||
|
) {
|
||||||
|
outData = processNullValues(outData, options);
|
||||||
|
}
|
||||||
|
|
||||||
if (options.json) {
|
if (options.json) {
|
||||||
printLine(JSON.stringify(outData, undefined, 2));
|
printLine(JSON.stringify(outData, undefined, 2));
|
||||||
} else {
|
} else {
|
||||||
const chalk = getChalk();
|
|
||||||
const { capitalize } = await import('lodash');
|
|
||||||
|
|
||||||
// Find longest key, so we can align results
|
// Find longest key, so we can align results
|
||||||
const longestKeyLength = getLongestObjectKeyLength(outData);
|
const longestKeyLength = getLongestObjectKeyLength(outData);
|
||||||
|
|
||||||
|
if (options.titleField) {
|
||||||
|
printTitle(data[options.titleField as keyof any[]], options);
|
||||||
|
}
|
||||||
|
|
||||||
// Output one field per line
|
// Output one field per line
|
||||||
for (const [k, v] of Object.entries(outData)) {
|
for (let [k, v] of Object.entries(outData)) {
|
||||||
const shim = ' '.repeat(longestKeyLength - k.length);
|
const shim = ' '.repeat(longestKeyLength - k.length);
|
||||||
const kDisplay = capitalize(k.replace(/_/g, ' '));
|
let kDisplay = k.replace(/_/g, ' ');
|
||||||
printLine(`${chalk.bold(kDisplay) + shim} : ${v}`);
|
|
||||||
|
// Start multiline values on the line below the field name
|
||||||
|
if (typeof v === 'string' && v.includes('\n')) {
|
||||||
|
v = `\n${v}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!options.noCapitalizeKeys) {
|
||||||
|
kDisplay = capitalize(kDisplay);
|
||||||
|
}
|
||||||
|
if (k !== options.titleField) {
|
||||||
|
printLine(` ${bold(kDisplay) + shim} : ${v}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLongestObjectKeyLength(o: any): number {
|
/**
|
||||||
return Object.keys(o).length >= 1
|
* Amend null/undefined values in data as per options:
|
||||||
? Object.keys(o).reduce((a, b) => {
|
* - options.displayNullValuesAs will replace the value with the specified string
|
||||||
return a.length > b.length ? a : b;
|
* - options.hideNullOrUndefinedValues will remove the property from the data
|
||||||
}).length
|
*
|
||||||
: 0;
|
* @param data The data object to process
|
||||||
|
* @param options Output options
|
||||||
|
*
|
||||||
|
* @returns a copy of the data with amended values.
|
||||||
|
*/
|
||||||
|
function processNullValues(data: any, options: InternalOutputOptions) {
|
||||||
|
const dataCopy = { ...data };
|
||||||
|
|
||||||
|
Object.entries(dataCopy).forEach(([k, v]) => {
|
||||||
|
if (v == null) {
|
||||||
|
if (options.displayNullValuesAs) {
|
||||||
|
dataCopy[k] = options.displayNullValuesAs;
|
||||||
|
} else if (options.hideNullOrUndefinedValues) {
|
||||||
|
delete dataCopy[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return dataCopy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print a title with underscore
|
||||||
|
*
|
||||||
|
* @param title The title string to print
|
||||||
|
* @param options Output options
|
||||||
|
*/
|
||||||
|
export function printTitle(
|
||||||
|
title: string,
|
||||||
|
options?: InternalOutputOptions & DataSetOutputOptions,
|
||||||
|
) {
|
||||||
|
if (!options?.['no-header']) {
|
||||||
|
printLine(` ${capitalize(bold(title))}`);
|
||||||
|
printLine(` ${bold('─'.repeat(title.length))}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function printLine(s: any) {
|
function printLine(s: any) {
|
||||||
@ -156,3 +232,15 @@ function printLine(s: any) {
|
|||||||
// but using this one explicitly for ease of testing
|
// but using this one explicitly for ease of testing
|
||||||
process.stdout.write(s + '\n');
|
process.stdout.write(s + '\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function capitalize(s: string) {
|
||||||
|
return `${s[0].toUpperCase()}${s.slice(1)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function bold(s: string) {
|
||||||
|
return getChalk().bold(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLongestObjectKeyLength(o: any): number {
|
||||||
|
return Math.max(0, ...Object.keys(o).map((k) => k.length));
|
||||||
|
}
|
||||||
|
@ -53,6 +53,12 @@ export async function preparseArgs(argv: string[]): Promise<string[]> {
|
|||||||
if (extractBooleanFlag(cmdSlice, '--debug')) {
|
if (extractBooleanFlag(cmdSlice, '--debug')) {
|
||||||
process.env.DEBUG = '1';
|
process.env.DEBUG = '1';
|
||||||
}
|
}
|
||||||
|
// support global --v-next flag
|
||||||
|
if (extractBooleanFlag(cmdSlice, '--v-next')) {
|
||||||
|
const { version } = await import('../package.json');
|
||||||
|
const { inc } = await import('semver');
|
||||||
|
process.env.BALENA_CLI_VERSION_OVERRIDE = inc(version, 'major') || '';
|
||||||
|
}
|
||||||
unsupportedFlag = extractBooleanFlag(cmdSlice, '--unsupported');
|
unsupportedFlag = extractBooleanFlag(cmdSlice, '--unsupported');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,6 +137,13 @@ Please use "balena ${alternative}" instead.`);
|
|||||||
'local scan': [replaced, 'scan', 'v11.0.0'],
|
'local scan': [replaced, 'scan', 'v11.0.0'],
|
||||||
'local ssh': [replaced, 'ssh', 'v11.0.0'],
|
'local ssh': [replaced, 'ssh', 'v11.0.0'],
|
||||||
'local stop': [removed, stopAlternative, 'v11.0.0'],
|
'local stop': [removed, stopAlternative, 'v11.0.0'],
|
||||||
|
app: [replaced, 'fleet', 'v13.0.0'],
|
||||||
|
apps: [replaced, 'fleets', 'v13.0.0'],
|
||||||
|
'app create': [replaced, 'fleet create', 'v13.0.0'],
|
||||||
|
'app purge': [replaced, 'fleet purge', 'v13.0.0'],
|
||||||
|
'app rename': [replaced, 'fleet rename', 'v13.0.0'],
|
||||||
|
'app restart': [replaced, 'fleet restart', 'v13.0.0'],
|
||||||
|
'app rm': [replaced, 'fleet rm', 'v13.0.0'],
|
||||||
};
|
};
|
||||||
let cmd: string | undefined;
|
let cmd: string | undefined;
|
||||||
if (argvSlice.length > 1) {
|
if (argvSlice.length > 1) {
|
||||||
|
@ -212,15 +212,11 @@ async function resolveOSVersion(
|
|||||||
if (['menu', 'menu-esr'].includes(version)) {
|
if (['menu', 'menu-esr'].includes(version)) {
|
||||||
return await selectOSVersionFromMenu(deviceType, version === 'menu-esr');
|
return await selectOSVersionFromMenu(deviceType, version === 'menu-esr');
|
||||||
}
|
}
|
||||||
|
// Note that `version` may also be 'latest', 'recommended', 'default'
|
||||||
if (/^v?\d+\.\d+\.\d+/.test(version)) {
|
if (/^v?\d+\.\d+\.\d+/.test(version)) {
|
||||||
if (version[0] === 'v') {
|
if (version[0] === 'v') {
|
||||||
version = version.slice(1);
|
version = version.slice(1);
|
||||||
}
|
}
|
||||||
// The version must end with either '.dev' or '.prod', as expected
|
|
||||||
// by `balena-image-manager` and the balena SDK.
|
|
||||||
if (!version.endsWith('.dev') && !version.endsWith('.prod')) {
|
|
||||||
version += '.prod';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
@ -229,53 +225,45 @@ async function selectOSVersionFromMenu(
|
|||||||
deviceType: string,
|
deviceType: string,
|
||||||
esr: boolean,
|
esr: boolean,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const vs = await getFormattedOsVersions(deviceType, esr);
|
const vs = await getOsVersions(deviceType, esr);
|
||||||
|
|
||||||
const choices = vs.map((v) => ({
|
const choices = vs.map((v) => ({
|
||||||
value: v.rawVersion,
|
value: v.raw_version,
|
||||||
name: v.formattedVersion,
|
name: formatOsVersion(v),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return getCliForm().ask({
|
return getCliForm().ask({
|
||||||
message: 'Select the OS version:',
|
message: 'Select the OS version:',
|
||||||
type: 'list',
|
type: 'list',
|
||||||
choices,
|
choices,
|
||||||
default: (vs.find((v) => v.isRecommended) ?? vs[0])?.rawVersion,
|
default: vs[0]?.raw_version,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the output of sdk.models.hostapp.getAvailableOsVersions(), filtered
|
* Return the output of sdk.models.os.getAvailableOsVersions(), resolving
|
||||||
* regarding ESR or non-ESR versions, and having the `formattedVersion` field
|
* device type aliases and filtering with regard to ESR versions.
|
||||||
* reformatted for compatibility with the pre-existing output format of the
|
|
||||||
* `os versions` and `os download` commands.
|
|
||||||
*/
|
*/
|
||||||
export async function getFormattedOsVersions(
|
export async function getOsVersions(
|
||||||
deviceType: string,
|
deviceType: string,
|
||||||
esr: boolean,
|
esr: boolean,
|
||||||
): Promise<SDK.OsVersion[]> {
|
): Promise<SDK.OsVersion[]> {
|
||||||
const sdk = getBalenaSdk();
|
const sdk = getBalenaSdk();
|
||||||
let slug = deviceType;
|
let slug = deviceType;
|
||||||
let versionsByDT: SDK.OsVersionsByDeviceType =
|
let versions: SDK.OsVersion[] = await sdk.models.os.getAvailableOsVersions(
|
||||||
await sdk.models.hostapp.getAvailableOsVersions([slug]);
|
slug,
|
||||||
|
);
|
||||||
// if slug is an alias, fetch the real slug
|
// if slug is an alias, fetch the real slug
|
||||||
if (!versionsByDT[slug]?.length) {
|
if (!versions.length) {
|
||||||
// unaliasDeviceType() produces a nice error msg if slug is invalid
|
// unaliasDeviceType() produces a nice error msg if slug is invalid
|
||||||
slug = await unaliasDeviceType(sdk, slug);
|
slug = await unaliasDeviceType(sdk, slug);
|
||||||
if (slug !== deviceType) {
|
if (slug !== deviceType) {
|
||||||
versionsByDT = await sdk.models.hostapp.getAvailableOsVersions([slug]);
|
versions = await sdk.models.os.getAvailableOsVersions(slug);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const versions: SDK.OsVersion[] = (versionsByDT[slug] || [])
|
versions = versions.filter(
|
||||||
.filter((v: SDK.OsVersion) => v.osType === (esr ? 'esr' : 'default'))
|
(v: SDK.OsVersion) => v.osType === (esr ? 'esr' : 'default'),
|
||||||
.map((v: SDK.OsVersion) => {
|
);
|
||||||
const i = v.formattedVersion.indexOf(' ');
|
|
||||||
v.formattedVersion =
|
|
||||||
i < 0
|
|
||||||
? `v${v.rawVersion}`
|
|
||||||
: `v${v.rawVersion}${v.formattedVersion.substring(i)}`;
|
|
||||||
return v;
|
|
||||||
});
|
|
||||||
if (!versions.length) {
|
if (!versions.length) {
|
||||||
const vType = esr ? 'ESR versions' : 'versions';
|
const vType = esr ? 'ESR versions' : 'versions';
|
||||||
throw new ExpectedError(
|
throw new ExpectedError(
|
||||||
@ -284,3 +272,9 @@ export async function getFormattedOsVersions(
|
|||||||
}
|
}
|
||||||
return versions;
|
return versions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function formatOsVersion(osVersion: SDK.OsVersion): string {
|
||||||
|
return osVersion.line
|
||||||
|
? `v${osVersion.raw_version} (${osVersion.line})`
|
||||||
|
: `v${osVersion.raw_version}`;
|
||||||
|
}
|
||||||
|
@ -19,35 +19,13 @@ import { flags } from '@oclif/command';
|
|||||||
import { stripIndent } from './lazy';
|
import { stripIndent } from './lazy';
|
||||||
import { lowercaseIfSlug } from './normalization';
|
import { lowercaseIfSlug } from './normalization';
|
||||||
|
|
||||||
import { isV13 } from './version';
|
import { isV14 } from './version';
|
||||||
import type { IBooleanFlag } from '@oclif/parser/lib/flags';
|
import type { IBooleanFlag } from '@oclif/parser/lib/flags';
|
||||||
import type { DataOutputOptions, DataSetOutputOptions } from '../framework';
|
import type { DataOutputOptions, DataSetOutputOptions } from '../framework';
|
||||||
|
|
||||||
export const v13: IBooleanFlag<boolean> = flags.boolean({
|
|
||||||
description: stripIndent`\
|
|
||||||
enable selected balena CLI v13 pre-release features, like the renaming
|
|
||||||
from "application" to "fleet" in command output`,
|
|
||||||
default: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const application = flags.string({
|
|
||||||
char: 'a',
|
|
||||||
description: 'DEPRECATED alias for -f, --fleet',
|
|
||||||
parse: lowercaseIfSlug,
|
|
||||||
});
|
|
||||||
// TODO: Consider remove second alias 'app' when we can, to simplify.
|
|
||||||
export const app = flags.string({
|
|
||||||
description: 'DEPRECATED alias for -f, --fleet',
|
|
||||||
parse: lowercaseIfSlug,
|
|
||||||
});
|
|
||||||
export const fleet = flags.string({
|
export const fleet = flags.string({
|
||||||
char: 'f',
|
char: 'f',
|
||||||
description: isV13()
|
description: 'fleet name, slug (preferred), or numeric ID (deprecated)',
|
||||||
? 'fleet name, slug (preferred), or numeric ID (deprecated)'
|
|
||||||
: // avoid the '(deprecated)' remark in v12 while cf.application and
|
|
||||||
// cf.app are also described as deprecated, to avoid the impression
|
|
||||||
// that cf.fleet is deprecated as well.
|
|
||||||
'fleet name, slug (preferred), or numeric ID',
|
|
||||||
parse: lowercaseIfSlug,
|
parse: lowercaseIfSlug,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -92,6 +70,11 @@ export const force: IBooleanFlag<boolean> = flags.boolean({
|
|||||||
default: false,
|
default: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const dev: IBooleanFlag<boolean> = flags.boolean({
|
||||||
|
description: 'Configure balenaOS to operate in development mode',
|
||||||
|
default: false,
|
||||||
|
});
|
||||||
|
|
||||||
export const drive = flags.string({
|
export const drive = flags.string({
|
||||||
char: 'd',
|
char: 'd',
|
||||||
description: stripIndent`
|
description: stripIndent`
|
||||||
@ -114,12 +97,18 @@ export const deviceType = flags.string({
|
|||||||
required: true,
|
required: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const deviceTypeIgnored = flags.string({
|
export const deviceTypeIgnored = {
|
||||||
description: 'ignored - no longer required',
|
...(isV14()
|
||||||
char: 't',
|
? {}
|
||||||
required: false,
|
: {
|
||||||
hidden: isV13(),
|
type: flags.string({
|
||||||
});
|
description: 'ignored - no longer required',
|
||||||
|
char: 't',
|
||||||
|
required: false,
|
||||||
|
hidden: true,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
export const json: IBooleanFlag<boolean> = flags.boolean({
|
export const json: IBooleanFlag<boolean> = flags.boolean({
|
||||||
char: 'j',
|
char: 'j',
|
||||||
|
14
lib/utils/compose-types.d.ts
vendored
14
lib/utils/compose-types.d.ts
vendored
@ -15,6 +15,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type { ImageModel, ReleaseModel } from 'balena-release/build/models';
|
||||||
import type { Composition, ImageDescriptor } from 'resin-compose-parse';
|
import type { Composition, ImageDescriptor } from 'resin-compose-parse';
|
||||||
import type { Pack } from 'tar-stream';
|
import type { Pack } from 'tar-stream';
|
||||||
|
|
||||||
@ -51,9 +52,8 @@ export interface ComposeOpts {
|
|||||||
dockerfilePath?: string;
|
dockerfilePath?: string;
|
||||||
inlineLogs?: boolean;
|
inlineLogs?: boolean;
|
||||||
multiDockerignore: boolean;
|
multiDockerignore: boolean;
|
||||||
nogitignore: boolean; // v13: delete this line
|
|
||||||
noParentCheck: boolean;
|
noParentCheck: boolean;
|
||||||
projectName: string;
|
projectName?: string;
|
||||||
projectPath: string;
|
projectPath: string;
|
||||||
isLocal?: boolean;
|
isLocal?: boolean;
|
||||||
}
|
}
|
||||||
@ -63,12 +63,9 @@ export interface ComposeCliFlags {
|
|||||||
dockerfile?: string;
|
dockerfile?: string;
|
||||||
logs: boolean;
|
logs: boolean;
|
||||||
nologs: boolean;
|
nologs: boolean;
|
||||||
gitignore?: boolean; // v13: delete this line
|
|
||||||
nogitignore?: boolean; // v13: delete this line
|
|
||||||
'multi-dockerignore': boolean;
|
'multi-dockerignore': boolean;
|
||||||
'noparent-check': boolean;
|
'noparent-check': boolean;
|
||||||
'registry-secrets'?: RegistrySecrets;
|
'registry-secrets'?: RegistrySecrets;
|
||||||
'convert-eol': boolean;
|
|
||||||
'noconvert-eol': boolean;
|
'noconvert-eol': boolean;
|
||||||
projectName?: string;
|
projectName?: string;
|
||||||
}
|
}
|
||||||
@ -83,7 +80,7 @@ export interface ComposeProject {
|
|||||||
export interface Release {
|
export interface Release {
|
||||||
client: ReturnType<typeof import('balena-release').createClient>;
|
client: ReturnType<typeof import('balena-release').createClient>;
|
||||||
release: Pick<
|
release: Pick<
|
||||||
import('balena-release/build/models').ReleaseModel,
|
ReleaseModel,
|
||||||
| 'id'
|
| 'id'
|
||||||
| 'status'
|
| 'status'
|
||||||
| 'commit'
|
| 'commit'
|
||||||
@ -95,13 +92,14 @@ export interface Release {
|
|||||||
| 'start_timestamp'
|
| 'start_timestamp'
|
||||||
| 'end_timestamp'
|
| 'end_timestamp'
|
||||||
>;
|
>;
|
||||||
serviceImages: Partial<import('balena-release/build/models').ImageModel>;
|
serviceImages: Dictionary<
|
||||||
|
Omit<ImageModel, 'created_at' | 'is_a_build_of__service' | '__metadata'>
|
||||||
|
>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TarDirectoryOptions {
|
interface TarDirectoryOptions {
|
||||||
composition?: Composition;
|
composition?: Composition;
|
||||||
convertEol?: boolean;
|
convertEol?: boolean;
|
||||||
multiDockerignore?: boolean;
|
multiDockerignore?: boolean;
|
||||||
nogitignore: boolean; // v13: delete this line
|
|
||||||
preFinalizeCallback?: (pack: Pack) => void | Promise<void>;
|
preFinalizeCallback?: (pack: Pack) => void | Promise<void>;
|
||||||
}
|
}
|
||||||
|
@ -15,22 +15,32 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type { Renderer } from './compose_ts';
|
||||||
|
import type * as SDK from 'balena-sdk';
|
||||||
|
import type Dockerode = require('dockerode');
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { ExpectedError } from '../errors';
|
import type { Composition, ImageDescriptor } from 'resin-compose-parse';
|
||||||
|
import type {
|
||||||
|
BuiltImage,
|
||||||
|
ComposeOpts,
|
||||||
|
ComposeProject,
|
||||||
|
Release,
|
||||||
|
TaggedImage,
|
||||||
|
} from './compose-types';
|
||||||
import { getChalk } from './lazy';
|
import { getChalk } from './lazy';
|
||||||
import { isV13 } from './version';
|
import Logger = require('./logger');
|
||||||
|
import { ProgressCallback } from 'docker-progress';
|
||||||
|
|
||||||
/**
|
export function generateOpts(options: {
|
||||||
* @returns Promise<{import('./compose-types').ComposeOpts}>
|
source?: string;
|
||||||
*/
|
projectName?: string;
|
||||||
export function generateOpts(options) {
|
nologs: boolean;
|
||||||
const { promises: fs } = require('fs');
|
'noconvert-eol': boolean;
|
||||||
|
dockerfile?: string;
|
||||||
if (!isV13() && options.gitignore && options['multi-dockerignore']) {
|
'multi-dockerignore': boolean;
|
||||||
throw new ExpectedError(
|
'noparent-check': boolean;
|
||||||
'The --gitignore and --multi-dockerignore options cannot be used together',
|
}): Promise<ComposeOpts> {
|
||||||
);
|
const { promises: fs } = require('fs') as typeof import('fs');
|
||||||
}
|
|
||||||
return fs.realpath(options.source || '.').then((projectPath) => ({
|
return fs.realpath(options.source || '.').then((projectPath) => ({
|
||||||
projectName: options.projectName,
|
projectName: options.projectName,
|
||||||
projectPath,
|
projectPath,
|
||||||
@ -38,29 +48,23 @@ export function generateOpts(options) {
|
|||||||
convertEol: !options['noconvert-eol'],
|
convertEol: !options['noconvert-eol'],
|
||||||
dockerfilePath: options.dockerfile,
|
dockerfilePath: options.dockerfile,
|
||||||
multiDockerignore: !!options['multi-dockerignore'],
|
multiDockerignore: !!options['multi-dockerignore'],
|
||||||
nogitignore: !options.gitignore, // v13: delete this line
|
|
||||||
noParentCheck: options['noparent-check'],
|
noParentCheck: options['noparent-check'],
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the given composition and return a structure with info. Input is:
|
/** Parse the given composition and return a structure with info. Input is:
|
||||||
// - composePath: the *absolute* path to the directory containing the compose file
|
* - composePath: the *absolute* path to the directory containing the compose file
|
||||||
// - composeStr: the contents of the compose file, as a string
|
* - composeStr: the contents of the compose file, as a string
|
||||||
/**
|
|
||||||
* @param {string} composePath
|
|
||||||
* @param {string} composeStr
|
|
||||||
* @param {string | undefined} projectName The --projectName flag (build, deploy)
|
|
||||||
* @param {string | undefined} imageTag The --tag flag (build, deploy)
|
|
||||||
* @returns {import('./compose-types').ComposeProject}
|
|
||||||
*/
|
*/
|
||||||
export function createProject(
|
export function createProject(
|
||||||
composePath,
|
composePath: string,
|
||||||
composeStr,
|
composeStr: string,
|
||||||
projectName = '',
|
projectName = '',
|
||||||
imageTag = '',
|
imageTag = '',
|
||||||
) {
|
): ComposeProject {
|
||||||
const yml = require('js-yaml');
|
const yml = require('js-yaml') as typeof import('js-yaml');
|
||||||
const compose = require('resin-compose-parse');
|
const compose =
|
||||||
|
require('resin-compose-parse') as typeof import('resin-compose-parse');
|
||||||
|
|
||||||
// both methods below may throw.
|
// both methods below may throw.
|
||||||
const rawComposition = yml.load(composeStr);
|
const rawComposition = yml.load(composeStr);
|
||||||
@ -76,7 +80,8 @@ export function createProject(
|
|||||||
descr.image.context != null &&
|
descr.image.context != null &&
|
||||||
descr.image.tag == null
|
descr.image.tag == null
|
||||||
) {
|
) {
|
||||||
const { makeImageName } = require('./compose_ts');
|
const { makeImageName } =
|
||||||
|
require('./compose_ts') as typeof import('./compose_ts');
|
||||||
descr.image.tag = makeImageName(projectName, descr.serviceName, imageTag);
|
descr.image.tag = makeImageName(projectName, descr.serviceName, imageTag);
|
||||||
}
|
}
|
||||||
return descr;
|
return descr;
|
||||||
@ -89,116 +94,20 @@ export function createProject(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* This is the CLI v10 / v11 "original" tarDirectory function. It is still
|
|
||||||
* around for the benefit of the `--gitignore` option, but is expected to be
|
|
||||||
* deleted in CLI v13.
|
|
||||||
* @param {string} dir Source directory
|
|
||||||
* @param {import('./compose-types').TarDirectoryOptions} param
|
|
||||||
* @returns {Promise<import('stream').Readable>}
|
|
||||||
*
|
|
||||||
* v13: delete this function
|
|
||||||
*/
|
|
||||||
export async function originalTarDirectory(dir, param) {
|
|
||||||
let {
|
|
||||||
preFinalizeCallback = null,
|
|
||||||
convertEol = false,
|
|
||||||
nogitignore = false,
|
|
||||||
} = param;
|
|
||||||
if (convertEol == null) {
|
|
||||||
convertEol = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Bluebird = require('bluebird');
|
|
||||||
const tar = require('tar-stream');
|
|
||||||
const klaw = require('klaw');
|
|
||||||
const { promises: fs } = require('fs');
|
|
||||||
const streamToPromise = require('stream-to-promise');
|
|
||||||
const { printGitignoreWarn } = require('./compose_ts');
|
|
||||||
const { FileIgnorer, IgnoreFileType } = require('./ignore');
|
|
||||||
const { toPosixPath } = require('resin-multibuild').PathUtils;
|
|
||||||
let readFile;
|
|
||||||
if (process.platform === 'win32') {
|
|
||||||
const { readFileWithEolConversion } = require('./eol-conversion');
|
|
||||||
readFile = (file) => readFileWithEolConversion(file, convertEol);
|
|
||||||
} else {
|
|
||||||
({ readFile } = fs);
|
|
||||||
}
|
|
||||||
|
|
||||||
const getFiles = () =>
|
|
||||||
Bluebird.resolve(streamToPromise(klaw(dir)))
|
|
||||||
// @ts-ignore
|
|
||||||
.filter((item) => !item.stats.isDirectory())
|
|
||||||
// @ts-ignore
|
|
||||||
.map((item) => item.path);
|
|
||||||
|
|
||||||
const ignore = new FileIgnorer(dir);
|
|
||||||
const pack = tar.pack();
|
|
||||||
const ignoreFiles = {};
|
|
||||||
return getFiles()
|
|
||||||
.each(function (file) {
|
|
||||||
const type = ignore.getIgnoreFileType(path.relative(dir, file));
|
|
||||||
if (type != null) {
|
|
||||||
ignoreFiles[type] = ignoreFiles[type] || [];
|
|
||||||
ignoreFiles[type].push(path.resolve(dir, file));
|
|
||||||
return ignore.addIgnoreFile(file, type);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.tap(() => {
|
|
||||||
if (!nogitignore) {
|
|
||||||
printGitignoreWarn(
|
|
||||||
(ignoreFiles[IgnoreFileType.DockerIgnore] || [])[0] || '',
|
|
||||||
ignoreFiles[IgnoreFileType.GitIgnore] || [],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.filter(ignore.filter)
|
|
||||||
.map(function (file) {
|
|
||||||
const relPath = path.relative(path.resolve(dir), file);
|
|
||||||
return Promise.all([relPath, fs.stat(file), readFile(file)]).then(
|
|
||||||
([filename, stats, data]) =>
|
|
||||||
pack.entry(
|
|
||||||
{
|
|
||||||
name: toPosixPath(filename),
|
|
||||||
mtime: stats.mtime,
|
|
||||||
size: stats.size,
|
|
||||||
mode: stats.mode,
|
|
||||||
},
|
|
||||||
data,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.then(() => preFinalizeCallback?.(pack))
|
|
||||||
.then(function () {
|
|
||||||
pack.finalize();
|
|
||||||
return pack;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} apiEndpoint
|
|
||||||
* @param {string} auth
|
|
||||||
* @param {number} userId
|
|
||||||
* @param {number} appId
|
|
||||||
* @param {import('resin-compose-parse').Composition} composition
|
|
||||||
* @param {boolean} draft
|
|
||||||
* @param {string|undefined} semver
|
|
||||||
* @param {string|undefined} contract
|
|
||||||
* @returns {Promise<import('./compose-types').Release>}
|
|
||||||
*/
|
|
||||||
export const createRelease = async function (
|
export const createRelease = async function (
|
||||||
apiEndpoint,
|
apiEndpoint: string,
|
||||||
auth,
|
auth: string,
|
||||||
userId,
|
userId: number,
|
||||||
appId,
|
appId: number,
|
||||||
composition,
|
composition: Composition,
|
||||||
draft,
|
draft: boolean,
|
||||||
semver,
|
semver?: string,
|
||||||
contract,
|
contract?: string,
|
||||||
) {
|
): Promise<Release> {
|
||||||
const _ = require('lodash');
|
const _ = require('lodash') as typeof import('lodash');
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto') as typeof import('crypto');
|
||||||
const releaseMod = require('balena-release');
|
const releaseMod =
|
||||||
|
require('balena-release') as typeof import('balena-release');
|
||||||
|
|
||||||
const client = releaseMod.createClient({ apiEndpoint, auth });
|
const client = releaseMod.createClient({ apiEndpoint, auth });
|
||||||
|
|
||||||
@ -228,24 +137,26 @@ export const createRelease = async function (
|
|||||||
'start_timestamp',
|
'start_timestamp',
|
||||||
'end_timestamp',
|
'end_timestamp',
|
||||||
]),
|
]),
|
||||||
serviceImages: _.mapValues(serviceImages, (serviceImage) =>
|
serviceImages: _.mapValues(
|
||||||
_.omit(serviceImage, [
|
serviceImages,
|
||||||
'created_at',
|
(serviceImage) =>
|
||||||
'is_a_build_of__service',
|
_.omit(serviceImage, [
|
||||||
'__metadata',
|
'created_at',
|
||||||
]),
|
'is_a_build_of__service',
|
||||||
|
'__metadata',
|
||||||
|
]) as Omit<
|
||||||
|
typeof serviceImage,
|
||||||
|
'created_at' | 'is_a_build_of__service' | '__metadata'
|
||||||
|
>,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
export const tagServiceImages = (
|
||||||
*
|
docker: Dockerode,
|
||||||
* @param {import('dockerode')} docker
|
images: BuiltImage[],
|
||||||
* @param {Array<import('./compose-types').BuiltImage>} images
|
serviceImages: Release['serviceImages'],
|
||||||
* @param {Partial<import('balena-release/build/models').ImageModel>} serviceImages
|
): Promise<TaggedImage[]> =>
|
||||||
* @returns {Promise<Array<import('./compose-types').TaggedImage>>}
|
|
||||||
*/
|
|
||||||
export const tagServiceImages = (docker, images, serviceImages) =>
|
|
||||||
Promise.all(
|
Promise.all(
|
||||||
images.map(function (d) {
|
images.map(function (d) {
|
||||||
const serviceImage = serviceImages[d.serviceName];
|
const serviceImage = serviceImages[d.serviceName];
|
||||||
@ -272,25 +183,24 @@ export const tagServiceImages = (docker, images, serviceImages) =>
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
export const getPreviousRepos = (
|
||||||
* @param {*} sdk
|
sdk: SDK.BalenaSDK,
|
||||||
* @param {import('./logger')} logger
|
logger: Logger,
|
||||||
* @param {number} appID
|
appID: number,
|
||||||
* @returns {Promise<string[]>}
|
): Promise<string[]> =>
|
||||||
*/
|
|
||||||
export const getPreviousRepos = (sdk, logger, appID) =>
|
|
||||||
sdk.pine
|
sdk.pine
|
||||||
.get({
|
.get<SDK.Release>({
|
||||||
resource: 'release',
|
resource: 'release',
|
||||||
options: {
|
options: {
|
||||||
|
$select: 'id',
|
||||||
$filter: {
|
$filter: {
|
||||||
belongs_to__application: appID,
|
belongs_to__application: appID,
|
||||||
status: 'success',
|
status: 'success',
|
||||||
},
|
},
|
||||||
$select: ['id'],
|
|
||||||
$expand: {
|
$expand: {
|
||||||
contains__image: {
|
contains__image: {
|
||||||
$expand: 'image',
|
$select: 'image',
|
||||||
|
$expand: { image: { $select: 'is_stored_at__image_location' } },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
$orderby: 'id desc',
|
$orderby: 'id desc',
|
||||||
@ -300,8 +210,11 @@ export const getPreviousRepos = (sdk, logger, appID) =>
|
|||||||
.then(function (release) {
|
.then(function (release) {
|
||||||
// grab all images from the latest release, return all image locations in the registry
|
// grab all images from the latest release, return all image locations in the registry
|
||||||
if (release.length > 0) {
|
if (release.length > 0) {
|
||||||
const images = release[0].contains__image;
|
const images = release[0].contains__image as Array<{
|
||||||
const { getRegistryAndName } = require('resin-multibuild');
|
image: [SDK.Image];
|
||||||
|
}>;
|
||||||
|
const { getRegistryAndName } =
|
||||||
|
require('resin-multibuild') as typeof import('resin-multibuild');
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
images.map(function (d) {
|
images.map(function (d) {
|
||||||
const imageName = d.image[0].is_stored_at__image_location || '';
|
const imageName = d.image[0].is_stored_at__image_location || '';
|
||||||
@ -321,21 +234,13 @@ export const getPreviousRepos = (sdk, logger, appID) =>
|
|||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {*} sdk
|
|
||||||
* @param {string} tokenAuthEndpoint
|
|
||||||
* @param {string} registry
|
|
||||||
* @param {string[]} images
|
|
||||||
* @param {string[]} previousRepos
|
|
||||||
* @returns {Promise<string>}
|
|
||||||
*/
|
|
||||||
export const authorizePush = function (
|
export const authorizePush = function (
|
||||||
sdk,
|
sdk: SDK.BalenaSDK,
|
||||||
tokenAuthEndpoint,
|
tokenAuthEndpoint: string,
|
||||||
registry,
|
registry: string,
|
||||||
images,
|
images: string[],
|
||||||
previousRepos,
|
previousRepos: string[],
|
||||||
) {
|
): Promise<string> {
|
||||||
if (!Array.isArray(images)) {
|
if (!Array.isArray(images)) {
|
||||||
images = [images];
|
images = [images];
|
||||||
}
|
}
|
||||||
@ -356,17 +261,20 @@ export const authorizePush = function (
|
|||||||
|
|
||||||
// utilities
|
// utilities
|
||||||
|
|
||||||
const renderProgressBar = function (percentage, stepCount) {
|
const renderProgressBar = function (percentage: number, stepCount: number) {
|
||||||
const _ = require('lodash');
|
const _ = require('lodash') as typeof import('lodash');
|
||||||
percentage = _.clamp(percentage, 0, 100);
|
percentage = _.clamp(percentage, 0, 100);
|
||||||
const barCount = Math.floor((stepCount * percentage) / 100);
|
const barCount = Math.floor((stepCount * percentage) / 100);
|
||||||
const spaceCount = stepCount - barCount;
|
const spaceCount = stepCount - barCount;
|
||||||
const bar = `[${_.repeat('=', barCount)}>${_.repeat(' ', spaceCount)}]`;
|
const bar = `[${_.repeat('=', barCount)}>${_.repeat(' ', spaceCount)}]`;
|
||||||
return `${bar} ${_.padStart(percentage, 3)}%`;
|
return `${bar} ${_.padStart(`${percentage}`, 3)}%`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const pushProgressRenderer = function (tty, prefix) {
|
export const pushProgressRenderer = function (
|
||||||
const fn = function (e) {
|
tty: ReturnType<typeof import('./tty')>,
|
||||||
|
prefix: string,
|
||||||
|
): ProgressCallback & { end: () => void } {
|
||||||
|
const fn: ProgressCallback & { end: () => void } = function (e) {
|
||||||
const { error, percentage } = e;
|
const { error, percentage } = e;
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
@ -380,14 +288,39 @@ export const pushProgressRenderer = function (tty, prefix) {
|
|||||||
return fn;
|
return fn;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class BuildProgressUI {
|
export class BuildProgressUI implements Renderer {
|
||||||
constructor(tty, descriptors) {
|
public streams;
|
||||||
|
private _prefix;
|
||||||
|
private _prefixWidth;
|
||||||
|
private _tty;
|
||||||
|
private _services;
|
||||||
|
private _startTime: undefined | number;
|
||||||
|
private _ended;
|
||||||
|
private _serviceToDataMap: Dictionary<{
|
||||||
|
status?: string;
|
||||||
|
progress?: number;
|
||||||
|
error?: Error;
|
||||||
|
}> = {};
|
||||||
|
private _cancelled;
|
||||||
|
private _spinner;
|
||||||
|
private _runloop:
|
||||||
|
| undefined
|
||||||
|
| ReturnType<typeof import('./compose_ts').createRunLoop>;
|
||||||
|
|
||||||
|
// these are to handle window wrapping
|
||||||
|
private _maxLineWidth: undefined | number;
|
||||||
|
private _lineWidths: number[] = [];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
tty: ReturnType<typeof import('./tty')>,
|
||||||
|
descriptors: ImageDescriptor[],
|
||||||
|
) {
|
||||||
this._handleEvent = this._handleEvent.bind(this);
|
this._handleEvent = this._handleEvent.bind(this);
|
||||||
this.start = this.start.bind(this);
|
this.start = this.start.bind(this);
|
||||||
this.end = this.end.bind(this);
|
this.end = this.end.bind(this);
|
||||||
this._display = this._display.bind(this);
|
this._display = this._display.bind(this);
|
||||||
const _ = require('lodash');
|
const _ = require('lodash') as typeof import('lodash');
|
||||||
const through = require('through2');
|
const through = require('through2') as typeof import('through2');
|
||||||
|
|
||||||
const eventHandler = this._handleEvent;
|
const eventHandler = this._handleEvent;
|
||||||
const services = _.map(descriptors, 'serviceName');
|
const services = _.map(descriptors, 'serviceName');
|
||||||
@ -405,7 +338,6 @@ export class BuildProgressUI {
|
|||||||
.value();
|
.value();
|
||||||
|
|
||||||
this._tty = tty;
|
this._tty = tty;
|
||||||
this._serviceToDataMap = {};
|
|
||||||
this._services = services;
|
this._services = services;
|
||||||
|
|
||||||
// Logger magically prefixes the log line with [Build] etc., but it doesn't
|
// Logger magically prefixes the log line with [Build] etc., but it doesn't
|
||||||
@ -415,22 +347,22 @@ export class BuildProgressUI {
|
|||||||
|
|
||||||
const offset = 10; // account for escape sequences inserted for colouring
|
const offset = 10; // account for escape sequences inserted for colouring
|
||||||
this._prefixWidth =
|
this._prefixWidth =
|
||||||
offset + prefix.length + _.max(_.map(services, 'length'));
|
offset + prefix.length + _.max(_.map(services, (s) => s.length))!;
|
||||||
this._prefix = prefix;
|
this._prefix = prefix;
|
||||||
|
|
||||||
// these are to handle window wrapping
|
|
||||||
this._maxLineWidth = null;
|
|
||||||
this._lineWidths = [];
|
|
||||||
|
|
||||||
this._startTime = null;
|
|
||||||
this._ended = false;
|
this._ended = false;
|
||||||
this._cancelled = false;
|
this._cancelled = false;
|
||||||
this._spinner = require('./compose_ts').createSpinner();
|
this._spinner = (
|
||||||
|
require('./compose_ts') as typeof import('./compose_ts')
|
||||||
|
).createSpinner();
|
||||||
|
|
||||||
this.streams = streams;
|
this.streams = streams;
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleEvent(service, event) {
|
_handleEvent(
|
||||||
|
service: string,
|
||||||
|
event: { status?: string; progress?: number; error?: Error },
|
||||||
|
) {
|
||||||
this._serviceToDataMap[service] = event;
|
this._serviceToDataMap[service] = event;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -439,17 +371,19 @@ export class BuildProgressUI {
|
|||||||
this._services.forEach((service) => {
|
this._services.forEach((service) => {
|
||||||
this.streams[service].write({ status: 'Preparing...' });
|
this.streams[service].write({ status: 'Preparing...' });
|
||||||
});
|
});
|
||||||
this._runloop = require('./compose_ts').createRunLoop(this._display);
|
this._runloop = (
|
||||||
|
require('./compose_ts') as typeof import('./compose_ts')
|
||||||
|
).createRunLoop(this._display);
|
||||||
this._startTime = Date.now();
|
this._startTime = Date.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
end(summary = null) {
|
end(summary?: Dictionary<string>) {
|
||||||
if (this._ended) {
|
if (this._ended) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._ended = true;
|
this._ended = true;
|
||||||
this._runloop?.end();
|
this._runloop?.end();
|
||||||
this._runloop = null;
|
this._runloop = undefined;
|
||||||
|
|
||||||
this._clear();
|
this._clear();
|
||||||
this._renderStatus(true);
|
this._renderStatus(true);
|
||||||
@ -470,7 +404,7 @@ export class BuildProgressUI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_getServiceSummary() {
|
_getServiceSummary() {
|
||||||
const _ = require('lodash');
|
const _ = require('lodash') as typeof import('lodash');
|
||||||
|
|
||||||
const services = this._services;
|
const services = this._services;
|
||||||
const serviceToDataMap = this._serviceToDataMap;
|
const serviceToDataMap = this._serviceToDataMap;
|
||||||
@ -497,11 +431,11 @@ export class BuildProgressUI {
|
|||||||
.value();
|
.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderStatus(end) {
|
_renderStatus(end = false) {
|
||||||
end ??= false;
|
const moment = require('moment') as typeof import('moment');
|
||||||
|
(
|
||||||
const moment = require('moment');
|
require('moment-duration-format') as typeof import('moment-duration-format')
|
||||||
require('moment-duration-format')(moment);
|
)(moment);
|
||||||
|
|
||||||
this._tty.clearLine();
|
this._tty.clearLine();
|
||||||
this._tty.write(this._prefix);
|
this._tty.write(this._prefix);
|
||||||
@ -526,11 +460,11 @@ export class BuildProgressUI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderSummary(serviceToStrMap) {
|
_renderSummary(serviceToStrMap: Dictionary<string>) {
|
||||||
const _ = require('lodash');
|
const _ = require('lodash') as typeof import('lodash');
|
||||||
const chalk = getChalk();
|
const chalk = getChalk();
|
||||||
const truncate = require('cli-truncate');
|
const truncate = require('cli-truncate') as typeof import('cli-truncate');
|
||||||
const strlen = require('string-width');
|
const strlen = require('string-width') as typeof import('string-width');
|
||||||
|
|
||||||
this._services.forEach((service, index) => {
|
this._services.forEach((service, index) => {
|
||||||
let str = _.padEnd(this._prefix + chalk.bold(service), this._prefixWidth);
|
let str = _.padEnd(this._prefix + chalk.bold(service), this._prefixWidth);
|
||||||
@ -546,13 +480,23 @@ export class BuildProgressUI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class BuildProgressInline {
|
export class BuildProgressInline implements Renderer {
|
||||||
constructor(outStream, descriptors) {
|
public streams;
|
||||||
|
private _prefixWidth;
|
||||||
|
private _outStream;
|
||||||
|
private _services;
|
||||||
|
private _startTime: number | undefined;
|
||||||
|
private _ended;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
outStream: NodeJS.ReadWriteStream,
|
||||||
|
descriptors: Array<{ serviceName: string }>,
|
||||||
|
) {
|
||||||
this.start = this.start.bind(this);
|
this.start = this.start.bind(this);
|
||||||
this.end = this.end.bind(this);
|
this.end = this.end.bind(this);
|
||||||
this._renderEvent = this._renderEvent.bind(this);
|
this._renderEvent = this._renderEvent.bind(this);
|
||||||
const _ = require('lodash');
|
const _ = require('lodash') as typeof import('lodash');
|
||||||
const through = require('through2');
|
const through = require('through2') as typeof import('through2');
|
||||||
|
|
||||||
const services = _.map(descriptors, 'serviceName');
|
const services = _.map(descriptors, 'serviceName');
|
||||||
const eventHandler = this._renderEvent;
|
const eventHandler = this._renderEvent;
|
||||||
@ -569,10 +513,9 @@ export class BuildProgressInline {
|
|||||||
.value();
|
.value();
|
||||||
|
|
||||||
const offset = 10; // account for escape sequences inserted for colouring
|
const offset = 10; // account for escape sequences inserted for colouring
|
||||||
this._prefixWidth = offset + _.max(_.map(services, 'length'));
|
this._prefixWidth = offset + _.max(_.map(services, (s) => s.length))!;
|
||||||
this._outStream = outStream;
|
this._outStream = outStream;
|
||||||
this._services = services;
|
this._services = services;
|
||||||
this._startTime = null;
|
|
||||||
this._ended = false;
|
this._ended = false;
|
||||||
|
|
||||||
this.streams = streams;
|
this.streams = streams;
|
||||||
@ -586,9 +529,11 @@ export class BuildProgressInline {
|
|||||||
this._startTime = Date.now();
|
this._startTime = Date.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
end(summary = null) {
|
end(summary?: Dictionary<string>) {
|
||||||
const moment = require('moment');
|
const moment = require('moment') as typeof import('moment');
|
||||||
require('moment-duration-format')(moment);
|
(
|
||||||
|
require('moment-duration-format') as typeof import('moment-duration-format')
|
||||||
|
)(moment);
|
||||||
|
|
||||||
if (this._ended) {
|
if (this._ended) {
|
||||||
return;
|
return;
|
||||||
@ -616,8 +561,8 @@ export class BuildProgressInline {
|
|||||||
this._outStream.write(`Built ${serviceStr} in ${durationStr}\n`);
|
this._outStream.write(`Built ${serviceStr} in ${durationStr}\n`);
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderEvent(service, event) {
|
_renderEvent(service: string, event: { status?: string; error?: Error }) {
|
||||||
const _ = require('lodash');
|
const _ = require('lodash') as typeof import('lodash');
|
||||||
|
|
||||||
const str = (function () {
|
const str = (function () {
|
||||||
const { status, error } = event;
|
const { status, error } = event;
|
@ -43,7 +43,6 @@ import {
|
|||||||
import type { DeviceInfo } from './device/api';
|
import type { DeviceInfo } from './device/api';
|
||||||
import { getBalenaSdk, getChalk, stripIndent } from './lazy';
|
import { getBalenaSdk, getChalk, stripIndent } from './lazy';
|
||||||
import Logger = require('./logger');
|
import Logger = require('./logger');
|
||||||
import { isV13 } from './version';
|
|
||||||
import { exists } from './which';
|
import { exists } from './which';
|
||||||
|
|
||||||
const allowedContractTypes = ['sw.application', 'sw.block'];
|
const allowedContractTypes = ['sw.application', 'sw.block'];
|
||||||
@ -105,8 +104,6 @@ export async function applyReleaseTagKeysAndValues(
|
|||||||
|
|
||||||
const LOG_LENGTH_MAX = 512 * 1024; // 512KB
|
const LOG_LENGTH_MAX = 512 * 1024; // 512KB
|
||||||
const compositionFileNames = ['docker-compose.yml', 'docker-compose.yaml'];
|
const compositionFileNames = ['docker-compose.yml', 'docker-compose.yaml'];
|
||||||
const hr =
|
|
||||||
'----------------------------------------------------------------------';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* high-level function resolving a project and creating a composition out
|
* high-level function resolving a project and creating a composition out
|
||||||
@ -238,7 +235,7 @@ interface BuildTaskPlus extends MultiBuild.BuildTask {
|
|||||||
logBuffer?: string[];
|
logBuffer?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Renderer {
|
export interface Renderer {
|
||||||
start: () => void;
|
start: () => void;
|
||||||
end: (buildSummaryByService?: Dictionary<string>) => void;
|
end: (buildSummaryByService?: Dictionary<string>) => void;
|
||||||
streams: Dictionary<NodeJS.ReadWriteStream>;
|
streams: Dictionary<NodeJS.ReadWriteStream>;
|
||||||
@ -257,7 +254,6 @@ export interface BuildProjectOpts {
|
|||||||
inlineLogs?: boolean;
|
inlineLogs?: boolean;
|
||||||
convertEol: boolean;
|
convertEol: boolean;
|
||||||
dockerfilePath?: string;
|
dockerfilePath?: string;
|
||||||
nogitignore: boolean; // v13: delete this line
|
|
||||||
multiDockerignore: boolean;
|
multiDockerignore: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -748,43 +744,19 @@ export function isBuildConfig(
|
|||||||
* Create a tar stream out of the local filesystem at the given directory,
|
* Create a tar stream out of the local filesystem at the given directory,
|
||||||
* while optionally applying file filters such as '.dockerignore' and
|
* while optionally applying file filters such as '.dockerignore' and
|
||||||
* optionally converting text file line endings (CRLF to LF).
|
* optionally converting text file line endings (CRLF to LF).
|
||||||
* @param dir Source directory
|
* @param dir Project directory (the '--source' command line option)
|
||||||
* @param param Options
|
* @param param TarDirectoryOptions
|
||||||
* @returns Readable stream
|
* @returns Readable stream (to be sent to the Docker Engine)
|
||||||
*/
|
*/
|
||||||
export async function tarDirectory(
|
export async function tarDirectory(
|
||||||
dir: string,
|
|
||||||
param: TarDirectoryOptions,
|
|
||||||
): Promise<import('stream').Readable> {
|
|
||||||
const { nogitignore = false } = param; // v13: delete this line
|
|
||||||
if (isV13() || nogitignore) {
|
|
||||||
return newTarDirectory(dir, param);
|
|
||||||
} else {
|
|
||||||
return (await import('./compose')).originalTarDirectory(dir, param);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a tar stream out of the local filesystem at the given directory,
|
|
||||||
* while optionally applying file filters such as '.dockerignore' and
|
|
||||||
* optionally converting text file line endings (CRLF to LF).
|
|
||||||
* @param dir Source directory
|
|
||||||
* @param param Options
|
|
||||||
* @returns Readable stream
|
|
||||||
*/
|
|
||||||
async function newTarDirectory(
|
|
||||||
dir: string,
|
dir: string,
|
||||||
{
|
{
|
||||||
composition,
|
composition,
|
||||||
convertEol = false,
|
convertEol = false,
|
||||||
multiDockerignore = false,
|
multiDockerignore = false,
|
||||||
nogitignore = false, // v13: delete this line
|
|
||||||
preFinalizeCallback,
|
preFinalizeCallback,
|
||||||
}: TarDirectoryOptions,
|
}: TarDirectoryOptions,
|
||||||
): Promise<import('stream').Readable> {
|
): Promise<import('stream').Readable> {
|
||||||
if (!isV13()) {
|
|
||||||
require('assert').strict.equal(nogitignore, true);
|
|
||||||
}
|
|
||||||
const { filterFilesWithDockerignore } = await import('./ignore');
|
const { filterFilesWithDockerignore } = await import('./ignore');
|
||||||
const { toPosixPath } = (await import('resin-multibuild')).PathUtils;
|
const { toPosixPath } = (await import('resin-multibuild')).PathUtils;
|
||||||
|
|
||||||
@ -905,48 +877,6 @@ function printDockerignoreWarn(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Print a deprecation warning if any '.gitignore' or '.dockerignore' file is
|
|
||||||
* found and the --gitignore (-g) option has been provided (v11 compatibility).
|
|
||||||
* @param dockerignoreFile Absolute path to a .dockerignore file
|
|
||||||
* @param gitignoreFiles Array of absolute paths to .gitginore files
|
|
||||||
*
|
|
||||||
* v13: delete this function
|
|
||||||
*/
|
|
||||||
export function printGitignoreWarn(
|
|
||||||
dockerignoreFile: string,
|
|
||||||
gitignoreFiles: string[],
|
|
||||||
) {
|
|
||||||
if (isV13()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const ignoreFiles = [dockerignoreFile, ...gitignoreFiles].filter((e) => e);
|
|
||||||
if (ignoreFiles.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const msg = [' ', hr, 'Using file ignore patterns from:'];
|
|
||||||
msg.push(...ignoreFiles.map((e) => `* ${e}`));
|
|
||||||
if (gitignoreFiles.length) {
|
|
||||||
msg.push(stripIndent`
|
|
||||||
.gitignore files are being considered because the --gitignore option was used.
|
|
||||||
This option is deprecated and will be removed in the next major version release.
|
|
||||||
For more information, see 'balena help ${Logger.command}'.
|
|
||||||
`);
|
|
||||||
msg.push(hr);
|
|
||||||
Logger.getLogger().logWarn(msg.join('\n'));
|
|
||||||
} else if (dockerignoreFile && process.platform === 'win32') {
|
|
||||||
msg.push(stripIndent`
|
|
||||||
The --gitignore option was used, but no .gitignore files were found.
|
|
||||||
The --gitignore option is deprecated and will be removed in the next major
|
|
||||||
version release. It prevents the use of a better dockerignore parser and
|
|
||||||
filter library that fixes several issues on Windows and improves compatibility
|
|
||||||
with 'docker build'. For more information, see 'balena help ${Logger.command}'.
|
|
||||||
`);
|
|
||||||
msg.push(hr);
|
|
||||||
Logger.getLogger().logWarn(msg.join('\n'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether the "build secrets" feature is being used and, if so,
|
* Check whether the "build secrets" feature is being used and, if so,
|
||||||
* verify that the target docker daemon is balenaEngine. If the
|
* verify that the target docker daemon is balenaEngine. If the
|
||||||
@ -1729,21 +1659,6 @@ export const composeCliFlags: flags.Input<ComposeCliFlags> = {
|
|||||||
description:
|
description:
|
||||||
'Hide the image build log output (produce less verbose output)',
|
'Hide the image build log output (produce less verbose output)',
|
||||||
}),
|
}),
|
||||||
...(isV13()
|
|
||||||
? {}
|
|
||||||
: {
|
|
||||||
gitignore: flags.boolean({
|
|
||||||
description: stripIndent`
|
|
||||||
Consider .gitignore files in addition to the .dockerignore file. This reverts
|
|
||||||
to the CLI v11 behavior/implementation (deprecated) if compatibility is required
|
|
||||||
until your project can be adapted.`,
|
|
||||||
char: 'g',
|
|
||||||
}),
|
|
||||||
nogitignore: flags.boolean({
|
|
||||||
description: `No-op (default behavior) since balena CLI v12.0.0. See "balena help build".`,
|
|
||||||
char: 'G',
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
'multi-dockerignore': flags.boolean({
|
'multi-dockerignore': flags.boolean({
|
||||||
description:
|
description:
|
||||||
'Have each service use its own .dockerignore file. See "balena help build".',
|
'Have each service use its own .dockerignore file. See "balena help build".',
|
||||||
@ -1758,10 +1673,6 @@ export const composeCliFlags: flags.Input<ComposeCliFlags> = {
|
|||||||
'Path to a YAML or JSON file with passwords for a private Docker registry',
|
'Path to a YAML or JSON file with passwords for a private Docker registry',
|
||||||
char: 'R',
|
char: 'R',
|
||||||
}),
|
}),
|
||||||
'convert-eol': flags.boolean({
|
|
||||||
description: 'No-op and deprecated since balena CLI v12.0.0',
|
|
||||||
char: 'l',
|
|
||||||
}),
|
|
||||||
'noconvert-eol': flags.boolean({
|
'noconvert-eol': flags.boolean({
|
||||||
description:
|
description:
|
||||||
"Don't convert line endings from CRLF (Windows format) to LF (Unix format).",
|
"Don't convert line endings from CRLF (Windows format) to LF (Unix format).",
|
||||||
|
@ -15,7 +15,7 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
import type * as BalenaSdk from 'balena-sdk';
|
import type * as BalenaSdk from 'balena-sdk';
|
||||||
import * as semver from 'balena-semver';
|
import * as semver from 'balena-semver';
|
||||||
import { getBalenaSdk } from './lazy';
|
import { getBalenaSdk, stripIndent } from './lazy';
|
||||||
|
|
||||||
export interface ImgConfig {
|
export interface ImgConfig {
|
||||||
applicationName: string;
|
applicationName: string;
|
||||||
@ -134,3 +134,34 @@ export function generateDeviceConfig(
|
|||||||
return config;
|
return config;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chech whether the `--dev` option of commands related to OS configuration
|
||||||
|
* such as `os configure` and `config generate` is compatible with a given
|
||||||
|
* balenaOS version, and print a warning regarding the consequences of using
|
||||||
|
* that option.
|
||||||
|
*/
|
||||||
|
export async function validateDevOptionAndWarn(
|
||||||
|
dev?: boolean,
|
||||||
|
version?: string,
|
||||||
|
logger?: import('./logger'),
|
||||||
|
) {
|
||||||
|
if (!dev) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (version && /\bprod\b/.test(version)) {
|
||||||
|
const { ExpectedError } = await import('../errors');
|
||||||
|
throw new ExpectedError(
|
||||||
|
`Error: The '--dev' option conflicts with production balenaOS version '${version}'`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!logger) {
|
||||||
|
const Logger = await import('./logger');
|
||||||
|
logger = Logger.getLogger();
|
||||||
|
}
|
||||||
|
logger.logInfo(stripIndent`
|
||||||
|
The '--dev' option is being used to configure a balenaOS image in development mode.
|
||||||
|
Please note that development mode allows unauthenticated, passwordless root ssh access
|
||||||
|
and exposes network ports such as 2375 that allows unencrypted access to balenaEngine.
|
||||||
|
Therefore, development mode should only be used in private, trusted local networks.`);
|
||||||
|
}
|
||||||
|
@ -17,15 +17,27 @@
|
|||||||
|
|
||||||
import { getVisuals } from './lazy';
|
import { getVisuals } from './lazy';
|
||||||
import { promisify } from 'util';
|
import { promisify } from 'util';
|
||||||
|
import type * as Dockerode from 'dockerode';
|
||||||
|
import type Logger = require('./logger');
|
||||||
|
import type { Request } from 'request';
|
||||||
|
|
||||||
const getBuilderPushEndpoint = function (baseUrl, owner, app) {
|
const getBuilderPushEndpoint = function (
|
||||||
const querystring = require('querystring');
|
baseUrl: string,
|
||||||
|
owner: string,
|
||||||
|
app: string,
|
||||||
|
) {
|
||||||
|
const querystring = require('querystring') as typeof import('querystring');
|
||||||
const args = querystring.stringify({ owner, app });
|
const args = querystring.stringify({ owner, app });
|
||||||
return `https://builder.${baseUrl}/v1/push?${args}`;
|
return `https://builder.${baseUrl}/v1/push?${args}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getBuilderLogPushEndpoint = function (baseUrl, buildId, owner, app) {
|
const getBuilderLogPushEndpoint = function (
|
||||||
const querystring = require('querystring');
|
baseUrl: string,
|
||||||
|
buildId: number,
|
||||||
|
owner: string,
|
||||||
|
app: string,
|
||||||
|
) {
|
||||||
|
const querystring = require('querystring') as typeof import('querystring');
|
||||||
const args = querystring.stringify({ owner, app, buildId });
|
const args = querystring.stringify({ owner, app, buildId });
|
||||||
return `https://builder.${baseUrl}/v1/pushLogs?${args}`;
|
return `https://builder.${baseUrl}/v1/pushLogs?${args}`;
|
||||||
};
|
};
|
||||||
@ -35,32 +47,37 @@ const getBuilderLogPushEndpoint = function (baseUrl, buildId, owner, app) {
|
|||||||
* @param {string} imageId
|
* @param {string} imageId
|
||||||
* @param {string} bufferFile
|
* @param {string} bufferFile
|
||||||
*/
|
*/
|
||||||
const bufferImage = function (docker, imageId, bufferFile) {
|
const bufferImage = function (
|
||||||
const streamUtils = require('./streams');
|
docker: Dockerode,
|
||||||
|
imageId: string,
|
||||||
|
bufferFile: string,
|
||||||
|
): Promise<NodeJS.ReadableStream & { length: number }> {
|
||||||
|
const streamUtils = require('./streams') as typeof import('./streams');
|
||||||
|
|
||||||
const image = docker.getImage(imageId);
|
const image = docker.getImage(imageId);
|
||||||
const sizePromise = image.inspect().then((img) => img.Size);
|
const sizePromise = image.inspect().then((img) => img.Size);
|
||||||
|
|
||||||
return Promise.all([image.get(), sizePromise]).then(
|
return Promise.all([image.get(), sizePromise]).then(
|
||||||
([imageStream, imageSize]) =>
|
([imageStream, imageSize]) =>
|
||||||
streamUtils.buffer(imageStream, bufferFile).then((bufferedStream) => {
|
streamUtils
|
||||||
// @ts-ignore adding an extra property
|
.buffer(imageStream, bufferFile)
|
||||||
bufferedStream.length = imageSize;
|
.then((bufferedStream: NodeJS.ReadableStream & { length?: number }) => {
|
||||||
return bufferedStream;
|
bufferedStream.length = imageSize;
|
||||||
}),
|
return bufferedStream as NodeJS.ReadableStream & { length: number };
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const showPushProgress = function (message) {
|
const showPushProgress = function (message: string) {
|
||||||
const visuals = getVisuals();
|
const visuals = getVisuals();
|
||||||
const progressBar = new visuals.Progress(message);
|
const progressBar = new visuals.Progress(message);
|
||||||
progressBar.update({ percentage: 0 });
|
progressBar.update({ percentage: 0 });
|
||||||
return progressBar;
|
return progressBar;
|
||||||
};
|
};
|
||||||
|
|
||||||
const uploadToPromise = (uploadRequest, logger) =>
|
const uploadToPromise = (uploadRequest: Request, logger: Logger) =>
|
||||||
new Promise(function (resolve, reject) {
|
new Promise<{ buildId: number }>(function (resolve, reject) {
|
||||||
const handleMessage = function (data) {
|
uploadRequest.on('error', reject).on('data', function handleMessage(data) {
|
||||||
let obj;
|
let obj;
|
||||||
data = data.toString();
|
data = data.toString();
|
||||||
logger.logDebug(`Received data: ${data}`);
|
logger.logDebug(`Received data: ${data}`);
|
||||||
@ -86,25 +103,24 @@ const uploadToPromise = (uploadRequest, logger) =>
|
|||||||
default:
|
default:
|
||||||
reject(new Error(`Received unexpected reply from remote: ${data}`));
|
reject(new Error(`Received unexpected reply from remote: ${data}`));
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
uploadRequest.on('error', reject).on('data', handleMessage);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {Promise<{ buildId: number }>}
|
* @returns {Promise<{ buildId: number }>}
|
||||||
*/
|
*/
|
||||||
const uploadImage = function (
|
const uploadImage = function (
|
||||||
imageStream,
|
imageStream: NodeJS.ReadableStream & { length: number },
|
||||||
token,
|
token: string,
|
||||||
username,
|
username: string,
|
||||||
url,
|
url: string,
|
||||||
appName,
|
appName: string,
|
||||||
logger,
|
logger: Logger,
|
||||||
) {
|
): Promise<{ buildId: number }> {
|
||||||
const request = require('request');
|
const request = require('request') as typeof import('request');
|
||||||
const progressStream = require('progress-stream');
|
const progressStream =
|
||||||
const zlib = require('zlib');
|
require('progress-stream') as typeof import('progress-stream');
|
||||||
|
const zlib = require('zlib') as typeof import('zlib');
|
||||||
|
|
||||||
// Need to strip off the newline
|
// Need to strip off the newline
|
||||||
const progressMessage = logger
|
const progressMessage = logger
|
||||||
@ -143,8 +159,15 @@ const uploadImage = function (
|
|||||||
return uploadToPromise(uploadRequest, logger);
|
return uploadToPromise(uploadRequest, logger);
|
||||||
};
|
};
|
||||||
|
|
||||||
const uploadLogs = function (logs, token, url, buildId, username, appName) {
|
const uploadLogs = function (
|
||||||
const request = require('request');
|
logs: string,
|
||||||
|
token: string,
|
||||||
|
url: string,
|
||||||
|
buildId: number,
|
||||||
|
username: string,
|
||||||
|
appName: string,
|
||||||
|
) {
|
||||||
|
const request = require('request') as typeof import('request');
|
||||||
return request.post({
|
return request.post({
|
||||||
json: true,
|
json: true,
|
||||||
url: getBuilderLogPushEndpoint(url, buildId, username, appName),
|
url: getBuilderLogPushEndpoint(url, buildId, username, appName),
|
||||||
@ -156,25 +179,24 @@ const uploadLogs = function (logs, token, url, buildId, username, appName) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import('dockerode')} docker
|
|
||||||
* @param {import('./logger')} logger
|
|
||||||
* @param {string} token
|
|
||||||
* @param {string} username
|
|
||||||
* @param {string} url
|
|
||||||
* @param {{appName: string; imageName: string; buildLogs: string; shouldUploadLogs: boolean}} opts
|
|
||||||
* - appName: the name of the app to deploy to
|
* - appName: the name of the app to deploy to
|
||||||
* - imageName: the name of the image to deploy
|
* - imageName: the name of the image to deploy
|
||||||
* - buildLogs: a string with build output
|
* - buildLogs: a string with build output
|
||||||
*/
|
*/
|
||||||
export const deployLegacy = async function (
|
export const deployLegacy = async function (
|
||||||
docker,
|
docker: Dockerode,
|
||||||
logger,
|
logger: Logger,
|
||||||
token,
|
token: string,
|
||||||
username,
|
username: string,
|
||||||
url,
|
url: string,
|
||||||
opts,
|
opts: {
|
||||||
) {
|
appName: string;
|
||||||
const tmp = require('tmp');
|
imageName: string;
|
||||||
|
buildLogs: string;
|
||||||
|
shouldUploadLogs: boolean;
|
||||||
|
},
|
||||||
|
): Promise<number> {
|
||||||
|
const tmp = require('tmp') as typeof import('tmp');
|
||||||
const tmpNameAsync = promisify(tmp.tmpName);
|
const tmpNameAsync = promisify(tmp.tmpName);
|
||||||
|
|
||||||
// Ensure the tmp files gets deleted
|
// Ensure the tmp files gets deleted
|
||||||
@ -195,8 +217,8 @@ export const deployLegacy = async function (
|
|||||||
// has occured before any data was written) this call will throw an
|
// has occured before any data was written) this call will throw an
|
||||||
// ugly error, just suppress it
|
// ugly error, just suppress it
|
||||||
|
|
||||||
require('fs')
|
(require('fs') as typeof import('fs')).promises
|
||||||
.promises.unlink(bufferFile)
|
.unlink(bufferFile)
|
||||||
.catch(() => undefined),
|
.catch(() => undefined),
|
||||||
);
|
);
|
||||||
|
|
@ -59,7 +59,6 @@ export interface DeviceDeployOptions {
|
|||||||
registrySecrets: RegistrySecrets;
|
registrySecrets: RegistrySecrets;
|
||||||
multiDockerignore: boolean;
|
multiDockerignore: boolean;
|
||||||
nocache: boolean;
|
nocache: boolean;
|
||||||
nogitignore: boolean; // v13: delete this line
|
|
||||||
noParentCheck: boolean;
|
noParentCheck: boolean;
|
||||||
nolive: boolean;
|
nolive: boolean;
|
||||||
pull: boolean;
|
pull: boolean;
|
||||||
@ -184,7 +183,6 @@ export async function deployToDevice(opts: DeviceDeployOptions): Promise<void> {
|
|||||||
convertEol: opts.convertEol,
|
convertEol: opts.convertEol,
|
||||||
dockerfilePath: opts.dockerfilePath,
|
dockerfilePath: opts.dockerfilePath,
|
||||||
multiDockerignore: opts.multiDockerignore,
|
multiDockerignore: opts.multiDockerignore,
|
||||||
nogitignore: opts.nogitignore, // v13: delete this line
|
|
||||||
noParentCheck: opts.noParentCheck,
|
noParentCheck: opts.noParentCheck,
|
||||||
projectName: 'local',
|
projectName: 'local',
|
||||||
projectPath: opts.source,
|
projectPath: opts.source,
|
||||||
@ -204,7 +202,6 @@ export async function deployToDevice(opts: DeviceDeployOptions): Promise<void> {
|
|||||||
composition: project.composition,
|
composition: project.composition,
|
||||||
convertEol: opts.convertEol,
|
convertEol: opts.convertEol,
|
||||||
multiDockerignore: opts.multiDockerignore,
|
multiDockerignore: opts.multiDockerignore,
|
||||||
nogitignore: opts.nogitignore, // v13: delete this line
|
|
||||||
});
|
});
|
||||||
globalLogger.logDebug(`Tarring complete in ${Date.now() - tarStartTime} ms`);
|
globalLogger.logDebug(`Tarring complete in ${Date.now() - tarStartTime} ms`);
|
||||||
|
|
||||||
@ -435,7 +432,6 @@ export async function rebuildSingleTask(
|
|||||||
composition,
|
composition,
|
||||||
convertEol: opts.convertEol,
|
convertEol: opts.convertEol,
|
||||||
multiDockerignore: opts.multiDockerignore,
|
multiDockerignore: opts.multiDockerignore,
|
||||||
nogitignore: opts.nogitignore, // v13: delete this line
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const task = _.find(
|
const task = _.find(
|
||||||
|
@ -193,19 +193,7 @@ export async function createClient(
|
|||||||
opts: dockerode.DockerOptions,
|
opts: dockerode.DockerOptions,
|
||||||
): Promise<dockerode> {
|
): Promise<dockerode> {
|
||||||
const Docker = await import('dockerode');
|
const Docker = await import('dockerode');
|
||||||
const docker = new Docker(opts);
|
return new Docker(opts);
|
||||||
const { modem } = docker;
|
|
||||||
// Workaround for a docker-modem 2.0.x bug where it sets a default
|
|
||||||
// socketPath on Windows even if the input options specify a host/port.
|
|
||||||
if (modem.socketPath && modem.host) {
|
|
||||||
if (opts.socketPath) {
|
|
||||||
modem.host = undefined;
|
|
||||||
modem.port = undefined;
|
|
||||||
} else if (opts.host) {
|
|
||||||
modem.socketPath = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return docker;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function generateConnectOpts(opts: ExtendedDockerOptions) {
|
async function generateConnectOpts(opts: ExtendedDockerOptions) {
|
||||||
|
@ -89,10 +89,10 @@ export async function sudo(
|
|||||||
await executeWithPrivileges(command, stderr, isCLIcmd);
|
await executeWithPrivileges(command, stderr, isCLIcmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function runCommand<T>(commandArgs: string[]): Promise<T> {
|
export async function runCommand<T>(commandArgs: string[]): Promise<T> {
|
||||||
const { isSubcommand } =
|
const { isSubcommand } =
|
||||||
require('../preparser') as typeof import('../preparser');
|
require('../preparser') as typeof import('../preparser');
|
||||||
if (isSubcommand(commandArgs)) {
|
if (await isSubcommand(commandArgs)) {
|
||||||
commandArgs = [
|
commandArgs = [
|
||||||
commandArgs[0] + ':' + commandArgs[1],
|
commandArgs[0] + ':' + commandArgs[1],
|
||||||
...commandArgs.slice(2),
|
...commandArgs.slice(2),
|
||||||
@ -151,26 +151,8 @@ export async function osProgressHandler(step: InitializeEmitter) {
|
|||||||
export async function getAppWithArch(
|
export async function getAppWithArch(
|
||||||
applicationName: string,
|
applicationName: string,
|
||||||
): Promise<ApplicationWithDeviceType & { arch: string }> {
|
): Promise<ApplicationWithDeviceType & { arch: string }> {
|
||||||
const app = await getApplication(applicationName);
|
const { getApplication } = await import('./sdk');
|
||||||
const { getExpanded } = await import('./pine');
|
const options: BalenaSdk.PineOptions<BalenaSdk.Application> = {
|
||||||
|
|
||||||
return {
|
|
||||||
...app,
|
|
||||||
arch: getExpanded(
|
|
||||||
getExpanded(app.is_for__device_type)!.is_of__cpu_architecture,
|
|
||||||
)!.slug,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Drop this. The sdk now has this baked in application.get().
|
|
||||||
function getApplication(
|
|
||||||
applicationName: string,
|
|
||||||
): Promise<ApplicationWithDeviceType> {
|
|
||||||
// Check for an app of the form `user/application`, and send
|
|
||||||
// that off to a special handler (before importing any modules)
|
|
||||||
const match = applicationName.split('/');
|
|
||||||
|
|
||||||
const extraOptions: BalenaSdk.PineOptions<BalenaSdk.Application> = {
|
|
||||||
$expand: {
|
$expand: {
|
||||||
application_type: {
|
application_type: {
|
||||||
$select: ['name', 'slug', 'supports_multicontainer', 'is_legacy'],
|
$select: ['name', 'slug', 'supports_multicontainer', 'is_legacy'],
|
||||||
@ -185,20 +167,20 @@ function getApplication(
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const balena = getBalenaSdk();
|
const balena = getBalenaSdk();
|
||||||
if (match.length > 1) {
|
const app = (await getApplication(
|
||||||
return balena.models.application.getAppByOwner(
|
balena,
|
||||||
match[1],
|
|
||||||
match[0],
|
|
||||||
extraOptions,
|
|
||||||
) as Promise<ApplicationWithDeviceType>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return balena.models.application.get(
|
|
||||||
applicationName,
|
applicationName,
|
||||||
extraOptions,
|
options,
|
||||||
) as Promise<ApplicationWithDeviceType>;
|
)) as ApplicationWithDeviceType;
|
||||||
|
const { getExpanded } = await import('./pine');
|
||||||
|
|
||||||
|
return {
|
||||||
|
...app,
|
||||||
|
arch: getExpanded(
|
||||||
|
getExpanded(app.is_for__device_type)!.is_of__cpu_architecture,
|
||||||
|
)!.slug,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const second = 1000; // 1000 milliseconds
|
const second = 1000; // 1000 milliseconds
|
||||||
@ -428,7 +410,7 @@ export function getProxyConfig(): ProxyConfig | undefined {
|
|||||||
|
|
||||||
export const expandForAppName = {
|
export const expandForAppName = {
|
||||||
$expand: {
|
$expand: {
|
||||||
belongs_to__application: { $select: 'app_name' },
|
belongs_to__application: { $select: ['app_name', 'slug'] as any },
|
||||||
is_of__device_type: { $select: 'slug' },
|
is_of__device_type: { $select: 'slug' },
|
||||||
is_running__release: { $select: 'commit' },
|
is_running__release: { $select: 'commit' },
|
||||||
},
|
},
|
||||||
|
@ -17,183 +17,11 @@
|
|||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
import { promises as fs, Stats } from 'fs';
|
import { promises as fs, Stats } from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as MultiBuild from 'resin-multibuild';
|
|
||||||
|
|
||||||
import dockerIgnore = require('@zeit/dockerignore');
|
|
||||||
import ignore from 'ignore';
|
|
||||||
import type { Ignore } from '@balena/dockerignore';
|
import type { Ignore } from '@balena/dockerignore';
|
||||||
|
|
||||||
import { ExpectedError } from '../errors';
|
import { ExpectedError } from '../errors';
|
||||||
|
|
||||||
const { toPosixPath } = MultiBuild.PathUtils;
|
|
||||||
|
|
||||||
// v13: delete this enum
|
|
||||||
export enum IgnoreFileType {
|
|
||||||
DockerIgnore,
|
|
||||||
GitIgnore,
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IgnoreEntry {
|
|
||||||
pattern: string;
|
|
||||||
// The relative file path from the base path of the build context
|
|
||||||
filePath: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class is used by the CLI v10 / v11 "original" tarDirectory function
|
|
||||||
* in `compose.js`. It is still around for the benefit of the `--gitignore`
|
|
||||||
* option, but is expected to be deleted in CLI v13.
|
|
||||||
*
|
|
||||||
* v13: delete this class
|
|
||||||
*/
|
|
||||||
export class FileIgnorer {
|
|
||||||
private dockerIgnoreEntries: IgnoreEntry[];
|
|
||||||
private gitIgnoreEntries: IgnoreEntry[];
|
|
||||||
|
|
||||||
private static ignoreFiles: Array<{
|
|
||||||
pattern: string;
|
|
||||||
type: IgnoreFileType;
|
|
||||||
allowSubdirs: boolean;
|
|
||||||
}> = [
|
|
||||||
{
|
|
||||||
pattern: '.gitignore',
|
|
||||||
type: IgnoreFileType.GitIgnore,
|
|
||||||
allowSubdirs: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pattern: '.dockerignore',
|
|
||||||
type: IgnoreFileType.DockerIgnore,
|
|
||||||
allowSubdirs: false,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
public constructor(public basePath: string) {
|
|
||||||
this.dockerIgnoreEntries = [];
|
|
||||||
this.gitIgnoreEntries = [];
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @param {string} relativePath
|
|
||||||
* The relative pathname from the build context, for example a root level .gitignore should be
|
|
||||||
* ./.gitignore
|
|
||||||
* @returns IgnoreFileType
|
|
||||||
* The type of ignore file, or null
|
|
||||||
*/
|
|
||||||
public getIgnoreFileType(relativePath: string): IgnoreFileType | null {
|
|
||||||
for (const { pattern, type, allowSubdirs } of FileIgnorer.ignoreFiles) {
|
|
||||||
if (
|
|
||||||
path.basename(relativePath) === pattern &&
|
|
||||||
(allowSubdirs || path.dirname(relativePath) === '.')
|
|
||||||
) {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @param {string} fullPath
|
|
||||||
* The full path on disk of the ignore file
|
|
||||||
* @param {IgnoreFileType} type
|
|
||||||
* @returns Promise
|
|
||||||
*/
|
|
||||||
public async addIgnoreFile(
|
|
||||||
fullPath: string,
|
|
||||||
type: IgnoreFileType,
|
|
||||||
): Promise<void> {
|
|
||||||
const contents = await fs.readFile(fullPath, 'utf8');
|
|
||||||
|
|
||||||
contents.split('\n').forEach((line) => {
|
|
||||||
// ignore empty lines and comments
|
|
||||||
if (/\s*#/.test(line) || _.isEmpty(line)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.addEntry(line, fullPath, type);
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pass this function as a predicate to a filter function, and it will filter
|
|
||||||
// any ignored files
|
|
||||||
public filter = (filename: string): boolean => {
|
|
||||||
const relFile = path.relative(this.basePath, filename);
|
|
||||||
|
|
||||||
// Don't ignore any metadata files
|
|
||||||
// The regex below matches `.balena/qemu` and `myservice/.balena/qemu`
|
|
||||||
// but not `some.dir.for.balena/qemu`.
|
|
||||||
if (/(^|\/)\.(balena|resin)\//.test(toPosixPath(relFile))) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't ignore Dockerfile (with or without extension) or docker-compose.yml
|
|
||||||
if (
|
|
||||||
/^Dockerfile$|^Dockerfile\.\S+/.test(path.basename(relFile)) ||
|
|
||||||
path.basename(relFile) === 'docker-compose.yml'
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const dockerIgnoreHandle = dockerIgnore();
|
|
||||||
const gitIgnoreHandle = ignore();
|
|
||||||
|
|
||||||
interface IgnoreHandle {
|
|
||||||
add: (pattern: string) => void;
|
|
||||||
ignores: (file: string) => boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ignoreTypes: Array<{
|
|
||||||
handle: IgnoreHandle;
|
|
||||||
entries: IgnoreEntry[];
|
|
||||||
}> = [
|
|
||||||
{ handle: dockerIgnoreHandle, entries: this.dockerIgnoreEntries },
|
|
||||||
{ handle: gitIgnoreHandle, entries: this.gitIgnoreEntries },
|
|
||||||
];
|
|
||||||
|
|
||||||
_.each(ignoreTypes, ({ handle, entries }) => {
|
|
||||||
_.each(entries, ({ pattern, filePath }) => {
|
|
||||||
if (FileIgnorer.contains(path.posix.dirname(filePath), filename)) {
|
|
||||||
handle.add(pattern);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return !_.some(ignoreTypes, ({ handle }) => handle.ignores(relFile));
|
|
||||||
}; // tslint:disable-line:semicolon
|
|
||||||
|
|
||||||
private addEntry(
|
|
||||||
pattern: string,
|
|
||||||
filePath: string,
|
|
||||||
type: IgnoreFileType,
|
|
||||||
): void {
|
|
||||||
const entry: IgnoreEntry = { pattern, filePath };
|
|
||||||
switch (type) {
|
|
||||||
case IgnoreFileType.DockerIgnore:
|
|
||||||
this.dockerIgnoreEntries.push(entry);
|
|
||||||
break;
|
|
||||||
case IgnoreFileType.GitIgnore:
|
|
||||||
this.gitIgnoreEntries.push(entry);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given two paths, check whether the first contains the second
|
|
||||||
* @param path1 The potentially containing path
|
|
||||||
* @param path2 The potentially contained path
|
|
||||||
* @return A boolean indicating whether `path1` contains `path2`
|
|
||||||
*/
|
|
||||||
private static contains(path1: string, path2: string): boolean {
|
|
||||||
// First normalise the input, to remove any path weirdness
|
|
||||||
path1 = path.posix.normalize(path1);
|
|
||||||
path2 = path.posix.normalize(path2);
|
|
||||||
|
|
||||||
// Now test if the start of the relative path contains ../ ,
|
|
||||||
// which would tell us that path1 is not part of path2
|
|
||||||
return !/^\.\.\//.test(path.posix.relative(path1, path2));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FileStats {
|
export interface FileStats {
|
||||||
filePath: string;
|
filePath: string;
|
||||||
relPath: string;
|
relPath: string;
|
||||||
@ -225,7 +53,11 @@ async function listFiles(projectDir: string): Promise<string[]> {
|
|||||||
const _files = await fs.readdir(dir, { withFileTypes: true });
|
const _files = await fs.readdir(dir, { withFileTypes: true });
|
||||||
for (const entry of _files) {
|
for (const entry of _files) {
|
||||||
const fpath = path.join(dir, entry.name);
|
const fpath = path.join(dir, entry.name);
|
||||||
if (entry.isDirectory()) {
|
const isDirectory =
|
||||||
|
entry.isDirectory() ||
|
||||||
|
(entry.isSymbolicLink() && (await fs.stat(fpath)).isDirectory());
|
||||||
|
|
||||||
|
if (isDirectory) {
|
||||||
foundDirs.push(fpath);
|
foundDirs.push(fpath);
|
||||||
} else {
|
} else {
|
||||||
files.push(fpath);
|
files.push(fpath);
|
||||||
|
@ -21,7 +21,6 @@ import type { Chalk } from 'chalk';
|
|||||||
import type * as visuals from 'resin-cli-visuals';
|
import type * as visuals from 'resin-cli-visuals';
|
||||||
import type * as CliForm from 'resin-cli-form';
|
import type * as CliForm from 'resin-cli-form';
|
||||||
import type { ux } from 'cli-ux';
|
import type { ux } from 'cli-ux';
|
||||||
import type { stripIndent as StripIndent } from 'common-tags';
|
|
||||||
|
|
||||||
// Equivalent of _.once but avoiding the need to import lodash for lazy deps
|
// Equivalent of _.once but avoiding the need to import lodash for lazy deps
|
||||||
const once = <T>(fn: () => T) => {
|
const once = <T>(fn: () => T) => {
|
||||||
@ -63,4 +62,4 @@ export const getCliUx = once(() => require('cli-ux').ux as typeof ux);
|
|||||||
// Directly export stripIndent as we always use it immediately, but importing just `stripIndent` reduces startup time
|
// Directly export stripIndent as we always use it immediately, but importing just `stripIndent` reduces startup time
|
||||||
export const stripIndent =
|
export const stripIndent =
|
||||||
// tslint:disable-next-line:no-var-requires
|
// tslint:disable-next-line:no-var-requires
|
||||||
require('common-tags/lib/stripIndent') as typeof StripIndent;
|
require('common-tags/lib/stripIndent') as typeof import('common-tags/lib/stripIndent');
|
||||||
|
@ -15,8 +15,6 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { isV13 } from './version';
|
|
||||||
|
|
||||||
export const reachingOut = `\
|
export const reachingOut = `\
|
||||||
For further help or support, visit:
|
For further help or support, visit:
|
||||||
https://www.balena.io/docs/reference/balena-cli/#support-faq-and-troubleshooting
|
https://www.balena.io/docs/reference/balena-cli/#support-faq-and-troubleshooting
|
||||||
@ -88,60 +86,7 @@ If the --registry-secrets option is not specified, and a secrets.yml or
|
|||||||
secrets.json file exists in the balena directory (usually $HOME/.balena),
|
secrets.json file exists in the balena directory (usually $HOME/.balena),
|
||||||
this file will be used instead.`;
|
this file will be used instead.`;
|
||||||
|
|
||||||
const dockerignoreHelpV12 =
|
export const dockerignoreHelp =
|
||||||
'DOCKERIGNORE AND GITIGNORE FILES \n' +
|
|
||||||
`By default, the balena CLI will use a single ".dockerignore" file (if any) at
|
|
||||||
the project root (--source directory) in order to decide which source files to
|
|
||||||
exclude from the "build context" (tar stream) sent to balenaCloud, Docker
|
|
||||||
daemon or balenaEngine. In a microservices (multicontainer) fleet, the
|
|
||||||
source directory is the directory that contains the "docker-compose.yml" file.
|
|
||||||
|
|
||||||
The --multi-dockerignore (-m) option may be used with microservices
|
|
||||||
(multicontainer) fleets that define a docker-compose.yml file. When this
|
|
||||||
option is used, each service subdirectory (defined by the \`build\` or
|
|
||||||
\`build.context\` service properties in the docker-compose.yml file) is
|
|
||||||
filtered separately according to a .dockerignore file defined in the service
|
|
||||||
subdirectory. If no .dockerignore file exists in a service subdirectory, then
|
|
||||||
only the default .dockerignore patterns (see below) apply for that service
|
|
||||||
subdirectory.
|
|
||||||
|
|
||||||
When the --multi-dockerignore (-m) option is used, the .dockerignore file (if
|
|
||||||
any) defined at the overall project root will be used to filter files and
|
|
||||||
subdirectories other than service subdirectories. It will not have any effect
|
|
||||||
on service subdirectories, whether or not a service subdirectory defines its
|
|
||||||
own .dockerignore file. Multiple .dockerignore files are not merged or added
|
|
||||||
together, and cannot override or extend other files. This behavior maximizes
|
|
||||||
compatibility with the standard docker-compose tool, while still allowing a
|
|
||||||
root .dockerignore file (at the overall project root) to filter files and
|
|
||||||
folders that are outside service subdirectories.
|
|
||||||
|
|
||||||
balena CLI releases older than v12.0.0 also took .gitignore files into account.
|
|
||||||
This behavior is deprecated, but may still be enabled with the --gitignore (-g)
|
|
||||||
option if compatibility is required. This option is mutually exclusive with
|
|
||||||
--multi-dockerignore (-m) and will be removed in the CLI's next major version
|
|
||||||
release (v13).
|
|
||||||
|
|
||||||
Default .dockerignore patterns \n` +
|
|
||||||
`When --gitignore (-g) is NOT used (i.e. when not in v11 compatibility mode), a
|
|
||||||
few default/hardcoded dockerignore patterns are "merged" (in memory) with the
|
|
||||||
patterns found in the applicable .dockerignore files, in the following order:
|
|
||||||
\`\`\`
|
|
||||||
**/.git
|
|
||||||
< user's patterns from the applicable '.dockerignore' file, if any >
|
|
||||||
!**/.balena
|
|
||||||
!**/.resin
|
|
||||||
!**/Dockerfile
|
|
||||||
!**/Dockerfile.*
|
|
||||||
!**/docker-compose.yml
|
|
||||||
\`\`\`
|
|
||||||
These patterns always apply, whether or not .dockerignore files exist in the
|
|
||||||
project. If necessary, the effect of the \`**/.git\` pattern may be modified by
|
|
||||||
adding counter patterns to the applicable .dockerignore file(s), for example
|
|
||||||
\`!mysubmodule/.git\`. For documentation on pattern format, see:
|
|
||||||
- https://docs.docker.com/engine/reference/builder/#dockerignore-file
|
|
||||||
- https://www.npmjs.com/package/@balena/dockerignore`;
|
|
||||||
|
|
||||||
const dockerignoreHelpV13 =
|
|
||||||
'DOCKERIGNORE AND GITIGNORE FILES \n' +
|
'DOCKERIGNORE AND GITIGNORE FILES \n' +
|
||||||
`By default, the balena CLI will use a single ".dockerignore" file (if any) at
|
`By default, the balena CLI will use a single ".dockerignore" file (if any) at
|
||||||
the project root (--source directory) in order to decide which source files to
|
the project root (--source directory) in order to decide which source files to
|
||||||
@ -191,10 +136,6 @@ adding exception patterns to the applicable .dockerignore file(s), for example
|
|||||||
- https://docs.docker.com/engine/reference/builder/#dockerignore-file
|
- https://docs.docker.com/engine/reference/builder/#dockerignore-file
|
||||||
- https://www.npmjs.com/package/@balena/dockerignore`;
|
- https://www.npmjs.com/package/@balena/dockerignore`;
|
||||||
|
|
||||||
export const dockerignoreHelp = isV13()
|
|
||||||
? dockerignoreHelpV13
|
|
||||||
: dockerignoreHelpV12;
|
|
||||||
|
|
||||||
export const applicationIdInfo = `\
|
export const applicationIdInfo = `\
|
||||||
Fleets may be specified by fleet name, slug, or numeric ID. Fleet slugs are
|
Fleets may be specified by fleet name, slug, or numeric ID. Fleet slugs are
|
||||||
the recommended option, as they are unique and unambiguous. Slugs can be
|
the recommended option, as they are unique and unambiguous. Slugs can be
|
||||||
@ -218,6 +159,18 @@ created public/open fleet, or with fleets from other balena accounts that you
|
|||||||
may be invited to join under any role. For this reason, fleet names are
|
may be invited to join under any role. For this reason, fleet names are
|
||||||
especially discouraged in scripts (e.g. CI environments).`;
|
especially discouraged in scripts (e.g. CI environments).`;
|
||||||
|
|
||||||
|
export const devModeInfo = `\
|
||||||
|
The '--dev' option is used to configure balenaOS to operate in development mode,
|
||||||
|
allowing anauthenticated root ssh access and exposing network ports such as
|
||||||
|
balenaEngine's 2375 (unencrypted). This option causes \`"developmentMode": true\`
|
||||||
|
to be inserted in the 'config.json' file in the image's boot partion. Development
|
||||||
|
mode (as a configurable option) is applicable to balenaOS releases from early
|
||||||
|
2022. Older releases have separate development and production balenaOS images
|
||||||
|
that cannot be reconfigured through 'config.json' or the '--dev' option. Do not
|
||||||
|
confuse the balenaOS "development mode" with a device's "local mode", the latter
|
||||||
|
being a supervisor feature that allows the "balena push" command to push a user's
|
||||||
|
application directly to a device in the local network.`;
|
||||||
|
|
||||||
export const jsonInfo = `\
|
export const jsonInfo = `\
|
||||||
The --json option is recommended when scripting the output of this command,
|
The --json option is recommended when scripting the output of this command,
|
||||||
because field names are less likely to change in JSON format and because it
|
because field names are less likely to change in JSON format and because it
|
||||||
@ -234,30 +187,6 @@ If you have a particular use for buildArg, which is not satisfied by build-time
|
|||||||
secrets, please contact us via support or the forums: https://forums.balena.io/
|
secrets, please contact us via support or the forums: https://forums.balena.io/
|
||||||
\n`;
|
\n`;
|
||||||
|
|
||||||
// Note: if you edit this message, check that the regex replace
|
|
||||||
// logic in lib/commands/apps.ts still works.
|
|
||||||
export const appToFleetCmdMsg = `\
|
|
||||||
Renaming notice: The 'app' command was renamed to 'fleet', and 'app'
|
|
||||||
is now an alias. THE ALIAS WILL BE REMOVED in the next major version
|
|
||||||
of the balena CLI (so that a different 'app' command can be implemented
|
|
||||||
in the future). Use 'fleet' instead of 'app' to avoid this warning.
|
|
||||||
Find out more at: https://git.io/JRuZr`;
|
|
||||||
|
|
||||||
export const appToFleetFlagMsg = `\
|
|
||||||
Renaming notice: The '-a', '--app' or '--application' options are now
|
|
||||||
aliases for the '-f' or '--fleet' options. THE ALIASES WILL BE REMOVED
|
|
||||||
in the next major version of the balena CLI (so that a different '--app'
|
|
||||||
option can be implemented in the future). Use '-f' or '--fleet' instead.
|
|
||||||
Find out more at: https://git.io/JRuZr`;
|
|
||||||
|
|
||||||
export const appToFleetOutputMsg = `\
|
|
||||||
Renaming notice: The 'app' or 'application' words in table headers
|
|
||||||
or in JSON object keys/properties will be replaced with 'fleet' in
|
|
||||||
the next major version of the CLI (v13). The --v13 option may be used
|
|
||||||
to enable the new names already now, and suppress a warning message.
|
|
||||||
(The --v13 option will be silently ignored in CLI v13.)
|
|
||||||
Find out more at: https://git.io/JRuZr`;
|
|
||||||
|
|
||||||
export function getNodeEngineVersionWarn(
|
export function getNodeEngineVersionWarn(
|
||||||
version: string,
|
version: string,
|
||||||
validVersions: string,
|
validVersions: string,
|
||||||
|
@ -13,15 +13,14 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
import type * as BalenaSdk from 'balena-sdk';
|
|
||||||
|
import type { Application, BalenaSDK, Device, Organization } from 'balena-sdk';
|
||||||
import _ = require('lodash');
|
import _ = require('lodash');
|
||||||
|
|
||||||
import { instanceOf, NotLoggedInError, ExpectedError } from '../errors';
|
import { instanceOf, NotLoggedInError, ExpectedError } from '../errors';
|
||||||
import { getBalenaSdk, getVisuals, stripIndent, getCliForm } from './lazy';
|
import { getBalenaSdk, getVisuals, stripIndent, getCliForm } from './lazy';
|
||||||
import validation = require('./validation');
|
import validation = require('./validation');
|
||||||
import { delay } from './helpers';
|
import { delay } from './helpers';
|
||||||
import { isV13 } from './version';
|
|
||||||
import type { Application, Device, Organization } from 'balena-sdk';
|
|
||||||
|
|
||||||
export function authenticate(options: {}): Promise<void> {
|
export function authenticate(options: {}): Promise<void> {
|
||||||
const balena = getBalenaSdk();
|
const balena = getBalenaSdk();
|
||||||
@ -142,11 +141,7 @@ export async function confirm(
|
|||||||
) {
|
) {
|
||||||
if (yesOption) {
|
if (yesOption) {
|
||||||
if (yesMessage) {
|
if (yesMessage) {
|
||||||
if (isV13()) {
|
console.log(yesMessage);
|
||||||
console.error(yesMessage);
|
|
||||||
} else {
|
|
||||||
console.log(yesMessage);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -174,7 +169,7 @@ export function selectApplication(
|
|||||||
throw new ExpectedError('No fleets found');
|
throw new ExpectedError('No fleets found');
|
||||||
}
|
}
|
||||||
|
|
||||||
const apps = (await balena.models.application.getAll({
|
const apps = (await balena.models.application.getAllDirectlyAccessible({
|
||||||
$expand: {
|
$expand: {
|
||||||
is_for__device_type: {
|
is_for__device_type: {
|
||||||
$select: 'slug',
|
$select: 'slug',
|
||||||
@ -212,35 +207,6 @@ export async function selectOrganization(organizations?: Organization[]) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function awaitDevice(uuid: string) {
|
|
||||||
const balena = getBalenaSdk();
|
|
||||||
const deviceName = await balena.models.device.getName(uuid);
|
|
||||||
const visuals = getVisuals();
|
|
||||||
const spinner = new visuals.Spinner(
|
|
||||||
`Waiting for ${deviceName} to come online`,
|
|
||||||
);
|
|
||||||
|
|
||||||
const poll = async (): Promise<void> => {
|
|
||||||
const isOnline = await balena.models.device.isOnline(uuid);
|
|
||||||
if (isOnline) {
|
|
||||||
spinner.stop();
|
|
||||||
console.info(`The device **${deviceName}** is online!`);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
// Spinner implementation is smart enough to
|
|
||||||
// not start again if it was already started
|
|
||||||
spinner.start();
|
|
||||||
|
|
||||||
await delay(3000);
|
|
||||||
await poll();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
console.info(`Waiting for ${deviceName} to connect to balena...`);
|
|
||||||
await poll();
|
|
||||||
return uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function awaitDeviceOsUpdate(
|
export async function awaitDeviceOsUpdate(
|
||||||
uuid: string,
|
uuid: string,
|
||||||
targetOsVersion: string,
|
targetOsVersion: string,
|
||||||
@ -326,7 +292,7 @@ export function inferOrSelectDevice(preferredUuid: string) {
|
|||||||
* TODO: Modify this when app IDs dropped.
|
* TODO: Modify this when app IDs dropped.
|
||||||
*/
|
*/
|
||||||
export async function getOnlineTargetDeviceUuid(
|
export async function getOnlineTargetDeviceUuid(
|
||||||
sdk: BalenaSdk.BalenaSDK,
|
sdk: BalenaSDK,
|
||||||
applicationOrDevice: string,
|
applicationOrDevice: string,
|
||||||
) {
|
) {
|
||||||
const logger = (await import('../utils/logger')).getLogger();
|
const logger = (await import('../utils/logger')).getLogger();
|
||||||
|
@ -235,7 +235,7 @@ async function getOrSelectApplication(
|
|||||||
name = appName;
|
name = appName;
|
||||||
}
|
}
|
||||||
|
|
||||||
const applications = (await sdk.models.application.getAll(
|
const applications = (await sdk.models.application.getAllDirectlyAccessible(
|
||||||
options,
|
options,
|
||||||
)) as ApplicationWithDeviceType[];
|
)) as ApplicationWithDeviceType[];
|
||||||
|
|
||||||
@ -273,7 +273,7 @@ async function createOrSelectApp(
|
|||||||
deviceType: string,
|
deviceType: string,
|
||||||
): Promise<ApplicationWithDeviceType> {
|
): Promise<ApplicationWithDeviceType> {
|
||||||
// No fleet specified, show a list to select one.
|
// No fleet specified, show a list to select one.
|
||||||
const applications = (await sdk.models.application.getAll({
|
const applications = (await sdk.models.application.getAllDirectlyAccessible({
|
||||||
$expand: { is_for__device_type: { $select: 'slug' } },
|
$expand: { is_for__device_type: { $select: 'slug' } },
|
||||||
$filter: {
|
$filter: {
|
||||||
is_for__device_type: {
|
is_for__device_type: {
|
||||||
@ -322,10 +322,13 @@ async function createApplication(
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await sdk.models.application.get(appName, {
|
await sdk.models.application.getDirectlyAccessible(appName, {
|
||||||
$filter: {
|
$filter: {
|
||||||
$or: [
|
$or: [
|
||||||
{ slug: { $startswith: `${username!.toLowerCase()}/` } },
|
{ slug: { $startswith: `${username!.toLowerCase()}/` } },
|
||||||
|
// TODO: do we still need the following filter? Is it for
|
||||||
|
// old openBalena instances where slugs were equal to the
|
||||||
|
// app name and did not contain the slash character?
|
||||||
{ $not: { slug: { $contains: '/' } } },
|
{ $not: { slug: { $contains: '/' } } },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -50,7 +50,6 @@ export interface RemoteBuild {
|
|||||||
source: string;
|
source: string;
|
||||||
auth: string;
|
auth: string;
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
nogitignore: boolean; // v13: delete this line
|
|
||||||
opts: BuildOpts;
|
opts: BuildOpts;
|
||||||
sdk: BalenaSDK;
|
sdk: BalenaSDK;
|
||||||
// For internal use
|
// For internal use
|
||||||
@ -213,7 +212,7 @@ function handleBuilderMetadata(obj: BuilderMessage, build: RemoteBuild) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const value = match[1];
|
const value = match[1];
|
||||||
const amount = match[2] || 1;
|
const amount = Number(match[2]) || 1;
|
||||||
|
|
||||||
switch (value) {
|
switch (value) {
|
||||||
case 'erase':
|
case 'erase':
|
||||||
@ -323,7 +322,6 @@ async function getTarStream(build: RemoteBuild): Promise<Stream.Readable> {
|
|||||||
preFinalizeCallback: preFinalizeCb,
|
preFinalizeCallback: preFinalizeCb,
|
||||||
convertEol: build.opts.convertEol,
|
convertEol: build.opts.convertEol,
|
||||||
multiDockerignore: build.opts.multiDockerignore,
|
multiDockerignore: build.opts.multiDockerignore,
|
||||||
nogitignore: build.nogitignore, // v13: delete this line
|
|
||||||
});
|
});
|
||||||
globalLogger.logDebug(
|
globalLogger.logDebug(
|
||||||
`Tarring complete in ${Date.now() - tarStartTime} ms`,
|
`Tarring complete in ${Date.now() - tarStartTime} ms`,
|
||||||
@ -406,7 +404,7 @@ async function getRemoteBuildStream(
|
|||||||
if (process.stdout.isTTY) {
|
if (process.stdout.isTTY) {
|
||||||
const visuals = getVisuals();
|
const visuals = getVisuals();
|
||||||
uploadSpinner = new visuals.Spinner(
|
uploadSpinner = new visuals.Spinner(
|
||||||
'Uploading source package to balenaCloud',
|
`Uploading source package to ${new URL(builderUrl).origin}`,
|
||||||
);
|
);
|
||||||
(uploadSpinner as any).start();
|
(uploadSpinner as any).start();
|
||||||
}
|
}
|
||||||
|
@ -23,21 +23,36 @@ import type {
|
|||||||
} from 'balena-sdk';
|
} from 'balena-sdk';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wraps the sdk application.get method,
|
* Get a fleet object, disambiguating the fleet identifier which may be a
|
||||||
* adding disambiguation in cases where the provided
|
* a fleet slug, name or numeric database ID (as a string).
|
||||||
* identifier could be interpreted in multiple valid ways.
|
* TODO: add support for fleet UUIDs.
|
||||||
*/
|
*/
|
||||||
export async function getApplication(
|
export async function getApplication(
|
||||||
sdk: BalenaSDK,
|
sdk: BalenaSDK,
|
||||||
nameOrSlugOrId: string | number,
|
nameOrSlugOrId: string | number,
|
||||||
options?: PineOptions<Application>,
|
options?: PineOptions<Application>,
|
||||||
): Promise<Application> {
|
): Promise<Application> {
|
||||||
const { looksLikeInteger } = await import('./validation');
|
const { looksLikeFleetSlug, looksLikeInteger } = await import('./validation');
|
||||||
if (looksLikeInteger(nameOrSlugOrId as string)) {
|
if (
|
||||||
|
typeof nameOrSlugOrId === 'string' &&
|
||||||
|
looksLikeFleetSlug(nameOrSlugOrId)
|
||||||
|
) {
|
||||||
|
return await sdk.models.application.getDirectlyAccessible(
|
||||||
|
nameOrSlugOrId,
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (typeof nameOrSlugOrId === 'number' || looksLikeInteger(nameOrSlugOrId)) {
|
||||||
try {
|
try {
|
||||||
// Test for existence of app with this numerical ID
|
// Test for existence of app with this numerical ID
|
||||||
return await sdk.models.application.get(Number(nameOrSlugOrId), options);
|
return await sdk.models.application.getDirectlyAccessible(
|
||||||
|
Number(nameOrSlugOrId),
|
||||||
|
options,
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
if (typeof nameOrSlugOrId === 'number') {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
const { instanceOf } = await import('../errors');
|
const { instanceOf } = await import('../errors');
|
||||||
const { BalenaApplicationNotFound } = await import('balena-errors');
|
const { BalenaApplicationNotFound } = await import('balena-errors');
|
||||||
if (!instanceOf(e, BalenaApplicationNotFound)) {
|
if (!instanceOf(e, BalenaApplicationNotFound)) {
|
||||||
@ -46,47 +61,34 @@ export async function getApplication(
|
|||||||
// App with this numerical ID not found, but there may be an app with this numerical name.
|
// App with this numerical ID not found, but there may be an app with this numerical name.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return sdk.models.application.get(nameOrSlugOrId, options);
|
// Not a slug and not a numeric database ID: must be an app name.
|
||||||
|
// TODO: revisit this logic when we add support for fleet UUIDs.
|
||||||
|
return await sdk.models.application.getAppByName(
|
||||||
|
nameOrSlugOrId,
|
||||||
|
options,
|
||||||
|
'directly_accessible',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given an string representation of an application identifier,
|
* Given a fleet name, slug or numeric database ID, return its slug.
|
||||||
* which could be one of:
|
* This function conditionally makes an async SDK/API call to retrieve the
|
||||||
* - name (including numeric names)
|
* application object, which can be wasteful if the application object is
|
||||||
* - slug
|
* required before or after the call to this function. If this is the case,
|
||||||
* - numerical id
|
* consider calling `getApplication()` and reusing the application object.
|
||||||
* disambiguate and return a properly typed identifier.
|
|
||||||
*
|
|
||||||
* Attempts to minimise the number of API calls required.
|
|
||||||
* TODO: Remove this once support for numeric App IDs is removed.
|
|
||||||
*/
|
*/
|
||||||
export async function getTypedApplicationIdentifier(
|
export async function getFleetSlug(
|
||||||
sdk: BalenaSDK,
|
sdk: BalenaSDK,
|
||||||
nameOrSlugOrId: string,
|
nameOrSlugOrId: string | number,
|
||||||
) {
|
): Promise<string> {
|
||||||
const { looksLikeInteger } = await import('./validation');
|
const { looksLikeFleetSlug } = await import('./validation');
|
||||||
// If there's no possible ambiguity,
|
if (
|
||||||
// return the passed identifier unchanged
|
typeof nameOrSlugOrId === 'string' &&
|
||||||
if (!looksLikeInteger(nameOrSlugOrId)) {
|
looksLikeFleetSlug(nameOrSlugOrId)
|
||||||
return nameOrSlugOrId;
|
) {
|
||||||
|
return nameOrSlugOrId.toLowerCase();
|
||||||
}
|
}
|
||||||
|
return (await getApplication(sdk, nameOrSlugOrId)).slug;
|
||||||
// Resolve ambiguity
|
|
||||||
try {
|
|
||||||
// Test for existence of app with this numerical ID,
|
|
||||||
// and return typed id if found
|
|
||||||
return (await sdk.models.application.get(Number(nameOrSlugOrId))).id;
|
|
||||||
} catch (e) {
|
|
||||||
const { instanceOf } = await import('../errors');
|
|
||||||
const { BalenaApplicationNotFound } = await import('balena-errors');
|
|
||||||
if (!instanceOf(e, BalenaApplicationNotFound)) {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// App with this numerical id not found
|
|
||||||
// return the passed identifier unchanged
|
|
||||||
return nameOrSlugOrId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -77,7 +77,7 @@ export async function exec(
|
|||||||
.on('error', reject)
|
.on('error', reject)
|
||||||
.on('close', resolve);
|
.on('close', resolve);
|
||||||
|
|
||||||
if (stdout) {
|
if (stdout && ps.stdout) {
|
||||||
ps.stdout.pipe(stdout);
|
ps.stdout.pipe(stdout);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -98,7 +98,7 @@ async function spawnAndPipe(
|
|||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (stderr) {
|
if (stderr && ps.stderr) {
|
||||||
ps.stderr.pipe(stderr);
|
ps.stderr.pipe(stderr);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -118,7 +118,6 @@ export function parseAsLocalHostnameOrIp(input: string, paramName?: string) {
|
|||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function looksLikeAppSlug(input: string) {
|
export function looksLikeFleetSlug(input: string) {
|
||||||
// One or more non whitespace chars, /, 4 or more non whitespace chars
|
return input.includes('/');
|
||||||
return /[\S]+\/[\S]{4,}/.test(input);
|
|
||||||
}
|
}
|
||||||
|
@ -22,12 +22,12 @@ export function isVersionGTE(v: string): boolean {
|
|||||||
return semver.gte(process.env.BALENA_CLI_VERSION_OVERRIDE || version, v);
|
return semver.gte(process.env.BALENA_CLI_VERSION_OVERRIDE || version, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
let v13: boolean;
|
let v14: boolean;
|
||||||
|
|
||||||
/** Feature switch for the next major version of the CLI */
|
/** Feature switch for the next major version of the CLI */
|
||||||
export function isV13(): boolean {
|
export function isV14(): boolean {
|
||||||
if (v13 === undefined) {
|
if (v14 === undefined) {
|
||||||
v13 = isVersionGTE('13.0.0');
|
v14 = isVersionGTE('14.0.0');
|
||||||
}
|
}
|
||||||
return v13;
|
return v14;
|
||||||
}
|
}
|
||||||
|
6775
npm-shrinkwrap.json
generated
6775
npm-shrinkwrap.json
generated
File diff suppressed because it is too large
Load Diff
127
package.json
127
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "balena-cli",
|
"name": "balena-cli",
|
||||||
"version": "12.55.5",
|
"version": "13.1.13",
|
||||||
"description": "The official balena Command Line Interface",
|
"description": "The official balena Command Line Interface",
|
||||||
"main": "./build/app.js",
|
"main": "./build/app.js",
|
||||||
"homepage": "https://github.com/balena-io/balena-cli",
|
"homepage": "https://github.com/balena-io/balena-cli",
|
||||||
@ -51,8 +51,9 @@
|
|||||||
"build": "npm run build:src && npm run catch-uncommitted",
|
"build": "npm run build:src && npm run catch-uncommitted",
|
||||||
"build:t": "npm run lint && npm run build:fast && npm run build:test",
|
"build:t": "npm run lint && npm run build:fast && npm run build:test",
|
||||||
"build:src": "npm run lint && npm run build:fast && npm run build:test && npm run build:doc && npm run build:completion",
|
"build:src": "npm run lint && npm run build:fast && npm run build:test && npm run build:doc && npm run build:completion",
|
||||||
"build:fast": "gulp pages && tsc && npx oclif-dev manifest",
|
"build:pages": "mkdirp ./build/auth/pages/&& inline-source --compress ./lib/auth/pages/error.ejs ./build/auth/pages/error.ejs && inline-source --compress ./lib/auth/pages/success.ejs ./build/auth/pages/success.ejs",
|
||||||
"build:test": "tsc -P ./tsconfig.dev.json --noEmit && tsc -P ./tsconfig.js.json --noEmit",
|
"build:fast": "npm run build:pages && tsc && npx oclif manifest",
|
||||||
|
"build:test": "tsc -P ./tsconfig.dev.json --noEmit",
|
||||||
"build:doc": "ts-node --transpile-only automation/capitanodoc/index.ts > docs/balena-cli.md",
|
"build:doc": "ts-node --transpile-only automation/capitanodoc/index.ts > docs/balena-cli.md",
|
||||||
"build:completion": "node completion/generate-completion.js",
|
"build:completion": "node completion/generate-completion.js",
|
||||||
"build:standalone": "ts-node --transpile-only automation/run.ts build:standalone",
|
"build:standalone": "ts-node --transpile-only automation/run.ts build:standalone",
|
||||||
@ -70,10 +71,9 @@
|
|||||||
"test:only": "npm run build:fast && cross-env BALENA_CLI_TEST_TYPE=source mocha \"tests/**/${npm_config_test}.spec.ts\"",
|
"test:only": "npm run build:fast && cross-env BALENA_CLI_TEST_TYPE=source mocha \"tests/**/${npm_config_test}.spec.ts\"",
|
||||||
"catch-uncommitted": "ts-node --transpile-only automation/run.ts catch-uncommitted",
|
"catch-uncommitted": "ts-node --transpile-only automation/run.ts catch-uncommitted",
|
||||||
"ci": "npm run test && npm run catch-uncommitted",
|
"ci": "npm run test && npm run catch-uncommitted",
|
||||||
"watch": "gulp watch",
|
|
||||||
"lint": "npm run lint-tsconfig && npm run lint-other",
|
"lint": "npm run lint-tsconfig && npm run lint-other",
|
||||||
"lint-tsconfig": "balena-lint -e ts -e js -t tsconfig.dev.json --fix automation/ lib/ tests/ typings/",
|
"lint-tsconfig": "balena-lint -e ts -e js -t tsconfig.dev.json --fix automation/ lib/ tests/ typings/",
|
||||||
"lint-other": "balena-lint -e ts -e js --fix bin/balena bin/balena-dev completion/ gulpfile.js .mocharc.js .mocharc-standalone.js",
|
"lint-other": "balena-lint -e ts -e js --fix bin/balena bin/balena-dev completion/ .mocharc.js .mocharc-standalone.js",
|
||||||
"update": "ts-node --transpile-only ./automation/update-module.ts",
|
"update": "ts-node --transpile-only ./automation/update-module.ts",
|
||||||
"prepare": "echo {} > bin/.fast-boot.json",
|
"prepare": "echo {} > bin/.fast-boot.json",
|
||||||
"prepublishOnly": "npm run build"
|
"prepublishOnly": "npm run build"
|
||||||
@ -90,7 +90,7 @@
|
|||||||
"author": "Balena Inc. (https://balena.io/)",
|
"author": "Balena Inc. (https://balena.io/)",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.20.0 <13.0.0",
|
"node": ">=12.8.0 <13.0.0",
|
||||||
"npm": "<7.0.0"
|
"npm": "<7.0.0"
|
||||||
},
|
},
|
||||||
"husky": {
|
"husky": {
|
||||||
@ -116,53 +116,52 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@balena/lint": "^6.2.0",
|
"@balena/lint": "^6.2.0",
|
||||||
"@oclif/config": "^1.17.0",
|
"@oclif/config": "^1.18.2",
|
||||||
"@oclif/dev-cli": "^1.26.0",
|
"@oclif/parser": "^3.8.6",
|
||||||
"@oclif/parser": "^3.8.5",
|
|
||||||
"@octokit/plugin-throttling": "^3.5.1",
|
"@octokit/plugin-throttling": "^3.5.1",
|
||||||
"@octokit/rest": "^18.6.7",
|
"@octokit/rest": "^18.6.7",
|
||||||
"@types/archiver": "^5.1.1",
|
"@types/archiver": "^5.1.1",
|
||||||
"@types/bluebird": "^3.5.36",
|
"@types/bluebird": "^3.5.36",
|
||||||
"@types/body-parser": "^1.19.1",
|
"@types/body-parser": "^1.19.2",
|
||||||
"@types/chai": "^4.2.21",
|
"@types/chai": "^4.3.0",
|
||||||
"@types/chai-as-promised": "^7.1.4",
|
"@types/chai-as-promised": "^7.1.4",
|
||||||
"@types/cli-truncate": "^2.0.0",
|
"@types/cli-truncate": "^2.0.0",
|
||||||
"@types/common-tags": "^1.8.1",
|
"@types/common-tags": "^1.8.1",
|
||||||
"@types/dockerode": "^3.2.4",
|
"@types/dockerode": "^3.3.0",
|
||||||
"@types/ejs": "^3.0.7",
|
"@types/ejs": "^3.1.0",
|
||||||
"@types/express": "^4.17.13",
|
"@types/express": "^4.17.13",
|
||||||
"@types/fs-extra": "^9.0.12",
|
"@types/fs-extra": "^9.0.13",
|
||||||
"@types/global-agent": "^2.1.1",
|
"@types/global-agent": "^2.1.1",
|
||||||
"@types/global-tunnel-ng": "^2.1.1",
|
"@types/global-tunnel-ng": "^2.1.1",
|
||||||
"@types/http-proxy": "^1.17.7",
|
"@types/http-proxy": "^1.17.8",
|
||||||
"@types/intercept-stdout": "^0.1.0",
|
"@types/intercept-stdout": "^0.1.0",
|
||||||
"@types/is-root": "^2.1.2",
|
"@types/is-root": "^2.1.2",
|
||||||
"@types/js-yaml": "^4.0.2",
|
"@types/js-yaml": "^4.0.5",
|
||||||
"@types/jsonwebtoken": "^8.5.4",
|
"@types/jsonwebtoken": "^8.5.6",
|
||||||
"@types/klaw": "^3.0.2",
|
"@types/klaw": "^3.0.3",
|
||||||
"@types/lodash": "^4.14.171",
|
"@types/lodash": "^4.14.178",
|
||||||
"@types/mixpanel": "^2.14.3",
|
"@types/mixpanel": "^2.14.3",
|
||||||
"@types/mocha": "^8.2.3",
|
"@types/mocha": "^8.2.3",
|
||||||
"@types/mock-require": "^2.0.0",
|
"@types/mock-require": "^2.0.1",
|
||||||
"@types/moment-duration-format": "^2.2.3",
|
"@types/moment-duration-format": "^2.2.3",
|
||||||
"@types/ndjson": "^2.0.1",
|
"@types/ndjson": "^2.0.1",
|
||||||
"@types/net-keepalive": "^0.4.1",
|
"@types/net-keepalive": "^0.4.1",
|
||||||
"@types/nock": "^11.1.0",
|
"@types/nock": "^11.1.0",
|
||||||
"@types/node": "^10.17.60",
|
"@types/node": "^12.20.42",
|
||||||
"@types/node-cleanup": "^2.1.1",
|
"@types/node-cleanup": "^2.1.2",
|
||||||
"@types/parse-link-header": "^1.0.0",
|
"@types/parse-link-header": "^1.0.1",
|
||||||
"@types/prettyjson": "^0.0.30",
|
"@types/prettyjson": "^0.0.30",
|
||||||
"@types/progress-stream": "^2.0.2",
|
"@types/progress-stream": "^2.0.2",
|
||||||
"@types/request": "^2.48.6",
|
"@types/request": "^2.48.7",
|
||||||
"@types/rewire": "^2.5.28",
|
"@types/rewire": "^2.5.28",
|
||||||
"@types/rimraf": "^3.0.1",
|
"@types/rimraf": "^3.0.2",
|
||||||
"@types/shell-escape": "^0.2.0",
|
"@types/shell-escape": "^0.2.0",
|
||||||
"@types/sinon": "^10.0.2",
|
"@types/sinon": "^10.0.6",
|
||||||
"@types/split": "^1.0.0",
|
"@types/split": "^1.0.0",
|
||||||
"@types/stream-to-promise": "^2.2.1",
|
"@types/stream-to-promise": "^2.2.1",
|
||||||
"@types/tar-stream": "^2.2.1",
|
"@types/tar-stream": "^2.2.2",
|
||||||
"@types/through2": "^2.0.36",
|
"@types/through2": "^2.0.36",
|
||||||
"@types/tmp": "^0.2.1",
|
"@types/tmp": "^0.2.3",
|
||||||
"@types/which": "^2.0.1",
|
"@types/which": "^2.0.1",
|
||||||
"archiver": "^5.3.0",
|
"archiver": "^5.3.0",
|
||||||
"catch-uncommitted": "^2.0.0",
|
"catch-uncommitted": "^2.0.0",
|
||||||
@ -173,116 +172,114 @@
|
|||||||
"diff": "^5.0.0",
|
"diff": "^5.0.0",
|
||||||
"electron-notarize": "^1.0.0",
|
"electron-notarize": "^1.0.0",
|
||||||
"ent": "^2.2.0",
|
"ent": "^2.2.0",
|
||||||
"filehound": "^1.17.4",
|
"filehound": "^1.17.5",
|
||||||
"fs-extra": "^9.1.0",
|
"fs-extra": "^9.1.0",
|
||||||
"gulp": "^4.0.2",
|
|
||||||
"gulp-inline-source": "^4.0.0",
|
|
||||||
"http-proxy": "^1.18.1",
|
"http-proxy": "^1.18.1",
|
||||||
"husky": "^4.3.8",
|
"husky": "^4.3.8",
|
||||||
|
"inline-source-cli": "^2.0.0",
|
||||||
"intercept-stdout": "^0.1.2",
|
"intercept-stdout": "^0.1.2",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
|
"mkdirp": "^1.0.4",
|
||||||
"mocha": "^8.4.0",
|
"mocha": "^8.4.0",
|
||||||
"mock-require": "^3.0.3",
|
"mock-require": "^3.0.3",
|
||||||
"nock": "^13.1.1",
|
"nock": "^13.2.1",
|
||||||
"parse-link-header": "^1.0.1",
|
"parse-link-header": "^1.0.1",
|
||||||
"pkg": "^4.4.9",
|
"pkg": "^5.5.1",
|
||||||
"publish-release": "^1.6.1",
|
"publish-release": "^1.6.1",
|
||||||
"rewire": "^5.0.0",
|
"rewire": "^5.0.0",
|
||||||
"simple-git": "^2.40.0",
|
"simple-git": "^2.48.0",
|
||||||
"sinon": "^11.1.2",
|
"sinon": "^11.1.2",
|
||||||
"ts-node": "^10.0.0",
|
"ts-node": "^10.4.0",
|
||||||
"typescript": "^4.3.5"
|
"typescript": "^4.5.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@balena/dockerignore": "^1.0.2",
|
"@balena/dockerignore": "^1.0.2",
|
||||||
"@balena/es-version": "^1.0.0",
|
"@balena/es-version": "^1.0.1",
|
||||||
"@oclif/command": "^1.8.0",
|
"@oclif/command": "^1.8.16",
|
||||||
"@resin.io/valid-email": "^0.1.0",
|
"@resin.io/valid-email": "^0.1.0",
|
||||||
"@sentry/node": "^6.13.2",
|
"@sentry/node": "^6.16.1",
|
||||||
"@types/fast-levenshtein": "0.0.1",
|
"@types/fast-levenshtein": "0.0.1",
|
||||||
"@types/update-notifier": "^4.1.1",
|
"@types/update-notifier": "^4.1.1",
|
||||||
"@zeit/dockerignore": "0.0.3",
|
|
||||||
"JSONStream": "^1.0.3",
|
"JSONStream": "^1.0.3",
|
||||||
"balena-config-json": "^4.2.0",
|
"balena-config-json": "^4.2.0",
|
||||||
"balena-device-init": "^6.0.0",
|
"balena-device-init": "^6.0.0",
|
||||||
"balena-errors": "^4.7.1",
|
"balena-errors": "^4.7.1",
|
||||||
"balena-image-fs": "^7.0.6",
|
"balena-image-fs": "^7.0.6",
|
||||||
"balena-image-manager": "^7.1.1",
|
"balena-image-manager": "^7.1.1",
|
||||||
"balena-preload": "^11.0.0",
|
"balena-preload": "^12.0.0",
|
||||||
"balena-release": "^3.2.0",
|
"balena-release": "^3.2.0",
|
||||||
"balena-sdk": "^15.51.1",
|
"balena-sdk": "^16.9.0",
|
||||||
"balena-semver": "^2.3.0",
|
"balena-semver": "^2.3.0",
|
||||||
"balena-settings-client": "^4.0.7",
|
"balena-settings-client": "^4.0.7",
|
||||||
"balena-settings-storage": "^7.0.0",
|
"balena-settings-storage": "^7.0.0",
|
||||||
"balena-sync": "^11.0.2",
|
"balena-sync": "^11.0.2",
|
||||||
"bluebird": "^3.7.2",
|
"bluebird": "^3.7.2",
|
||||||
"body-parser": "^1.19.0",
|
"body-parser": "^1.19.1",
|
||||||
"chalk": "^3.0.0",
|
"chalk": "^3.0.0",
|
||||||
"chokidar": "^3.4.3",
|
"chokidar": "^3.5.2",
|
||||||
"cli-truncate": "^2.1.0",
|
"cli-truncate": "^2.1.0",
|
||||||
"cli-ux": "^5.5.1",
|
"color-hash": "^1.1.1",
|
||||||
"color-hash": "^1.0.3",
|
"cli-ux": "^6.0.5",
|
||||||
"columnify": "^1.5.2",
|
"columnify": "^1.5.2",
|
||||||
"common-tags": "^1.7.2",
|
"common-tags": "^1.7.2",
|
||||||
"denymount": "^2.3.0",
|
"denymount": "^2.3.0",
|
||||||
"docker-modem": "^3.0.0",
|
"docker-modem": "3.0.0",
|
||||||
"docker-progress": "^5.0.0",
|
"docker-progress": "^5.0.1",
|
||||||
"docker-qemu-transpose": "^1.1.1",
|
"docker-qemu-transpose": "^1.1.1",
|
||||||
"dockerode": "^3.3.0",
|
"dockerode": "^3.3.1",
|
||||||
"ejs": "^3.1.3",
|
"ejs": "^3.1.6",
|
||||||
"etcher-sdk": "^6.2.1",
|
"etcher-sdk": "^6.2.1",
|
||||||
"event-stream": "3.3.4",
|
"event-stream": "3.3.4",
|
||||||
"express": "^4.13.3",
|
"express": "^4.17.2",
|
||||||
"fast-boot2": "^1.1.0",
|
"fast-boot2": "^1.1.0",
|
||||||
"fast-levenshtein": "^3.0.0",
|
"fast-levenshtein": "^3.0.0",
|
||||||
"filenamify": "^4.3.0",
|
"filenamify": "^4.3.0",
|
||||||
"get-stdin": "^8.0.0",
|
"get-stdin": "^8.0.0",
|
||||||
"glob": "^7.1.7",
|
"glob": "^7.2.0",
|
||||||
"global-agent": "^2.1.12",
|
"global-agent": "^2.2.0",
|
||||||
"global-tunnel-ng": "^2.1.1",
|
"global-tunnel-ng": "^2.1.1",
|
||||||
"got": "^11.8.2",
|
"got": "^11.8.3",
|
||||||
"humanize": "0.0.9",
|
"humanize": "0.0.9",
|
||||||
"ignore": "^5.1.8",
|
|
||||||
"inquirer": "^7.3.3",
|
"inquirer": "^7.3.3",
|
||||||
"is-elevated": "^3.0.0",
|
"is-elevated": "^3.0.0",
|
||||||
"is-root": "^2.1.0",
|
"is-root": "^2.1.0",
|
||||||
"js-yaml": "^4.0.0",
|
"js-yaml": "^4.1.0",
|
||||||
"klaw": "^3.0.0",
|
"klaw": "^3.0.0",
|
||||||
"livepush": "^3.5.0",
|
"livepush": "^3.5.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"minimatch": "^3.0.4",
|
"minimatch": "^3.0.4",
|
||||||
"moment": "^2.27.0",
|
"moment": "^2.29.1",
|
||||||
"moment-duration-format": "^2.3.2",
|
"moment-duration-format": "^2.3.2",
|
||||||
"ndjson": "^2.0.0",
|
"ndjson": "^2.0.0",
|
||||||
"net-keepalive": "^3.0.0",
|
"net-keepalive": "^3.0.0",
|
||||||
"node-cleanup": "^2.1.2",
|
"node-cleanup": "^2.1.2",
|
||||||
"node-unzip-2": "^0.2.8",
|
"node-unzip-2": "^0.2.8",
|
||||||
"oclif": "^1.18.1",
|
"oclif": "^1.18.4",
|
||||||
"open": "^7.1.0",
|
"open": "^7.1.0",
|
||||||
"patch-package": "^6.4.7",
|
"patch-package": "^6.4.7",
|
||||||
"prettyjson": "^1.1.3",
|
"prettyjson": "^1.2.5",
|
||||||
"progress-stream": "^2.0.0",
|
"progress-stream": "^2.0.0",
|
||||||
"reconfix": "^1.0.0-v0-1-0-fork-46760acff4d165f5238bfac5e464256ef1944476",
|
"reconfix": "^1.0.0-v0-1-0-fork-46760acff4d165f5238bfac5e464256ef1944476",
|
||||||
"request": "^2.88.2",
|
"request": "^2.88.2",
|
||||||
"resin-cli-form": "^2.0.2",
|
"resin-cli-form": "^2.0.2",
|
||||||
"resin-cli-visuals": "^1.8.0",
|
"resin-cli-visuals": "^1.8.0",
|
||||||
"resin-compose-parse": "^2.1.3",
|
"resin-compose-parse": "^2.1.3",
|
||||||
"resin-doodles": "^0.1.1",
|
"resin-doodles": "^0.2.0",
|
||||||
"resin-multibuild": "^4.12.2",
|
"resin-multibuild": "^4.12.2",
|
||||||
"resin-stream-logger": "^0.1.2",
|
"resin-stream-logger": "^0.1.2",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"semver": "^7.3.2",
|
"semver": "^7.3.5",
|
||||||
"shell-escape": "^0.2.0",
|
"shell-escape": "^0.2.0",
|
||||||
"split": "^1.0.1",
|
"split": "^1.0.1",
|
||||||
"stream-to-promise": "^2.2.0",
|
"stream-to-promise": "^2.2.0",
|
||||||
"string-width": "^4.2.0",
|
"string-width": "^4.2.3",
|
||||||
"strip-ansi-stream": "^1.0.0",
|
"strip-ansi-stream": "^1.0.0",
|
||||||
"tar-stream": "^2.1.3",
|
"tar-stream": "^2.1.3",
|
||||||
"tar-utils": "^2.1.1",
|
"tar-utils": "^2.1.1",
|
||||||
"through2": "^2.0.3",
|
"through2": "^2.0.3",
|
||||||
"tmp": "^0.2.1",
|
"tmp": "^0.2.1",
|
||||||
"typed-error": "^3.2.1",
|
"typed-error": "^3.2.1",
|
||||||
"update-notifier": "^4.1.0",
|
"update-notifier": "^5.1.0",
|
||||||
"which": "^2.0.2",
|
"which": "^2.0.2",
|
||||||
"window-size": "^1.1.0"
|
"window-size": "^1.1.0"
|
||||||
},
|
},
|
||||||
@ -290,6 +287,6 @@
|
|||||||
"windosu": "^0.3.0"
|
"windosu": "^0.3.0"
|
||||||
},
|
},
|
||||||
"versionist": {
|
"versionist": {
|
||||||
"publishedAt": "2021-12-13T23:29:59.373Z"
|
"publishedAt": "2022-02-10T11:50:34.458Z"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
diff --git a/node_modules/@oclif/plugin-help/lib/command.js b/node_modules/@oclif/plugin-help/lib/command.js
|
diff --git a/node_modules/@oclif/plugin-help/lib/command.js b/node_modules/@oclif/plugin-help/lib/command.js
|
||||||
index 1caa65b..e205faf 100644
|
index b3b9010..788e5c6 100644
|
||||||
--- a/node_modules/@oclif/plugin-help/lib/command.js
|
--- a/node_modules/@oclif/plugin-help/lib/command.js
|
||||||
+++ b/node_modules/@oclif/plugin-help/lib/command.js
|
+++ b/node_modules/@oclif/plugin-help/lib/command.js
|
||||||
@@ -87,7 +87,7 @@ class CommandHelp {
|
@@ -88,7 +88,7 @@ class CommandHelp {
|
||||||
if (args.filter(a => a.description).length === 0)
|
|
||||||
return;
|
return;
|
||||||
const body = list_1.renderList(args.map(a => {
|
const body = list_1.renderList(args.map(a => {
|
||||||
|
var _a;
|
||||||
- const name = a.name.toUpperCase();
|
- const name = a.name.toUpperCase();
|
||||||
+ const name = a.required ? `<${a.name}>` : `[${a.name}]`;
|
+ const name = a.required ? `<${a.name}>` : `[${a.name}]`;
|
||||||
let description = a.description || '';
|
let description = a.description || '';
|
||||||
if (a.default)
|
// `a.default` is actually not always a string (typing bug), hence `toString()`
|
||||||
description = `[default: ${a.default}] ${description}`;
|
if (a.default || ((_a = a.default) === null || _a === void 0 ? void 0 : _a.toString()) === '0')
|
||||||
@@ -130,9 +130,7 @@ class CommandHelp {
|
@@ -133,9 +133,7 @@ class CommandHelp {
|
||||||
if (!flag.helpValue && flag.options) {
|
if (!flag.helpValue && flag.options) {
|
||||||
value = flag.options.join('|');
|
value = flag.options.join('|');
|
||||||
}
|
}
|
||||||
@ -21,12 +21,12 @@ index 1caa65b..e205faf 100644
|
|||||||
+ left += ` <${value}>`;
|
+ left += ` <${value}>`;
|
||||||
}
|
}
|
||||||
let right = flag.description || '';
|
let right = flag.description || '';
|
||||||
if (flag.type === 'option' && flag.default) {
|
// `flag.default` is not always a string (typing bug), hence `toString()`
|
||||||
diff --git a/node_modules/@oclif/plugin-help/lib/index.js b/node_modules/@oclif/plugin-help/lib/index.js
|
diff --git a/node_modules/@oclif/plugin-help/lib/index.js b/node_modules/@oclif/plugin-help/lib/index.js
|
||||||
index 02646b6..525e19f 100644
|
index 04d7861..c2fb591 100644
|
||||||
--- a/node_modules/@oclif/plugin-help/lib/index.js
|
--- a/node_modules/@oclif/plugin-help/lib/index.js
|
||||||
+++ b/node_modules/@oclif/plugin-help/lib/index.js
|
+++ b/node_modules/@oclif/plugin-help/lib/index.js
|
||||||
@@ -95,11 +95,12 @@ class Help extends HelpBase {
|
@@ -98,11 +98,12 @@ class Help extends HelpBase {
|
||||||
console.log(title + '\n');
|
console.log(title + '\n');
|
||||||
console.log(this.formatCommand(command));
|
console.log(this.formatCommand(command));
|
||||||
console.log('');
|
console.log('');
|
@ -1,20 +1,20 @@
|
|||||||
diff --git a/node_modules/@oclif/dev-cli/lib/commands/pack/macos.js b/node_modules/@oclif/dev-cli/lib/commands/pack/macos.js
|
diff --git a/node_modules/oclif/lib/commands/pack/macos.js b/node_modules/oclif/lib/commands/pack/macos.js
|
||||||
index e0abbbe..debf799 100644
|
index 924f092..a69e60b 100644
|
||||||
--- a/node_modules/@oclif/dev-cli/lib/commands/pack/macos.js
|
--- a/node_modules/oclif/lib/commands/pack/macos.js
|
||||||
+++ b/node_modules/@oclif/dev-cli/lib/commands/pack/macos.js
|
+++ b/node_modules/oclif/lib/commands/pack/macos.js
|
||||||
@@ -128,6 +128,7 @@ class PackMacos extends command_1.Command {
|
@@ -133,6 +133,7 @@ class PackMacos extends command_1.Command {
|
||||||
if (process.env.OSX_KEYCHAIN)
|
if (process.env.OSX_KEYCHAIN)
|
||||||
args.push('--keychain', process.env.OSX_KEYCHAIN);
|
args.push('--keychain', process.env.OSX_KEYCHAIN);
|
||||||
args.push(dist);
|
args.push(dist);
|
||||||
+ console.error(`[debug] @oclif/dev-cli pkgbuild "${args.join('" "')}"`);
|
+ console.error(`[debug] oclif pkgbuild "${args.join('" "')}"`);
|
||||||
await qq.x('pkgbuild', args);
|
await qq.x('pkgbuild', args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
diff --git a/node_modules/@oclif/dev-cli/lib/commands/pack/win.js b/node_modules/@oclif/dev-cli/lib/commands/pack/win.js
|
diff --git a/node_modules/oclif/lib/commands/pack/win.js b/node_modules/oclif/lib/commands/pack/win.js
|
||||||
index a313991..6681892 100644
|
index bf4657e..fd58c7d 100644
|
||||||
--- a/node_modules/@oclif/dev-cli/lib/commands/pack/win.js
|
--- a/node_modules/oclif/lib/commands/pack/win.js
|
||||||
+++ b/node_modules/@oclif/dev-cli/lib/commands/pack/win.js
|
+++ b/node_modules/oclif/lib/commands/pack/win.js
|
||||||
@@ -51,6 +51,13 @@ VIAddVersionKey /LANG=\${LANG_ENGLISH} "ProductVersion" "\${VERSION}.0"
|
@@ -52,6 +52,13 @@ VIAddVersionKey /LANG=\${LANG_ENGLISH} "ProductVersion" "\${VERSION}.0"
|
||||||
InstallDir "\$PROGRAMFILES${arch === 'x64' ? '64' : ''}\\${config.dirname}"
|
InstallDir "\$PROGRAMFILES${arch === 'x64' ? '64' : ''}\\${config.dirname}"
|
||||||
|
|
||||||
Section "${config.name} CLI \${VERSION}"
|
Section "${config.name} CLI \${VERSION}"
|
||||||
@ -28,44 +28,53 @@ index a313991..6681892 100644
|
|||||||
SetOutPath $INSTDIR
|
SetOutPath $INSTDIR
|
||||||
File /r bin
|
File /r bin
|
||||||
File /r client
|
File /r client
|
||||||
@@ -192,7 +199,8 @@ class PackWin extends command_1.Command {
|
@@ -61,6 +68,8 @@ Section "${config.name} CLI \${VERSION}"
|
||||||
|
WriteUninstaller "$INSTDIR\\Uninstall.exe"
|
||||||
|
WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${config.dirname}" \\
|
||||||
|
"DisplayName" "${config.name}"
|
||||||
|
+ WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${config.dirname}" \\
|
||||||
|
+ "DisplayVersion" "\${VERSION}"
|
||||||
|
WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${config.dirname}" \\
|
||||||
|
"UninstallString" "$\\"$INSTDIR\\uninstall.exe$\\""
|
||||||
|
WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${config.dirname}" \\
|
||||||
|
@@ -193,7 +202,8 @@ class PackWin extends command_1.Command {
|
||||||
async run() {
|
async run() {
|
||||||
await this.checkForNSIS();
|
await this.checkForNSIS();
|
||||||
const { flags } = this.parse(PackWin);
|
const { flags } = this.parse(PackWin);
|
||||||
- const buildConfig = await Tarballs.buildConfig(flags.root);
|
- const buildConfig = await Tarballs.buildConfig(flags.root);
|
||||||
+ const targets = flags.targets ? flags.targets.split(',') : undefined;
|
+ const $targets = flags.targets ? flags.targets.split(',') : undefined;
|
||||||
+ const buildConfig = await Tarballs.buildConfig(flags.root, { targets });
|
+ const buildConfig = await Tarballs.buildConfig(flags.root, { targets: $targets });
|
||||||
const { config } = buildConfig;
|
const { config, version, gitSha, targets, tmp } = buildConfig;
|
||||||
await Tarballs.build(buildConfig, { platform: 'win32', pack: false });
|
await Tarballs.build(buildConfig, { platform: 'win32', pack: false });
|
||||||
const arches = buildConfig.targets.filter(t => t.platform === 'win32').map(t => t.arch);
|
const arches = targets.filter(t => t.platform === 'win32').map(t => t.arch);
|
||||||
@@ -207,7 +215,8 @@ class PackWin extends command_1.Command {
|
@@ -208,7 +218,8 @@ class PackWin extends command_1.Command {
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
await qq.mv(buildConfig.workspace({ platform: 'win32', arch }), [installerBase, 'client']);
|
await qq.mv(buildConfig.workspace({ platform: 'win32', arch }), [installerBase, 'client']);
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
- await qq.x(`makensis ${installerBase}/${config.bin}.nsi | grep -v "\\[compress\\]" | grep -v "^File: Descending to"`);
|
- await qq.x(`makensis ${installerBase}/${config.bin}.nsi | grep -v "\\[compress\\]" | grep -v "^File: Descending to"`);
|
||||||
+ const { msysExec, toMsysPath } = require("../../util");
|
+ const { msysExec, toMsysPath } = require("../../util");
|
||||||
+ await msysExec(`makensis ${toMsysPath(installerBase)}/${config.bin}.nsi | grep -v "\\[compress\\]" | grep -v "^File: Descending to"`);
|
+ await msysExec(`makensis ${toMsysPath(installerBase)}/${config.bin}.nsi | grep -v "\\[compress\\]" | grep -v "^File: Descending to"`);
|
||||||
const o = buildConfig.dist(`win/${config.bin}-v${buildConfig.version}-${arch}.exe`);
|
const templateKey = upload_util_1.templateShortKey('win32', { bin: config.bin, version: version, sha: gitSha, arch });
|
||||||
|
const o = buildConfig.dist(`win32/${templateKey}`);
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
await qq.mv([installerBase, 'installer.exe'], o);
|
@@ -255,4 +266,5 @@ PackWin.hidden = true;
|
||||||
@@ -232,4 +241,5 @@ exports.default = PackWin;
|
|
||||||
PackWin.description = 'create windows installer from oclif CLI';
|
PackWin.description = 'create windows installer from oclif CLI';
|
||||||
PackWin.flags = {
|
PackWin.flags = {
|
||||||
root: command_1.flags.string({ char: 'r', description: 'path to oclif CLI root', default: '.', required: true }),
|
root: command_1.flags.string({ char: 'r', description: 'path to oclif CLI root', default: '.', required: true }),
|
||||||
+ targets: command_1.flags.string({char: 't', description: 'comma-separated targets to pack (e.g.: win32-x86,win32-x64)'}),
|
+ targets: command_1.flags.string({char: 't', description: 'comma-separated targets to pack (e.g.: win32-x86,win32-x64)'}),
|
||||||
};
|
};
|
||||||
diff --git a/node_modules/@oclif/dev-cli/lib/tarballs/build.js b/node_modules/@oclif/dev-cli/lib/tarballs/build.js
|
diff --git a/node_modules/oclif/lib/tarballs/build.js b/node_modules/oclif/lib/tarballs/build.js
|
||||||
index c6bd245..baa7f6f 100644
|
index d3e8e89..a5d29e2 100644
|
||||||
--- a/node_modules/@oclif/dev-cli/lib/tarballs/build.js
|
--- a/node_modules/oclif/lib/tarballs/build.js
|
||||||
+++ b/node_modules/@oclif/dev-cli/lib/tarballs/build.js
|
+++ b/node_modules/oclif/lib/tarballs/build.js
|
||||||
@@ -18,8 +18,9 @@ const pack = async (from, to) => {
|
@@ -18,8 +18,9 @@ const pack = async (from, to) => {
|
||||||
qq.cd(prevCwd);
|
qq.cd(prevCwd);
|
||||||
};
|
};
|
||||||
async function build(c, options = {}) {
|
async function build(c, options = {}) {
|
||||||
- const { xz, config } = c;
|
- const { xz, config, version, s3Config, gitSha, nodeVersion, targets, updateConfig } = c;
|
||||||
+ const { xz, config, tmp } = c;
|
+ const { xz, config, version, s3Config, gitSha, nodeVersion, targets, updateConfig, tmp } = c;
|
||||||
const prevCwd = qq.cwd();
|
const prevCwd = qq.cwd();
|
||||||
+ console.error(`[debug] @oclif/dev-cli cwd="${prevCwd}"\n c.root="${c.root}" c.workspace()="${c.workspace()}"`);
|
+ console.error(`[debug] oclif cwd="${prevCwd}"\n c.root="${c.root}" c.workspace()="${c.workspace()}"`);
|
||||||
const packCLI = async () => {
|
const packCLI = async () => {
|
||||||
const stdout = await qq.x.stdout('npm', ['pack', '--unsafe-perm'], { cwd: c.root });
|
const stdout = await qq.x.stdout('npm', ['pack', '--unsafe-perm'], { cwd: c.root });
|
||||||
return path.join(c.root, stdout.split('\n').pop());
|
return path.join(c.root, stdout.split('\n').pop());
|
||||||
@ -110,7 +119,7 @@ index c6bd245..baa7f6f 100644
|
|||||||
- await qq.x('npm install --production');
|
- await qq.x('npm install --production');
|
||||||
+ const ws = c.workspace();
|
+ const ws = c.workspace();
|
||||||
+ qq.cd(ws);
|
+ qq.cd(ws);
|
||||||
+ console.error(`[debug] @oclif/dev-cli copying node_modules to "${ws}"`)
|
+ console.error(`[debug] oclif copying node_modules to "${ws}"`)
|
||||||
+ const source = path.join(c.root, 'node_modules');
|
+ const source = path.join(c.root, 'node_modules');
|
||||||
+ if (process.platform === 'win32') {
|
+ if (process.platform === 'win32') {
|
||||||
+ // xcopy is much faster than `qq.cp(source, ws)`
|
+ // xcopy is much faster than `qq.cp(source, ws)`
|
||||||
@ -120,13 +129,13 @@ index c6bd245..baa7f6f 100644
|
|||||||
+ // file attributes containing `codesign` digital signatures
|
+ // file attributes containing `codesign` digital signatures
|
||||||
+ await qq.x(`cp -pR "${source}" "${ws}"`);
|
+ await qq.x(`cp -pR "${source}" "${ws}"`);
|
||||||
}
|
}
|
||||||
+ console.error(`[debug] @oclif/dev-cli running "npm prune --production" in "${ws}"`);
|
+ console.error(`[debug] oclif running "npm prune --production" in "${ws}"`);
|
||||||
+ await qq.x('npm prune --production');
|
+ await qq.x('npm prune --production');
|
||||||
+ console.error(`[debug] @oclif/dev-cli done`);
|
+ console.error(`[debug] oclif done`);
|
||||||
};
|
};
|
||||||
const buildTarget = async (target) => {
|
const pretarball = async () => {
|
||||||
const workspace = c.workspace(target);
|
qq.cd(c.workspace());
|
||||||
@@ -74,7 +83,8 @@ async function build(c, options = {}) {
|
@@ -99,7 +108,8 @@ async function build(c, options = {}) {
|
||||||
output: path.join(workspace, 'bin', 'node'),
|
output: path.join(workspace, 'bin', 'node'),
|
||||||
platform: target.platform,
|
platform: target.platform,
|
||||||
arch: target.arch,
|
arch: target.arch,
|
||||||
@ -136,11 +145,11 @@ index c6bd245..baa7f6f 100644
|
|||||||
});
|
});
|
||||||
if (options.pack === false)
|
if (options.pack === false)
|
||||||
return;
|
return;
|
||||||
diff --git a/node_modules/@oclif/dev-cli/lib/tarballs/config.js b/node_modules/@oclif/dev-cli/lib/tarballs/config.js
|
diff --git a/node_modules/oclif/lib/tarballs/config.js b/node_modules/oclif/lib/tarballs/config.js
|
||||||
index 9754a6b..68ef6b7 100644
|
index 0dc3cd7..1336219 100644
|
||||||
--- a/node_modules/@oclif/dev-cli/lib/tarballs/config.js
|
--- a/node_modules/oclif/lib/tarballs/config.js
|
||||||
+++ b/node_modules/@oclif/dev-cli/lib/tarballs/config.js
|
+++ b/node_modules/oclif/lib/tarballs/config.js
|
||||||
@@ -17,7 +17,10 @@ function gitSha(cwd, options = {}) {
|
@@ -18,7 +18,10 @@ function gitSha(cwd, options = {}) {
|
||||||
}
|
}
|
||||||
exports.gitSha = gitSha;
|
exports.gitSha = gitSha;
|
||||||
async function Tmp(config) {
|
async function Tmp(config) {
|
||||||
@ -148,23 +157,23 @@ index 9754a6b..68ef6b7 100644
|
|||||||
+ const tmp = process.env.BUILD_TMP
|
+ const tmp = process.env.BUILD_TMP
|
||||||
+ ? path.join(process.env.BUILD_TMP, 'oclif')
|
+ ? path.join(process.env.BUILD_TMP, 'oclif')
|
||||||
+ : path.join(config.root, 'tmp');
|
+ : path.join(config.root, 'tmp');
|
||||||
+ console.error(`[debug] @oclif/dev-cli tmp="${tmp}"`);
|
+ console.error(`[debug] oclif tmp="${tmp}"`);
|
||||||
await qq.mkdirp(tmp);
|
await qq.mkdirp(tmp);
|
||||||
return tmp;
|
return tmp;
|
||||||
}
|
}
|
||||||
@@ -44,7 +47,7 @@ async function buildConfig(root, options = {}) {
|
@@ -43,7 +46,7 @@ async function buildConfig(root, options = {}) {
|
||||||
s3Config: updateConfig.s3,
|
s3Config: updateConfig.s3,
|
||||||
nodeVersion: updateConfig.node.version || process.versions.node,
|
nodeVersion: updateConfig.node.version || process.versions.node,
|
||||||
workspace(target) {
|
workspace(target) {
|
||||||
- const base = qq.join(config.root, 'tmp');
|
- const base = qq.join(config.root, 'tmp');
|
||||||
+ const base = tmp;
|
+ const base = tmp;
|
||||||
if (target && target.platform)
|
if (target && target.platform)
|
||||||
return qq.join(base, [target.platform, target.arch].join('-'), config.s3Key('baseDir', target));
|
return qq.join(base, [target.platform, target.arch].join('-'), upload_util_1.templateShortKey('baseDir', { bin: config.bin }));
|
||||||
return qq.join(base, config.s3Key('baseDir', target));
|
return qq.join(base, upload_util_1.templateShortKey('baseDir', { bin: config.bin }));
|
||||||
diff --git a/node_modules/@oclif/dev-cli/lib/tarballs/node.js b/node_modules/@oclif/dev-cli/lib/tarballs/node.js
|
diff --git a/node_modules/oclif/lib/tarballs/node.js b/node_modules/oclif/lib/tarballs/node.js
|
||||||
index fabe5c4..e32dd76 100644
|
index fabe5c4..e32dd76 100644
|
||||||
--- a/node_modules/@oclif/dev-cli/lib/tarballs/node.js
|
--- a/node_modules/oclif/lib/tarballs/node.js
|
||||||
+++ b/node_modules/@oclif/dev-cli/lib/tarballs/node.js
|
+++ b/node_modules/oclif/lib/tarballs/node.js
|
||||||
@@ -4,9 +4,10 @@ const errors_1 = require("@oclif/errors");
|
@@ -4,9 +4,10 @@ const errors_1 = require("@oclif/errors");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const qq = require("qqjs");
|
const qq = require("qqjs");
|
||||||
@ -196,14 +205,33 @@ index fabe5c4..e32dd76 100644
|
|||||||
await qq.mv([nodeBase, 'node.exe'], cache);
|
await qq.mv([nodeBase, 'node.exe'], cache);
|
||||||
qq.popd();
|
qq.popd();
|
||||||
}
|
}
|
||||||
diff --git a/node_modules/@oclif/dev-cli/lib/util.js b/node_modules/@oclif/dev-cli/lib/util.js
|
diff --git a/node_modules/oclif/lib/upload-util.js b/node_modules/oclif/lib/upload-util.js
|
||||||
index b3d48b7..540bbe6 100644
|
index 45392cb..3c806c7 100644
|
||||||
--- a/node_modules/@oclif/dev-cli/lib/util.js
|
--- a/node_modules/oclif/lib/upload-util.js
|
||||||
+++ b/node_modules/@oclif/dev-cli/lib/util.js
|
+++ b/node_modules/oclif/lib/upload-util.js
|
||||||
@@ -40,3 +40,47 @@ function sortBy(arr, fn) {
|
@@ -28,10 +28,10 @@ function templateShortKey(type, ext, options = { root: '.' }) {
|
||||||
}
|
const templates = {
|
||||||
exports.sortBy = sortBy;
|
baseDir: '<%- bin %>',
|
||||||
exports.template = (context) => (t) => _.template(t || '')(context);
|
unversioned: '<%- bin %>-<%- platform %>-<%- arch %><%- ext %>',
|
||||||
|
- versioned: '<%- bin %>-v<%- version %>-<%- sha %>-<%- platform %>-<%- arch %><%- ext %>',
|
||||||
|
- manifest: '<%- bin %>-v<%- version %>-<%- sha %>-<%- platform %>-<%- arch %>-buildmanifest',
|
||||||
|
- macos: '<%- bin %>-v<%- version %>-<%- sha %>.pkg',
|
||||||
|
- win32: '<%- bin %>-v<%- version %>-<%- sha %>-<%- arch %>.exe',
|
||||||
|
+ versioned: '<%- bin %>-v<%- version %>-<%- platform %>-<%- arch %><%- ext %>',
|
||||||
|
+ manifest: '<%- bin %>-v<%- version %>-<%- platform %>-<%- arch %>-buildmanifest',
|
||||||
|
+ macos: '<%- bin %>-v<%- version %>.pkg',
|
||||||
|
+ win32: '<%- bin %>-v<%- version %>-<%- arch %>.exe',
|
||||||
|
deb: '<%- bin %>_<%- versionShaRevision %>_<%- arch %>.deb',
|
||||||
|
};
|
||||||
|
return _.template(templates[type])(Object.assign({}, options));
|
||||||
|
diff --git a/node_modules/oclif/lib/util.js b/node_modules/oclif/lib/util.js
|
||||||
|
index 17748ad..4928fc9 100644
|
||||||
|
--- a/node_modules/oclif/lib/util.js
|
||||||
|
+++ b/node_modules/oclif/lib/util.js
|
||||||
|
@@ -67,3 +67,47 @@ exports.sortVersionsObjectByKeysDesc = (input) => {
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
+
|
+
|
||||||
+// OSTYPE is 'msys' for MSYS 1.0 and for MSYS2, or 'cygwin' for Cygwin
|
+// OSTYPE is 'msys' for MSYS 1.0 and for MSYS2, or 'cygwin' for Cygwin
|
||||||
+// but note that OSTYPE is not "exported" by default, so run: export OSTYPE=$OSTYPE
|
+// but note that OSTYPE is not "exported" by default, so run: export OSTYPE=$OSTYPE
|
||||||
@ -219,7 +247,7 @@ index b3d48b7..540bbe6 100644
|
|||||||
+exports.isCygwin = isCygwin;
|
+exports.isCygwin = isCygwin;
|
||||||
+exports.isMinGW = isMinGW;
|
+exports.isMinGW = isMinGW;
|
||||||
+exports.isMSYS2 = isMSYS2;
|
+exports.isMSYS2 = isMSYS2;
|
||||||
+console.error(`[debug] @oclif/dev-cli MSYSSHELLPATH=${MSYSSHELLPATH} MSYSTEM=${process.env.MSYSTEM} OSTYPE=${process.env.OSTYPE} isMSYS2=${isMSYS2} isMingGW=${isMinGW} isCygwin=${isCygwin}`);
|
+console.error(`[debug] oclif MSYSSHELLPATH=${MSYSSHELLPATH} MSYSTEM=${process.env.MSYSTEM} OSTYPE=${process.env.OSTYPE} isMSYS2=${isMSYS2} isMingGW=${isMinGW} isCygwin=${isCygwin}`);
|
||||||
+
|
+
|
||||||
+const qq = require("qqjs");
|
+const qq = require("qqjs");
|
||||||
+
|
+
|
||||||
@ -232,7 +260,7 @@ index b3d48b7..540bbe6 100644
|
|||||||
+ } else if (isCygwin) {
|
+ } else if (isCygwin) {
|
||||||
+ msysPath = msysPath.replace(/^([a-zA-Z]):/, '/cygdrive/$1');
|
+ msysPath = msysPath.replace(/^([a-zA-Z]):/, '/cygdrive/$1');
|
||||||
+ }
|
+ }
|
||||||
+ console.error(`[debug] @oclif/dev-cli toMsysPath before="${windowsPath}" after="${msysPath}"`);
|
+ console.error(`[debug] oclif toMsysPath before="${windowsPath}" after="${msysPath}"`);
|
||||||
+ return msysPath;
|
+ return msysPath;
|
||||||
+}
|
+}
|
||||||
+exports.toMsysPath = toMsysPath;
|
+exports.toMsysPath = toMsysPath;
|
||||||
@ -244,7 +272,7 @@ index b3d48b7..540bbe6 100644
|
|||||||
+ }
|
+ }
|
||||||
+ const sh = MSYSSHELLPATH;
|
+ const sh = MSYSSHELLPATH;
|
||||||
+ const args = ['-c', cmd];
|
+ const args = ['-c', cmd];
|
||||||
+ console.error(`[debug] @oclif/dev-cli msysExec sh="${sh}" args=${JSON.stringify(args)} options=${JSON.stringify(options)}`);
|
+ console.error(`[debug] oclif msysExec sh="${sh}" args=${JSON.stringify(args)} options=${JSON.stringify(options)}`);
|
||||||
+ return qq.x(sh, args, options);
|
+ return qq.x(sh, args, options);
|
||||||
+}
|
+}
|
||||||
+exports.msysExec = msysExec;
|
+exports.msysExec = msysExec;
|
@ -1,37 +0,0 @@
|
|||||||
diff --git a/node_modules/pkg/lib-es5/packer.js b/node_modules/pkg/lib-es5/packer.js
|
|
||||||
index 7295bb6..76805a3 100644
|
|
||||||
--- a/node_modules/pkg/lib-es5/packer.js
|
|
||||||
+++ b/node_modules/pkg/lib-es5/packer.js
|
|
||||||
@@ -128,6 +128,7 @@ function _default({
|
|
||||||
const newStat = Object.assign({}, value);
|
|
||||||
newStat.isFileValue = value.isFile();
|
|
||||||
newStat.isDirectoryValue = value.isDirectory();
|
|
||||||
+ newStat.isSocketValue = value.isSocket();
|
|
||||||
const buffer = Buffer.from(JSON.stringify(newStat));
|
|
||||||
stripes.push({
|
|
||||||
snap,
|
|
||||||
diff --git a/node_modules/pkg/prelude/bootstrap.js b/node_modules/pkg/prelude/bootstrap.js
|
|
||||||
index 0d19f1d..db69015 100644
|
|
||||||
--- a/node_modules/pkg/prelude/bootstrap.js
|
|
||||||
+++ b/node_modules/pkg/prelude/bootstrap.js
|
|
||||||
@@ -925,8 +925,10 @@ function payloadFileSync (pointer) {
|
|
||||||
|
|
||||||
var isFileValue = s.isFileValue;
|
|
||||||
var isDirectoryValue = s.isDirectoryValue;
|
|
||||||
+ var isSocketValue = s.isSocketValue;
|
|
||||||
delete s.isFileValue;
|
|
||||||
delete s.isDirectoryValue;
|
|
||||||
+ delete s.isSocketValue;
|
|
||||||
|
|
||||||
s.isFile = function () {
|
|
||||||
return isFileValue;
|
|
||||||
@@ -934,6 +936,9 @@ function payloadFileSync (pointer) {
|
|
||||||
s.isDirectory = function () {
|
|
||||||
return isDirectoryValue;
|
|
||||||
};
|
|
||||||
+ s.isSocket = function () {
|
|
||||||
+ return isSocketValue;
|
|
||||||
+ };
|
|
||||||
s.isSymbolicLink = function () {
|
|
||||||
return false;
|
|
||||||
};
|
|
@ -24,7 +24,10 @@ const { promisify } = require('util');
|
|||||||
const execFileAsync = promisify(execFile);
|
const execFileAsync = promisify(execFile);
|
||||||
const patchesDir = 'patches';
|
const patchesDir = 'patches';
|
||||||
|
|
||||||
/** Run the patch-package tool in a child process and wait for it to finish */
|
/**
|
||||||
|
* Run the patch-package tool in a child process and wait for it to finish
|
||||||
|
* @param {string} patchDir
|
||||||
|
*/
|
||||||
async function patchPackage(patchDir) {
|
async function patchPackage(patchDir) {
|
||||||
// Equivalent to: `npx patch-package --patch-dir $patchDir`
|
// Equivalent to: `npx patch-package --patch-dir $patchDir`
|
||||||
const result = await execFileAsync('node', [
|
const result = await execFileAsync('node', [
|
||||||
|
2
repo.yml
2
repo.yml
@ -16,3 +16,5 @@ upstream:
|
|||||||
url: 'https://github.com/balena-io-modules/etcher-sdk/'
|
url: 'https://github.com/balena-io-modules/etcher-sdk/'
|
||||||
- repo: 'resin-compose-parse'
|
- repo: 'resin-compose-parse'
|
||||||
url: 'https://github.com/balena-io-modules/resin-compose-parse'
|
url: 'https://github.com/balena-io-modules/resin-compose-parse'
|
||||||
|
- repo: 'docker-progress'
|
||||||
|
url: 'https://github.com/balena-io-modules/docker-progress'
|
||||||
|
@ -22,7 +22,6 @@ import { promises as fs } from 'fs';
|
|||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
import { stripIndent } from '../../build/utils/lazy';
|
import { stripIndent } from '../../build/utils/lazy';
|
||||||
import { isV13 } from '../../build/utils/version';
|
|
||||||
import { BalenaAPIMock } from '../nock/balena-api-mock';
|
import { BalenaAPIMock } from '../nock/balena-api-mock';
|
||||||
import { expectStreamNoCRLF, testDockerBuildStream } from '../docker-build';
|
import { expectStreamNoCRLF, testDockerBuildStream } from '../docker-build';
|
||||||
import { DockerMock, dockerResponsePath } from '../nock/docker-mock';
|
import { DockerMock, dockerResponsePath } from '../nock/docker-mock';
|
||||||
@ -127,23 +126,14 @@ describe('balena build', function () {
|
|||||||
];
|
];
|
||||||
if (isWindows) {
|
if (isWindows) {
|
||||||
const fname = path.join(projectPath, 'src', 'windows-crlf.sh');
|
const fname = path.join(projectPath, 'src', 'windows-crlf.sh');
|
||||||
if (isWindows) {
|
expectedResponseLines.push(
|
||||||
expectedResponseLines.push(
|
`[Info] Converting line endings CRLF -> LF for file: ${fname}`,
|
||||||
`[Info] Converting line endings CRLF -> LF for file: ${fname}`,
|
);
|
||||||
);
|
|
||||||
} else {
|
|
||||||
expectedResponseLines.push(
|
|
||||||
`[Warn] CRLF (Windows) line endings detected in file: ${fname}`,
|
|
||||||
'[Warn] Windows-format line endings were detected in some files. Consider using the `--convert-eol` option.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
docker.expectGetInfo({});
|
docker.expectGetInfo({});
|
||||||
docker.expectGetManifestBusybox();
|
docker.expectGetManifestBusybox();
|
||||||
await testDockerBuildStream({
|
await testDockerBuildStream({
|
||||||
commandLine: `build ${projectPath} --deviceType nuc --arch amd64 ${
|
commandLine: `build ${projectPath} --deviceType nuc --arch amd64`,
|
||||||
isV13() ? '' : '-g'
|
|
||||||
}`,
|
|
||||||
dockerMock: docker,
|
dockerMock: docker,
|
||||||
expectedFilesByService: { main: expectedFiles },
|
expectedFilesByService: { main: expectedFiles },
|
||||||
expectedQueryParamsByService: {
|
expectedQueryParamsByService: {
|
||||||
@ -192,16 +182,9 @@ describe('balena build', function () {
|
|||||||
];
|
];
|
||||||
if (isWindows) {
|
if (isWindows) {
|
||||||
const fname = path.join(projectPath, 'src', 'windows-crlf.sh');
|
const fname = path.join(projectPath, 'src', 'windows-crlf.sh');
|
||||||
if (isWindows) {
|
expectedResponseLines.push(
|
||||||
expectedResponseLines.push(
|
`[Info] Converting line endings CRLF -> LF for file: ${fname}`,
|
||||||
`[Info] Converting line endings CRLF -> LF for file: ${fname}`,
|
);
|
||||||
);
|
|
||||||
} else {
|
|
||||||
expectedResponseLines.push(
|
|
||||||
`[Warn] CRLF (Windows) line endings detected in file: ${fname}`,
|
|
||||||
'[Warn] Windows-format line endings were detected in some files. Consider using the `--convert-eol` option.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
docker.expectGetInfo({});
|
docker.expectGetInfo({});
|
||||||
docker.expectGetManifestBusybox();
|
docker.expectGetManifestBusybox();
|
||||||
@ -297,9 +280,7 @@ describe('balena build', function () {
|
|||||||
docker.expectGetInfo({ OperatingSystem: 'balenaOS 2.44.0+rev1' });
|
docker.expectGetInfo({ OperatingSystem: 'balenaOS 2.44.0+rev1' });
|
||||||
docker.expectGetManifestBusybox();
|
docker.expectGetManifestBusybox();
|
||||||
await testDockerBuildStream({
|
await testDockerBuildStream({
|
||||||
commandLine: `build ${projectPath} --emulated --deviceType ${deviceType} --arch ${arch} ${
|
commandLine: `build ${projectPath} --emulated --deviceType ${deviceType} --arch ${arch}`,
|
||||||
isV13() ? '' : '--nogitignore'
|
|
||||||
}`,
|
|
||||||
dockerMock: docker,
|
dockerMock: docker,
|
||||||
expectedFilesByService: { main: expectedFiles },
|
expectedFilesByService: { main: expectedFiles },
|
||||||
expectedQueryParamsByService: {
|
expectedQueryParamsByService: {
|
||||||
@ -317,7 +298,7 @@ describe('balena build', function () {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create the expected tar stream (single container, --[no]convert-eol, --multi-dockerignore)', async () => {
|
it('should create the expected tar stream (single container, --noconvert-eol, --multi-dockerignore)', async () => {
|
||||||
const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic');
|
const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic');
|
||||||
const expectedFiles: ExpectedTarStreamFiles = {
|
const expectedFiles: ExpectedTarStreamFiles = {
|
||||||
'src/.dockerignore': { fileSize: 16, type: 'file' },
|
'src/.dockerignore': { fileSize: 16, type: 'file' },
|
||||||
@ -448,9 +429,7 @@ describe('balena build', function () {
|
|||||||
docker.expectGetManifestNucAlpine();
|
docker.expectGetManifestNucAlpine();
|
||||||
docker.expectGetManifestBusybox();
|
docker.expectGetManifestBusybox();
|
||||||
await testDockerBuildStream({
|
await testDockerBuildStream({
|
||||||
commandLine: `build ${projectPath} --deviceType nuc --arch amd64 --convert-eol ${
|
commandLine: `build ${projectPath} --deviceType nuc --arch amd64 -B COMPOSE_ARG=A -B barg=b --cache-from my/img1,my/img2`,
|
||||||
isV13() ? '' : '-G'
|
|
||||||
} -B COMPOSE_ARG=A -B barg=b --cache-from my/img1,my/img2`,
|
|
||||||
dockerMock: docker,
|
dockerMock: docker,
|
||||||
expectedFilesByService,
|
expectedFilesByService,
|
||||||
expectedQueryParamsByService,
|
expectedQueryParamsByService,
|
||||||
@ -533,7 +512,7 @@ describe('balena build', function () {
|
|||||||
docker.expectGetManifestNucAlpine();
|
docker.expectGetManifestNucAlpine();
|
||||||
|
|
||||||
await testDockerBuildStream({
|
await testDockerBuildStream({
|
||||||
commandLine: `build ${projectPath} --deviceType nuc --arch amd64 --convert-eol -m`,
|
commandLine: `build ${projectPath} --deviceType nuc --arch amd64 -m`,
|
||||||
dockerMock: docker,
|
dockerMock: docker,
|
||||||
expectedFilesByService,
|
expectedFilesByService,
|
||||||
expectedQueryParamsByService,
|
expectedQueryParamsByService,
|
||||||
@ -618,7 +597,7 @@ describe('balena build', function () {
|
|||||||
docker.expectGetManifestNucAlpine();
|
docker.expectGetManifestNucAlpine();
|
||||||
|
|
||||||
await testDockerBuildStream({
|
await testDockerBuildStream({
|
||||||
commandLine: `build ${projectPath} --deviceType nuc --arch amd64 --convert-eol -m --tag ${tag} --projectName ${projectName}`,
|
commandLine: `build ${projectPath} --deviceType nuc --arch amd64 -m --tag ${tag} --projectName ${projectName}`,
|
||||||
dockerMock: docker,
|
dockerMock: docker,
|
||||||
expectedFilesByService,
|
expectedFilesByService,
|
||||||
expectedQueryParamsByService,
|
expectedQueryParamsByService,
|
||||||
|
@ -23,7 +23,6 @@ import * as nock from 'nock';
|
|||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as sinon from 'sinon';
|
import * as sinon from 'sinon';
|
||||||
|
|
||||||
import { isV13 } from '../../build/utils/version';
|
|
||||||
import { BalenaAPIMock } from '../nock/balena-api-mock';
|
import { BalenaAPIMock } from '../nock/balena-api-mock';
|
||||||
import { expectStreamNoCRLF, testDockerBuildStream } from '../docker-build';
|
import { expectStreamNoCRLF, testDockerBuildStream } from '../docker-build';
|
||||||
import { DockerMock, dockerResponsePath } from '../nock/docker-mock';
|
import { DockerMock, dockerResponsePath } from '../nock/docker-mock';
|
||||||
@ -149,9 +148,7 @@ describe('balena deploy', function () {
|
|||||||
docker.expectGetManifestBusybox();
|
docker.expectGetManifestBusybox();
|
||||||
|
|
||||||
await testDockerBuildStream({
|
await testDockerBuildStream({
|
||||||
commandLine: `deploy testApp --build --source ${projectPath} ${
|
commandLine: `deploy testApp --build --source ${projectPath}`,
|
||||||
isV13() ? '' : '-G'
|
|
||||||
}`,
|
|
||||||
dockerMock: docker,
|
dockerMock: docker,
|
||||||
expectedFilesByService: { main: expectedFiles },
|
expectedFilesByService: { main: expectedFiles },
|
||||||
expectedQueryParamsByService: { main: commonQueryParams },
|
expectedQueryParamsByService: { main: commonQueryParams },
|
||||||
@ -315,9 +312,7 @@ describe('balena deploy', function () {
|
|||||||
sinon.stub(process, 'exit');
|
sinon.stub(process, 'exit');
|
||||||
|
|
||||||
await testDockerBuildStream({
|
await testDockerBuildStream({
|
||||||
commandLine: `deploy testApp --build --source ${projectPath} --noconvert-eol ${
|
commandLine: `deploy testApp --build --source ${projectPath} --noconvert-eol`,
|
||||||
isV13() ? '' : '-G'
|
|
||||||
}`,
|
|
||||||
dockerMock: docker,
|
dockerMock: docker,
|
||||||
expectedFilesByService: { main: expectedFiles },
|
expectedFilesByService: { main: expectedFiles },
|
||||||
expectedQueryParamsByService: { main: commonQueryParams },
|
expectedQueryParamsByService: { main: commonQueryParams },
|
||||||
|
@ -21,8 +21,7 @@ import * as path from 'path';
|
|||||||
import { apiResponsePath, BalenaAPIMock } from '../../nock/balena-api-mock';
|
import { apiResponsePath, BalenaAPIMock } from '../../nock/balena-api-mock';
|
||||||
import { cleanOutput, runCommand } from '../../helpers';
|
import { cleanOutput, runCommand } from '../../helpers';
|
||||||
|
|
||||||
import { appToFleetOutputMsg, warnify } from '../../../build/utils/messages';
|
import { isV14 } from '../../../lib/utils/version';
|
||||||
import { isV13 } from '../../../build/utils/version';
|
|
||||||
|
|
||||||
describe('balena device', function () {
|
describe('balena device', function () {
|
||||||
let api: BalenaAPIMock;
|
let api: BalenaAPIMock;
|
||||||
@ -38,13 +37,6 @@ describe('balena device', function () {
|
|||||||
api.done();
|
api.done();
|
||||||
});
|
});
|
||||||
|
|
||||||
const expectedWarn =
|
|
||||||
!isV13() &&
|
|
||||||
process.stderr.isTTY &&
|
|
||||||
process.env.BALENA_CLI_TEST_TYPE !== 'standalone'
|
|
||||||
? warnify(appToFleetOutputMsg) + '\n'
|
|
||||||
: '';
|
|
||||||
|
|
||||||
it('should error if uuid not provided', async () => {
|
it('should error if uuid not provided', async () => {
|
||||||
const { out, err } = await runCommand('device');
|
const { out, err } = await runCommand('device');
|
||||||
const errLines = cleanOutput(err);
|
const errLines = cleanOutput(err);
|
||||||
@ -57,27 +49,32 @@ describe('balena device', function () {
|
|||||||
it('should list device details for provided uuid', async () => {
|
it('should list device details for provided uuid', async () => {
|
||||||
api.scope
|
api.scope
|
||||||
.get(
|
.get(
|
||||||
/^\/v6\/device\?.+&\$expand=belongs_to__application\(\$select=app_name\)/,
|
/^\/v6\/device\?.+&\$expand=belongs_to__application\(\$select=app_name,slug\)/,
|
||||||
)
|
)
|
||||||
.replyWithFile(200, path.join(apiResponsePath, 'device.json'), {
|
.replyWithFile(200, path.join(apiResponsePath, 'device.json'), {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
});
|
});
|
||||||
|
|
||||||
const { out, err } = await runCommand('device 27fda508c');
|
const { out } = await runCommand('device 27fda508c');
|
||||||
|
|
||||||
const lines = cleanOutput(out);
|
const lines = cleanOutput(out);
|
||||||
|
|
||||||
expect(lines).to.have.lengthOf(25);
|
if (isV14()) {
|
||||||
expect(lines[0]).to.equal('== SPARKLING WOOD');
|
expect(lines).to.have.lengthOf(26);
|
||||||
expect(lines[6].split(':')[1].trim()).to.equal('test app');
|
expect(lines[0]).to.equal('sparkling-wood');
|
||||||
|
expect(lines[2].split(':')[0].trim()).to.equal('Id');
|
||||||
expect(err.join('')).to.eql(expectedWarn);
|
expect(lines[2].split(':')[1].trim()).to.equal('1747415');
|
||||||
|
} else {
|
||||||
|
expect(lines).to.have.lengthOf(25);
|
||||||
|
expect(lines[0]).to.equal('== SPARKLING WOOD');
|
||||||
|
expect(lines[6].split(':')[1].trim()).to.equal('org/test app');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it.skip('correctly handles devices with missing fields', async () => {
|
it.skip('correctly handles devices with missing fields', async () => {
|
||||||
api.scope
|
api.scope
|
||||||
.get(
|
.get(
|
||||||
/^\/v6\/device\?.+&\$expand=belongs_to__application\(\$select=app_name\)/,
|
/^\/v6\/device\?.+&\$expand=belongs_to__application\(\$select=app_name,slug\)/,
|
||||||
)
|
)
|
||||||
.replyWithFile(
|
.replyWithFile(
|
||||||
200,
|
200,
|
||||||
@ -87,23 +84,27 @@ describe('balena device', function () {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const { out, err } = await runCommand('device 27fda508c');
|
const { out } = await runCommand('device 27fda508c');
|
||||||
|
|
||||||
const lines = cleanOutput(out);
|
const lines = cleanOutput(out);
|
||||||
|
|
||||||
expect(lines).to.have.lengthOf(14);
|
if (isV14()) {
|
||||||
expect(lines[0]).to.equal('== SPARKLING WOOD');
|
expect(lines).to.have.lengthOf(15);
|
||||||
expect(lines[6].split(':')[1].trim()).to.equal('test app');
|
expect(lines[0]).to.equal('sparkling-wood');
|
||||||
|
expect(lines[7].split(':')[1].trim()).to.equal('org/test app');
|
||||||
expect(err.join('')).to.eql(expectedWarn);
|
} else {
|
||||||
|
expect(lines).to.have.lengthOf(14);
|
||||||
|
expect(lines[0]).to.equal('== SPARKLING WOOD');
|
||||||
|
expect(lines[6].split(':')[1].trim()).to.equal('org/test app');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('correctly handles devices with missing application', async () => {
|
it.skip('correctly handles devices with missing fleet', async () => {
|
||||||
// Devices with missing applications will have application name set to `N/a`.
|
// Devices with missing fleets will have fleet name set to `N/a`.
|
||||||
// e.g. When user has a device associated with app that user is no longer a collaborator of.
|
// e.g. When user has a device associated with fleet that user is no longer a collaborator of.
|
||||||
api.scope
|
api.scope
|
||||||
.get(
|
.get(
|
||||||
/^\/v6\/device\?.+&\$expand=belongs_to__application\(\$select=app_name\)/,
|
/^\/v6\/device\?.+&\$expand=belongs_to__application\(\$select=app_name,slug\)/,
|
||||||
)
|
)
|
||||||
.replyWithFile(
|
.replyWithFile(
|
||||||
200,
|
200,
|
||||||
@ -113,14 +114,19 @@ describe('balena device', function () {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const { out, err } = await runCommand('device 27fda508c');
|
const { out } = await runCommand('device 27fda508c');
|
||||||
|
|
||||||
const lines = cleanOutput(out);
|
const lines = cleanOutput(out);
|
||||||
|
|
||||||
expect(lines).to.have.lengthOf(25);
|
if (isV14()) {
|
||||||
expect(lines[0]).to.equal('== SPARKLING WOOD');
|
expect(lines).to.have.lengthOf(26);
|
||||||
expect(lines[6].split(':')[1].trim()).to.equal('N/a');
|
expect(lines[0]).to.equal('sparkling-wood');
|
||||||
|
expect(lines[9].split(':')[0].trim()).to.equal('Fleet');
|
||||||
expect(err.join('')).to.eql(expectedWarn);
|
expect(lines[9].split(':')[1].trim()).to.equal('N/a');
|
||||||
|
} else {
|
||||||
|
expect(lines).to.have.lengthOf(25);
|
||||||
|
expect(lines[0]).to.equal('== SPARKLING WOOD');
|
||||||
|
expect(lines[6].split(':')[1].trim()).to.equal('N/a');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -21,8 +21,7 @@ import * as path from 'path';
|
|||||||
import { apiResponsePath, BalenaAPIMock } from '../../nock/balena-api-mock';
|
import { apiResponsePath, BalenaAPIMock } from '../../nock/balena-api-mock';
|
||||||
import { cleanOutput, runCommand } from '../../helpers';
|
import { cleanOutput, runCommand } from '../../helpers';
|
||||||
|
|
||||||
import { appToFleetOutputMsg, warnify } from '../../../build/utils/messages';
|
import { isV14 } from '../../../lib/utils/version';
|
||||||
import { isV13 } from '../../../build/utils/version';
|
|
||||||
|
|
||||||
describe('balena devices', function () {
|
describe('balena devices', function () {
|
||||||
let api: BalenaAPIMock;
|
let api: BalenaAPIMock;
|
||||||
@ -38,39 +37,37 @@ describe('balena devices', function () {
|
|||||||
api.done();
|
api.done();
|
||||||
});
|
});
|
||||||
|
|
||||||
const expectedWarn =
|
|
||||||
!isV13() &&
|
|
||||||
process.stderr.isTTY &&
|
|
||||||
process.env.BALENA_CLI_TEST_TYPE !== 'standalone'
|
|
||||||
? warnify(appToFleetOutputMsg) + '\n'
|
|
||||||
: '';
|
|
||||||
|
|
||||||
it('should list devices from own and collaborator apps', async () => {
|
it('should list devices from own and collaborator apps', async () => {
|
||||||
api.scope
|
api.scope
|
||||||
.get(
|
.get(
|
||||||
'/v6/device?$orderby=device_name%20asc&$expand=belongs_to__application($select=app_name),is_of__device_type($select=slug),is_running__release($select=commit)',
|
'/v6/device?$orderby=device_name%20asc&$expand=belongs_to__application($select=app_name,slug),is_of__device_type($select=slug),is_running__release($select=commit)',
|
||||||
)
|
)
|
||||||
.replyWithFile(200, path.join(apiResponsePath, 'devices.json'), {
|
.replyWithFile(200, path.join(apiResponsePath, 'devices.json'), {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
});
|
});
|
||||||
|
|
||||||
const { out, err } = await runCommand('devices');
|
const { out } = await runCommand('devices');
|
||||||
|
|
||||||
const lines = cleanOutput(out);
|
const lines = cleanOutput(out);
|
||||||
|
|
||||||
expect(lines[0].replace(/ +/g, ' ')).to.equal(
|
if (isV14()) {
|
||||||
isV13()
|
expect(lines[0].replace(/ +/g, ' ')).to.equal(
|
||||||
? 'ID UUID DEVICE NAME DEVICE TYPE FLEET STATUS IS ONLINE SUPERVISOR VERSION OS VERSION DASHBOARD URL'
|
' Id Uuid Device name Device type Fleet Status Is online Supervisor version Os version Dashboard url ',
|
||||||
: 'ID UUID DEVICE NAME DEVICE TYPE APPLICATION NAME STATUS IS ONLINE SUPERVISOR VERSION OS VERSION DASHBOARD URL',
|
);
|
||||||
);
|
expect(lines).to.have.lengthOf.at.least(3);
|
||||||
expect(lines).to.have.lengthOf.at.least(2);
|
expect(lines.some((l) => l.includes('org/test app'))).to.be.true;
|
||||||
|
// Devices with missing applications will have application name set to `N/a`.
|
||||||
expect(lines.some((l) => l.includes('test app'))).to.be.true;
|
// e.g. When user has a device associated with app that user is no longer a collaborator of.
|
||||||
|
expect(lines.some((l) => l.includes('N/a'))).to.be.true;
|
||||||
// Devices with missing applications will have application name set to `N/a`.
|
} else {
|
||||||
// e.g. When user has a device associated with app that user is no longer a collaborator of.
|
expect(lines[0].replace(/ +/g, ' ')).to.equal(
|
||||||
expect(lines.some((l) => l.includes('N/a'))).to.be.true;
|
'ID UUID DEVICE NAME DEVICE TYPE FLEET STATUS IS ONLINE SUPERVISOR VERSION OS VERSION DASHBOARD URL',
|
||||||
|
);
|
||||||
expect(err.join('')).to.eql(expectedWarn);
|
expect(lines).to.have.lengthOf.at.least(2);
|
||||||
|
expect(lines.some((l) => l.includes('org/test app'))).to.be.true;
|
||||||
|
// Devices with missing applications will have application name set to `N/a`.
|
||||||
|
// e.g. When user has a device associated with app that user is no longer a collaborator of.
|
||||||
|
expect(lines.some((l) => l.includes('N/a'))).to.be.true;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* @license
|
* @license
|
||||||
* Copyright 2019-2021 Balena Ltd.
|
* Copyright 2019 Balena Ltd.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -19,8 +19,7 @@ import { expect } from 'chai';
|
|||||||
|
|
||||||
import { BalenaAPIMock } from '../../nock/balena-api-mock';
|
import { BalenaAPIMock } from '../../nock/balena-api-mock';
|
||||||
import { cleanOutput, runCommand } from '../../helpers';
|
import { cleanOutput, runCommand } from '../../helpers';
|
||||||
|
import { isV14 } from '../../../lib/utils/version';
|
||||||
import { isV13 } from '../../../build/utils/version';
|
|
||||||
|
|
||||||
describe('balena devices supported', function () {
|
describe('balena devices supported', function () {
|
||||||
let api: BalenaAPIMock;
|
let api: BalenaAPIMock;
|
||||||
@ -48,34 +47,19 @@ describe('balena devices supported', function () {
|
|||||||
api.expectGetDeviceTypes();
|
api.expectGetDeviceTypes();
|
||||||
api.expectGetConfigDeviceTypes();
|
api.expectGetConfigDeviceTypes();
|
||||||
|
|
||||||
const { out, err } = await runCommand('devices supported -v');
|
const { out, err } = await runCommand('devices supported');
|
||||||
|
|
||||||
const lines = cleanOutput(out, true);
|
const lines = cleanOutput(out, true);
|
||||||
|
|
||||||
expect(lines[0]).to.equal(
|
expect(lines[0]).to.equal(
|
||||||
isV13() ? 'SLUG ALIASES ARCH NAME' : 'SLUG ALIASES ARCH STATE NAME',
|
isV14() ? ' Slug Aliases Arch Name ' : 'SLUG ALIASES ARCH NAME',
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(lines).to.have.lengthOf.at.least(2);
|
expect(lines).to.have.lengthOf.at.least(2);
|
||||||
|
expect(lines).to.contain('intel-nuc nuc amd64 Intel NUC');
|
||||||
expect(lines).to.contain(
|
expect(lines).to.contain(
|
||||||
isV13()
|
'odroid-xu4 odroid-ux3, odroid-u3+ armv7hf ODROID-XU4',
|
||||||
? 'intel-nuc nuc amd64 Intel NUC'
|
|
||||||
: 'intel-nuc nuc amd64 RELEASED Intel NUC',
|
|
||||||
);
|
);
|
||||||
expect(lines).to.contain(
|
|
||||||
isV13()
|
|
||||||
? 'odroid-xu4 odroid-ux3, odroid-u3+ armv7hf ODROID-XU4'
|
|
||||||
: 'odroid-xu4 odroid-ux3, odroid-u3+ armv7hf RELEASED ODROID-XU4',
|
|
||||||
);
|
|
||||||
|
|
||||||
// Discontinued devices should be filtered out from results
|
|
||||||
expect(lines.some((l) => l.includes('DISCONTINUED'))).to.be.false;
|
|
||||||
|
|
||||||
// Beta devices should be listed as new
|
|
||||||
expect(lines.some((l) => l.includes('BETA'))).to.be.false;
|
|
||||||
expect(lines.some((l) => l.includes('NEW'))).to.equal(
|
|
||||||
isV13() ? false : true,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(err).to.eql([]);
|
expect(err).to.eql([]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user