Compare commits

...

749 Commits

Author SHA1 Message Date
049e1da53e v18.2.10 2024-06-21 20:12:54 +00:00
2c0b4072ae Merge pull request #2776 from balena-io/renovate/actions-download-artifact-4.1.x
Update actions/download-artifact action to v4.1.7
2024-06-21 20:12:05 +00:00
15c0c32a01 Update actions/download-artifact action to v4.1.7
Update actions/download-artifact from 4.1.0 to 4.1.7

Change-type: patch
2024-06-21 19:48:21 +00:00
8f2c7f9dbf v18.2.9 2024-06-21 19:13:44 +00:00
90982256c7 Merge pull request #2773 from balena-io/renovate/actions-setup-python-digest
Update actions/setup-python digest to 65d7f2d
2024-06-21 19:12:42 +00:00
73220206a2 Update actions/setup-python digest to 65d7f2d
Update actions/setup-python

Change-type: patch
2024-06-21 18:48:07 +00:00
8b453aae89 v18.2.8 2024-06-21 18:13:51 +00:00
d85d5933fb Merge pull request #2775 from balena-io/renovate/actions-upload-artifact-digest
Update actions/upload-artifact digest to 6546280
2024-06-21 18:12:44 +00:00
2cd455ff81 Update actions/upload-artifact digest to 6546280
Update actions/upload-artifact

Change-type: patch
2024-06-21 17:49:06 +00:00
066cbaf35f v18.2.7 2024-06-21 17:16:21 +00:00
17fa888fea Merge pull request #2772 from balena-io/renovate/pin-dependencies
Pin dependencies
2024-06-21 17:15:25 +00:00
f50287873a Pin dependencies
Update actions/setup-node

Change-type: patch
2024-06-21 16:47:51 +00:00
edff14fa72 v18.2.6 2024-06-21 15:23:25 +00:00
9de753d9d3 Merge pull request #2770 from balena-io/bump-oclif-core
Update @oclif/core from 3.26.9 to 3.27.0
2024-06-21 15:22:19 +00:00
75d2d7d375 Update @oclif/core from 3.26.9 to 3.27.0
Change-type: patch
2024-06-21 09:49:38 -03:00
d9b193acc1 v18.2.5 2024-06-21 12:11:21 +00:00
2e42999642 Merge pull request #2769 from balena-io/bump-ts
Update TypeScript to 5.5.2
2024-06-21 12:10:18 +00:00
5a3f0ea453 Limit @oclif/core to ~3.26 so that npm dedupe doesn't auto-bump it
Change-type: patch
2024-06-21 01:37:00 +03:00
e1cd30060c Deduplicate dependencies 2024-06-21 01:31:53 +03:00
7959e23cd3 Update TypeScript to 5.5.2
Change-type: patch
2024-06-21 00:18:36 +03:00
9c4d788d6d v18.2.4 2024-05-17 12:04:11 +00:00
181f5a6a2f Merge pull request #2767 from balena-io/aethernet-patch-1
patch: fix outdated doc for "os configure"
2024-05-17 12:03:17 +00:00
163dcf596e patch: fix outdated doc for "os configure"
There were an outdated warning for `os configure` on windows.
The command actually works fine on windows.

see: https://balena.zulipchat.com/#narrow/stream/403752-channel.2Fsupport-help/topic/Cytiva.20image.20downloads/near/438786503
2024-05-17 13:39:22 +02:00
1724187466 v18.2.3 2024-05-15 13:32:53 +00:00
b27dcdd582 Merge pull request #2765 from balena-io/dfunckt-patch-1
Pluralize command categories in docs
2024-05-15 13:31:57 +00:00
c28039a3f2 Fix exitCode type 2024-05-15 16:01:38 +03:00
233bc705de Pluralize command categories in docs
Pluralize command categories, eg. "Device" -> "Devices”.

Results in much fewer categories in our docs pages and we no longer have the somewhat silly separate singular and plural categories for commands such as `devices` and `device <command>`.

Change-type: patch
2024-05-15 15:49:57 +03:00
71518678e1 v18.2.2 2024-04-30 13:57:49 +00:00
88a705c935 Merge pull request #2754 from balena-io/upgrade_dockerode
Upgrade dockerode and docker-modem dependencies
2024-04-30 13:56:52 +00:00
55d06aced2 Deduplicate npm dependencies
Signed-off-by: Ken Bannister <kb2ma@runbox.com>
2024-04-29 08:20:02 -04:00
aa9a148c46 Upgrade dockerode and docker-modem dependencies
Includes test fixes due to an interface change in docker-modem.

Change-type: patch
Signed-off-by: Ken Bannister <kb2ma@runbox.com>
2024-04-29 07:56:53 -04:00
10ca5b4f59 v18.2.1 2024-04-23 12:08:57 +00:00
47e11d5f9b Merge pull request #2758 from balena-io/kyle/actuated
Use Actuated runners for Linux test and publish
2024-04-23 12:07:47 +00:00
6fb65bcf22 Re-run npm install and npm dedupe
Signed-off-by: Kyle Harding <kyle@balena.io>
2024-04-22 17:56:20 +03:00
954de13b10 Use Actuated runners for Linux test and publish
Change-type: patch
Signed-off-by: Kyle Harding <kyle@balena.io>
2024-04-19 15:27:38 -04:00
f81a27e931 v18.2.0 2024-04-17 16:13:12 +00:00
e8815d0275 Merge pull request #2756 from balena-io/build-optional-arch
build: Auto-resolve the cpu arch when the --deviceType is provided
2024-04-17 19:12:11 +03:00
766e6d4e5e build: Auto-resolve the cpu arch when the --deviceType is provided
Change-type: minor
2024-04-17 18:10:30 +03:00
7b46f65a01 v18.1.10 2024-04-16 14:17:07 +00:00
db8df0ac35 Merge pull request #2755 from balena-io/node-20-6
Mark node 20.6.0 as the minimum working version
2024-04-16 17:16:00 +03:00
7c7f46fe2b Deduplicate dependencies 2024-04-16 16:46:25 +03:00
b29aae1821 Mark node 20.6.0 as the minimum working version
Change-type: patch
2024-04-16 15:42:41 +03:00
0b10701015 v18.1.9 2024-04-10 15:24:31 +00:00
1dbe08d7e0 Merge pull request #2748 from balena-io/kyle/renovate-npm-dedupe
Enable npm dedupe as part of Renovate postUpdateOptions
2024-04-10 18:23:33 +03:00
d01461ff3e Deduplicate dependencies 2024-04-10 17:39:18 +03:00
2a970478bd Enable npm dedupe as part of Renovate postUpdateOptions
See: https://docs.renovatebot.com/configuration-options/#postupdateoptions
Change-type: patch
Signed-off-by: Kyle Harding <kyle@balena.io>
2024-04-10 10:03:01 -04:00
ffd44d3fec v18.1.8 2024-04-09 17:29:48 +00:00
df51f87fbc Merge pull request #2752 from balena-io/shrinkwrap-v3
npm-shrinkwrap.json: Recreate with lockfileVersion 3
2024-04-09 20:28:45 +03:00
6178f34f88 Bump patch-package to 6.5.1
Change-type: patch
2024-04-09 19:25:17 +03:00
c5ecf692bb npm-shrinkwrap.json: Recreate with lockfileVersion 3
npm ci && \
  rm npm-shrinkwrap.json && \
  npm shrinkwrap

I confirmed that it generates the same
node_modules structure but cuts the
shrinkwrap file size by 43%.

Change-type: patch
See: https://docs.npmjs.com/cli/v9/configuring-npm/package-lock-json#lockfileversion
2024-04-09 19:14:11 +03:00
87f5f18721 v18.1.7 2024-04-09 16:03:36 +00:00
e33810b448 Merge pull request #2753 from balena-io/preload-drop-unused-deps
Update balena-preload to 15.0.5
2024-04-09 19:02:39 +03:00
3caf54aa16 Update balena-preload to 15.0.5
Update balena-preload from 15.0.4 to 15.0.5

Change-type: patch
2024-04-09 18:26:11 +03:00
9d3ee9eb49 v18.1.6 2024-04-09 15:04:42 +00:00
3dac94db70 Merge pull request #2751 from balena-io/drivelist-v12
Update dependencies to get node-addon-api >=7.0.0 to fix builds on node 20.12.0
2024-04-09 15:03:45 +00:00
04b4444fc2 Update expected build warning tests 2024-04-09 17:17:07 +03:00
98514cef09 Deduplicate dependencies 2024-04-09 17:17:07 +03:00
4811031172 Update @oclif/core to 3.26.2
Change-type: patch
2024-04-09 17:17:07 +03:00
be682c7426 Drop the keep-alive package in favor of node's setKeepAlive defaults
Since node 12.17.0 setKeepAlive also sets
TCP_KEEPCNT=10 (vs 5 that we had)
TCP_KEEPINTVL=1 (vs 5s that we had)

Change-type: patch
See: https://nodejs.org/docs/latest-v14.x/api/net.html#net_socket_setkeepalive_enable_initialdelay
See: https://github.com/balena-io/balena-cli/pull/1220
2024-04-09 17:17:07 +03:00
c6827ee51d Update balena-preload to v15.0.4
Change-type: patch
2024-04-09 17:17:07 +03:00
2cba3bbc22 Update resin-cli-form to v3
Change-type: patch
2024-04-09 17:17:07 +03:00
933eacf275 Update resin-cli-visuals to v2
Change-type: patch
2024-04-09 17:17:07 +03:00
e7869f4c6d Update balena-device-init to v7.0.1
Change-type: patch
2024-04-09 17:17:07 +03:00
1a246a9ba5 Update etcher-sdk to v9.0.8
Change-type: patch
2024-04-09 17:16:30 +03:00
e26895085d Mark bin/dev & bin/run as executable
Change-type: patch
2024-04-09 17:16:30 +03:00
71345a8cc1 v18.1.5 2024-03-14 15:52:40 +00:00
619f605eb2 Merge pull request #2747 from balena-io/move-claw-as-dev-dep
Move klaw library to dev dependency
2024-03-14 15:51:48 +00:00
bb4713ab9a Move klaw library to dev dependency
Change-type: patch
2024-03-14 11:41:10 -03:00
168bddf7db v18.1.4 2024-03-14 14:34:50 +00:00
24076e4f8d Merge pull request #2746 from balena-io/update-balena-lint
Update @balena/lint to 8.0.0
2024-03-14 14:33:48 +00:00
634ad156ce Update @balena/lint to 8.0.0
Change-type: patch
2024-03-14 10:00:52 -04:00
6ebeb97917 v18.1.3 2024-03-14 12:57:24 +00:00
cb444998cd Merge pull request #2742 from balena-io/adds-runjs-for-plugin-compatibility
Use standard oclif run.js & dev.js
2024-03-14 09:56:33 -03:00
742c015f21 Use standard oclif run.js & dev.js
Change-type: patch
2024-03-14 09:21:11 -03:00
556e50c87c v18.1.2 2024-03-13 13:49:11 +00:00
3294f78b00 Merge pull request #2743 from balena-io/moves-signing-to-pretarball
Move macos binary signing to oclif pretarball lifecycle
2024-03-13 10:48:14 -03:00
7f11805a7f Move macos binary signing to oclif pretarball lifecycle
Change-type: patch
2024-03-13 09:40:30 -03:00
42dd732f68 v18.1.1 2024-03-12 17:39:15 +00:00
aed50480c3 Merge pull request #2745 from balena-io/removes-tmp-workaround-for-windows-runners
Remove patching tmp for windows runners
2024-03-12 17:38:18 +00:00
6515d6ae10 Remove patching tmp for windows runners
See: https://github.com/balena-io/balena-cli/pull/1298/files#r297236577
Change-type: patch
2024-03-12 12:02:32 -03:00
7903c82821 v18.1.0 2024-03-12 14:21:30 +00:00
eee8a0ecca Merge pull request #2744 from balena-io/adds-arm64-macos-builds
Add support for macos arm64 builds
2024-03-12 11:20:36 -03:00
38a2817587 Add support for macos arm64 builds
Change-type: minor
2024-03-11 20:49:29 -03:00
2bd0641d5f v18.0.4 2024-03-11 22:19:10 +00:00
122a763f82 Merge pull request #2739 from balena-io/update-dependencies
Update dependencies
2024-03-11 22:18:03 +00:00
756f6b328b Update dependencies
Change-type: patch
2024-03-11 16:35:32 -03:00
eb9db6f7b4 v18.0.3 2024-03-11 14:25:51 +00:00
6f9e5a697c Merge pull request #2740 from balena-io/removes-signing-patches
Removes signing patches
2024-03-11 14:24:40 +00:00
f9f41eef4b Removes signing patches
Change-type: patch
2024-03-11 10:53:54 -03:00
5371fea588 v18.0.2 2024-03-07 19:40:27 +00:00
bacb55a1ea Merge pull request #2735 from balena-io/removes-patches-windows
Removes no longer needed patch
2024-03-07 19:39:38 +00:00
ecfd4a260e Remove no longer needed windows oclif patches
Change-type: patch
2024-03-07 15:51:00 -03:00
1525822239 v18.0.1 2024-03-07 16:30:17 +00:00
1614d9b2c8 Merge pull request #2733 from balena-io/fix-windows-signing
Fix windows signing
2024-03-07 16:29:07 +00:00
2e061845ae Fix windows signing
Change-type: patch
2024-03-07 13:01:08 -03:00
9e4dd3fce2 v18.0.0 2024-02-06 12:19:41 +00:00
b2590136fc Merge pull request #2720 from balena-io/bump-node-20
Update to Node 20
2024-02-06 12:18:34 +00:00
bf5e61a61c Update to Node 20
Change-type: major
2024-02-05 18:29:02 -03:00
f550d0c596 v17.5.1 2024-01-31 01:05:23 +00:00
54302669b8 Merge pull request #2725 from balena-io/livepush-fix
Fix target state construction with livepush
2024-01-30 20:04:28 -05:00
a4a4e33d7b Dedupe dependencies 2024-01-30 11:17:21 -03:00
8d6a621bfb Fix target state construction with livepush
When constructing the target state after a reported change from livepush, the
handler function would not pass all build tasks to the function that
constructs the target state, causing a TypeError when trying to obtain
the target image name for each service. This updates the handler to pass
all build tasks, ensuring the information is available to construct the
target state.

Relates-to: #2724
Change-type: patch
2024-01-30 11:03:37 -03:00
4b2602676b v17.5.0 2024-01-23 15:52:54 +00:00
b0810c0f85 Merge pull request #2722 from balena-io/draft-hup
device os-update: Enable updates to pre-release versions of higher base semver
2024-01-23 17:51:54 +02:00
97a6013537 Deduplicate dependencies 2024-01-23 16:55:36 +02:00
1ba8db1459 os versions: Add the --include-draft option
Change-type: minor
2024-01-23 16:55:36 +02:00
cdada0aec8 device os-update: Add option for including pre-release versions in the list
Change-type: minor
2024-01-23 16:55:36 +02:00
1166533482 device os-update: Enable updates to pre-release versions of higher base semver
Change-type: minor
Depends-on: https://github.com/balena-io/balena-sdk/pull/1398
See: https://balena.fibery.io/Work/Task/cli-Enable-OS-Updates-to-pre-release-OS-versions-1751
2024-01-23 16:55:36 +02:00
01538728cd Update balena-sdk to 19.4.0
Update balena-sdk from 19.0.1 to 19.4.0

Change-type: minor
2024-01-23 16:55:36 +02:00
3a7f6d78b0 v17.4.12 2024-01-18 10:55:53 +00:00
dce48c90e9 Merge pull request #2719 from balena-io/revert_dockerode
Revert upgrade to dockerode to avoid regression
2024-01-18 05:54:22 -05:00
fe70d164c1 Deduplicate dependencies 2024-01-15 22:38:12 +00:00
09e2550b32 Regression described in GitHub Issue 2715; balena push hangs in
local mode.

Change-type: patch
Signed-off-by: Ken Bannister <kb2ma@runbox.com>
2024-01-15 22:05:05 +00:00
07854c3d42 v17.4.11 2024-01-05 23:01:11 +00:00
858a455501 Merge pull request #2717 from balena-io/update-oclif-patch
Mark the oclif package patch as a dev only one
2024-01-05 23:00:17 +00:00
4e5eb4bcee Exclude the oclif package patch from the published files
Change-type: patch
2024-01-06 00:25:56 +02:00
696bad3ed6 Update the @oclif/core patch
Change-type: patch
2024-01-06 00:25:34 +02:00
9a9d0f02ef Deduplicate dependencies 2024-01-06 00:25:34 +02:00
f46d00640b v17.4.10 2024-01-02 12:41:44 +00:00
e369bd3599 Merge pull request #2716 from balena-io/normalize-v-version-prefix
Normalize v prefixes in the --version parameter of all commands
2024-01-02 14:40:45 +02:00
75b29112a7 Deduplicate dependencies 2024-01-02 13:55:40 +02:00
b7b01ecd53 Normalize v prefixes in the --version parameter of all commands
Change-type: patch
2024-01-02 13:33:38 +02:00
801a25995c v17.4.9 2023-12-19 23:02:33 +00:00
8296dea78c Merge pull request #2713 from balena-io/fix-ci
Fix publishing artifacts to gh release
2023-12-19 23:01:35 +00:00
1da5a75c14 Fix publishing artifacts to gh release
Change-type: patch
2023-12-19 19:10:06 -03:00
166de57179 v17.4.8 2023-12-19 21:59:10 +00:00
85dece9e95 Merge pull request #2714 from balena-io/kyle/patch
Remove repo config from flowzone.yml
2023-12-19 21:58:24 +00:00
bfbc71215c Remove repo config from flowzone.yml
This functionality is being deprecated in Flowzone.

See: https://github.com/product-os/flowzone/pull/833

Change-type: patch
Signed-off-by: Kyle Harding <kyle@balena.io>
2023-12-19 15:20:04 -05:00
d243c14d74 v17.4.7 2023-12-19 14:26:31 +00:00
804eb27551 Merge pull request #2712 from balena-io/add-request-rerties-to-balena-deploy
deploy: Add rate-limiting aware retries for failed requests
2023-12-19 14:25:32 +00:00
4266dc6951 deploy: Add rate-limiting aware retries for failed requests
Change-type: patch
2023-12-19 01:16:42 +02:00
0ba3522584 Update dependencies
Update @balena/compose from 3.0.5 to 3.2.0

Also updates pinejs-client-request to support
using the Retry-After header and dockerode
to 3.3.5 to be aligned with @balena/compose.

Change-type: patch
2023-12-19 01:16:42 +02:00
19b0e9489d Deduplicate node modules 2023-12-19 01:16:42 +02:00
d9fed9c34c v17.4.6 2023-12-08 15:55:53 +00:00
81ee9f397f Merge pull request #2711 from balena-io/bump-oclif-core
Bump oclif core & use default missing flag handler
2023-12-08 15:54:47 +00:00
b9722c6796 Bump oclif core & use default missing flag handler
Change-type: patch
2023-12-08 12:06:54 -03:00
29ade0f696 v17.4.5 2023-12-04 14:08:31 +00:00
d5ae612513 Merge pull request #2707 from balena-io/bump-ts
Update TypeScript to 5.3.2
2023-12-04 14:07:30 +00:00
65ba63d1a8 Stop testing dependency deduplication on the custom test runners
That's since we already run that test as part of
flowzone's default "Test npm (18.x)", and the
custom tests are using the latest node & npm
version of the selected major.

Change-type: patch
2023-12-04 15:39:10 +02:00
f5ffa7d84f Temporarily pin oclif-core to ~3.11.0 to deduplicate the dependencies
Change-type: patch
2023-12-04 15:36:43 +02:00
dac3ace61d Update TypeScript to 5.3.2
Change-type: patch
2023-11-30 00:07:17 +02:00
72459a04d1 v17.4.4 2023-11-20 17:57:23 +00:00
1e83fcf1e3 Merge pull request #2706 from balena-io/fix-balena-block-create
Fix balena block create to actually create a block
2023-11-20 17:56:32 +00:00
b8769bb9e9 Fix balena block create to actually create a block
Change-type: patch
2023-11-20 13:33:35 -03:00
9f52ee8b21 v17.4.3 2023-11-17 15:39:24 +00:00
90b65cd06b Merge pull request #2683 from balena-io/bump-oclif-core-to-v3
Bump oclif-core to v3
2023-11-17 15:38:25 +00:00
72a924f00e Bump oclif-core to v3
Change-type: patch
2023-11-16 15:06:24 -03:00
e4624eda10 v17.4.2 2023-11-15 11:02:24 +00:00
4173cd82e6 Merge pull request #2697 from balena-io/vipul/automate-capitan
Automatically generate Capitano Docs configuration
2023-11-15 11:01:38 +00:00
b393f27e1b Remove authentication directory
Signed-off-by: Vipul Gupta (@vipulgupta2048) <vipulgupta2048@gmail.com>
2023-11-15 15:55:03 +05:30
1a4a0e2439 Move auth commands into command directories
Signed-off-by: Vipul Gupta (@vipulgupta2048) <vipulgupta2048@gmail.com>
2023-11-15 15:10:22 +05:30
4cd8f4c16e Docs: Generate balena auto-completion
Signed-off-by: Vipul Gupta (@vipulgupta2048) <vipulgupta2048@gmail.com>
2023-11-15 13:08:08 +05:30
2de9d526e5 Docs: Move commands to their own directories, away from their categories
Signed-off-by: Vipul Gupta (@vipulgupta2048) <vipulgupta2048@gmail.com>
2023-11-15 13:07:34 +05:30
d9427c3c59 Docs: Generate balenaCLI with automatic configuration
Signed-off-by: Vipul Gupta (@vipulgupta2048) <vipulgupta2048@gmail.com>
2023-11-15 13:07:01 +05:30
fc0cfac475 Docs: Create version command directory
Signed-off-by: Vipul Gupta (@vipulgupta2048) <vipulgupta2048@gmail.com>
2023-11-15 13:06:45 +05:30
99094dbfda Throw error if command directory rules not followed
Signed-off-by: Vipul Gupta (@vipulgupta2048) <vipulgupta2048@gmail.com>
2023-11-15 13:06:35 +05:30
0711eefb7c Update all imports to match new command directory structure
Signed-off-by: Vipul Gupta (@vipulgupta2048) <vipulgupta2048@gmail.com>
2023-11-15 13:06:19 +05:30
dc40b0d969 Docs: Move CLI commands files to command directories
Signed-off-by: Vipul Gupta (@vipulgupta2048) <vipulgupta2048@gmail.com>
2023-11-15 13:05:58 +05:30
4b5def0a8a Docs: Automatically generate Capitano configuration
Change-type: patch
Signed-off-by: Vipul Gupta (@vipulgupta2048) <vipulgupta2048@gmail.com>
2023-11-15 13:05:39 +05:30
f44fa38113 v17.4.1 2023-11-13 19:18:47 +00:00
167dfeb269 Merge pull request #2701 from balena-io/bump-shrinkwrap
Bump shrinkwrap
2023-11-13 19:17:53 +00:00
a816548bb5 Bump shrinkwrap
Change-type: patch
2023-11-13 15:43:13 -03:00
94001efc81 v17.4.0 2023-11-10 16:28:02 +00:00
8bfafe8ecc Merge pull request #2692 from bbugh/device-json
device: Add `--json` option for JSON output
2023-11-10 13:26:59 -03:00
d78045b6ab device: Add --json option for JSON output
change-type: minor
2023-11-10 12:10:53 -03:00
11eabc4b96 v17.3.2 2023-11-10 11:03:00 +00:00
bfaa91c752 Merge pull request #2700 from balena-io/update-@balena/compose-3.0.5
Update @balena/compose to 3.0.5
2023-11-10 13:02:02 +02:00
1b615e4690 Update @balena/compose to 3.0.5
Update @balena/compose from 3.0.2 to 3.0.5

Change-type: patch
2023-11-10 11:05:42 +02:00
7954e13154 v17.3.1 2023-11-09 17:26:42 +00:00
45d8872a82 Merge pull request #2699 from balena-io/pipeline
Use `pipeline` instead of `.pipe` when downloading OS image
2023-11-09 17:25:34 +00:00
56cff46408 Use pipeline instead of .pipe when downloading OS image
Change-type: patch
2023-11-09 13:38:17 -03:00
47a1a9c6af v17.3.0 2023-11-06 20:19:10 +00:00
a434a5e657 Merge pull request #2689 from mlveggo/add_device_start_stop_service
Add device start-service and stop-service commands
2023-11-06 15:18:14 -05:00
221c213791 Add device start-service and stop-service commands
Change-type: minor
2023-11-06 16:13:51 -03:00
d2150c5cb7 v17.2.4 2023-11-06 19:12:02 +00:00
70e113152d Merge pull request #2696 from balena-io/fix-dsutils
Dedupe shrinkwrap
2023-11-06 16:11:13 -03:00
4a64102d67 Dedupe shrinkwrap
Change-type: patch
2023-11-06 14:11:38 -03:00
9bf267166e v17.2.3 2023-11-03 16:34:18 +00:00
f12249bc81 Merge pull request #2694 from balena-io/vipul/add-them-docs
Generate docs for recently supported commands
2023-11-03 16:33:14 +00:00
80d6c71b02 Update CONTRIBUTING.md with docs instructions
Signed-off-by: Vipul Gupta (@vipulgupta2048) <vipulgupta2048@gmail.com>
2023-11-03 20:39:25 +05:30
9ef4117fb8 Generate docs for recently supported commands
Added documentation for `balena fleet pin` and other recently supported commands

Change-type: patch
Signed-off-by: Vipul Gupta (@vipulgupta2048) <vipulgupta2048@gmail.com>
2023-11-03 20:28:19 +05:30
25f3bf1fbe v17.2.2 2023-10-30 12:25:19 +00:00
fe65351666 Merge pull request #2672 from balena-io/bump-eslint
Bump @balena/lint
2023-10-30 12:24:13 +00:00
7d13946c3e Bump balena-lint to 7.2.1
Change-type: patch
2023-10-30 07:45:51 -04:00
1ffa8d38f1 v17.2.1 2023-10-26 21:28:48 +00:00
6914d39370 Merge pull request #2691 from balena-io/bump-balena-sdk
Update balena-sdk and deduped dependencies
2023-10-26 21:27:22 +00:00
04db9c7a91 Update balena-sdk and deduped dependencies
Update balena-sdk from 18.0.0 to 19.0.0
Update balena-preload from 14.0.3 to 15.0.1
Update balena-image-manager from 9.0.2 to 10.0.1

Change-type: patch
2023-10-26 16:27:43 -03:00
c785d01a1c v17.2.0 2023-10-20 13:45:59 +00:00
87ba364f89 Merge pull request #2685 from bbugh/add-releases-json
Add --json option to `release` and `releases`
2023-10-20 10:44:52 -03:00
b7e5915c7a release: Add '--json' option for JSON output
change-type: minor
2023-10-20 07:42:39 -05:00
0cef6b8f87 releases: Add '--json' option for JSON output 2023-10-20 08:05:36 -03:00
71f1dbd80a v17.1.7 2023-10-20 10:49:19 +00:00
36edcf0cb8 Merge pull request #2688 from balena-io/emty-string-env-tag-values
Support empty string values for `env add` and `tag set`
2023-10-20 13:48:23 +03:00
8204dcad93 tag set: Fix using empty string as a value
Change-type: patch
2023-10-20 13:11:12 +03:00
03bcb6cff7 env add: Fix using empty string as a value
Change-type: patch
2023-10-20 13:11:12 +03:00
66b6eed57c Add tests for empty & non-provided value for the env add command
Change-type: patch
2023-10-20 13:11:12 +03:00
99b0f2c022 Add tests for the tag set command
Change-type: patch
2023-10-20 13:06:39 +03:00
947a9bade7 v17.1.6 2023-10-18 18:16:11 +00:00
15ea601d68 Merge pull request #2686 from balena-io/klutchell-patch-3
Allow custom actions for external contributors
2023-10-18 18:14:54 +00:00
ce888966dd Update npm shrinkwrap
Signed-off-by: Kyle Harding <kyle@balena.io>
2023-10-18 13:04:02 -04:00
3fc6eb7a47 Allow custom actions for external contributors
Change-type: patch
2023-10-18 12:53:43 -04:00
73aebf10bf v17.1.5 2023-10-05 10:54:36 +00:00
292c372eb4 Merge pull request #2682 from balena-io/fix-ssh-service-stdin
Fix ssh ignoreStdin device argument
2023-10-05 07:53:30 -03:00
dc3261d9c7 Fix ssh ignoreStdin device argument
Change-type: patch
2023-10-05 07:05:53 -03:00
301d0ab3a0 v17.1.4 2023-09-29 12:40:52 +00:00
efe0a018d7 Merge pull request #2679 from balena-io/bump-oclif-to-latest
Bump oclif to ^3.17.1
2023-09-29 12:39:46 +00:00
fa2a232e5f Bump oclif to ^3.17.1
Change-type: patch
2023-09-28 18:35:59 -03:00
da1f022df9 v17.1.3 2023-09-26 13:58:00 +00:00
8e6a6b81d9 Merge pull request #2671 from balena-io/stop-depending-on-old-oclif-libs
Stop depending on old oclif libs and migrate to @oclif/core directly
2023-09-26 13:56:38 +00:00
77906c4152 Move to @oclif/core v2
Change-type: patch
2023-09-26 09:50:39 -03:00
26bc68753b Remove direct dependency to @oclif/config
Change-type: patch
2023-09-26 09:48:13 -03:00
af6b263f7a v17.1.2 2023-09-26 12:14:31 +00:00
a27e216e44 Merge pull request #2678 from balena-io/fix-preload-with-commit-hash
Fix preload with commit hash
2023-09-26 12:13:24 +00:00
50e1efa448 Fix preload with commit hash
Update balena-preload from 14.0.2 to 14.0.3

Change-type: patch
2023-09-26 07:45:56 -03:00
519ac0383a v17.1.1 2023-09-05 15:06:14 +00:00
3d0ef9bc4f Merge pull request #2669 from balena-io/fixes-local-flash-on-unix
Fix local flash on unix environments
2023-09-05 15:05:16 +00:00
49e23464f9 Fix local flash on unix environments
Update etcher-sdk from 8.5.3 to 8.7.0
Change-type: patch
2023-09-05 11:26:06 -03:00
a1c9b4b80e v17.1.0 2023-09-05 13:01:03 +00:00
2b1be3e5d9 Merge pull request #2647 from balena-io/update-oclif
Update oclif, improve help command
2023-09-05 08:59:52 -04:00
e46378ec51 Update oclif, improve help command
Change-type: minor
2023-09-04 21:07:05 -03:00
27ee9c85e7 v17.0.0 2023-08-29 11:45:54 +00:00
21b6ec46e3 Merge pull request #2667 from balena-io/bump-cli-to-node-v18
Update to Node 18
2023-08-29 11:44:48 +00:00
817ce5dc96 Update to Node 18
Change-type: major
2023-08-29 07:35:53 -03:00
d9af28bca7 v16.8.0 2023-08-25 17:47:44 +00:00
8646be7979 Merge pull request #2665 from balena-io/accept-device-application-keys-as-experimental-feature
Accept device & application keys on login as experimental feature
2023-08-25 17:46:52 +00:00
14ba287e0d Accept device & application keys on login as experimental feature
Change-type: minor
2023-08-23 11:12:06 -03:00
1671e46d99 v16.7.9 2023-08-22 13:33:30 +00:00
507333c463 Merge pull request #2663 from balena-io/bumps-sdk-to-v18
Update balena-sdk to v18
2023-08-22 10:32:34 -03:00
8b320d3e9e Update balena-sdk to v18
Update balena-sdk from 17.21.1 to 18.0.0
Update balena-preload from 14.0.0 to 14.0.2
Update balena-image-manager from 9.0.0 to 9.0.2
Change-type: patch
2023-08-22 09:38:39 -03:00
e1be268749 v16.7.8 2023-08-22 11:16:38 +00:00
1a0019e6d0 Merge pull request #2662 from balena-io/otaviojacobi/bumps-to-sdk-17-12-1
Update balena-sdk to ^17.12.1
2023-08-22 11:15:33 +00:00
e79cdb671f Update balena-settings-storage to 8.1.0
Update balena-settings-storage from 7.0.0 to 8.1.0
Change-type: patch
2023-08-21 14:42:28 -03:00
f38e643cf0 env: Stop fetching unnecessary app fields
Change-type: patch
2023-08-21 14:39:16 -03:00
b8e190cd1d Remove redundant envs documentation
Change-type: patch
2023-08-21 14:39:16 -03:00
9cca654bd5 Update balena-sdk to 17.12.1
Update balena-sdk from 17.8.0 to 17.12.1
Change-type: patch
2023-08-21 14:39:16 -03:00
35177e2d2f v16.7.7 2023-08-21 16:56:33 +00:00
1a24b193e7 Merge pull request #2664 from balena-io/otavio-revert-flowzone-to-master
Revert flowzone to master
2023-08-21 16:55:31 +00:00
272915192b Revert flowzone to master
Change-type: patch
2023-08-17 20:22:53 -03:00
96774f4c52 v16.7.6 2023-07-24 13:38:06 +00:00
a034f585ba Merge pull request #2656 from balena-io/fix-app-create
app create: Fix halting with a deprecation warning
2023-07-24 13:37:00 +00:00
365d95c36b app create: Fix halting with a deprecation warning
Change-type: patch
2023-07-21 10:02:32 +03:00
c6313c08ae v16.7.5 2023-07-21 06:59:22 +00:00
f5764c4659 Merge pull request #2655 from balena-io/abstract-fleet-app-block-create
Abstract the fleet/app/block create commands
2023-07-21 09:58:36 +03:00
aff094575b Abstract the fleet/app/block create commands
Change-type: patch
2023-07-20 16:14:52 +03:00
4aaaf64f8d v16.7.4 2023-07-20 10:41:09 +00:00
7b88ce273f Merge pull request #2654 from balena-io/move-discontinued-dt
move: Include fleets of discontinued device types in the fleet selection
2023-07-20 10:40:12 +00:00
b011af89ad move: Include fleets of discontinued device types in the fleet selection
Change-type: patch
2023-07-20 13:03:54 +03:00
1bf8c1bfe7 v16.7.3 2023-07-20 08:30:09 +00:00
2b39d5d111 Merge pull request #2653 from balena-io/promote-discontinued-dt
promote: Allow joining fleets of discontinued device types
2023-07-20 08:29:20 +00:00
98663af7f6 Rerun npm-shrinkwrap.json deduplication 2023-07-19 19:44:43 +03:00
5628824bee promote: Allow joining fleets of discontinued device types
Change-type: patch
2023-07-19 19:17:27 +03:00
d12d7996bc v16.7.2 2023-07-19 01:43:55 +00:00
0dcf4cbff6 Merge pull request #2650 from balena-io/bump-balena-compose
Update balena-compose to v3.0.2
2023-07-19 01:42:56 +00:00
884e37d242 Update balena-compose to v3.0.2
Update balena-compose to v3.0.2

That release removes the use of the `cachefrom` on pull tasks, which
there is good evidence to suggest is the cause of #2165

Change-type: patch
2023-07-18 18:14:56 -04:00
f4a24e26c3 v16.7.1 2023-07-18 20:27:07 +00:00
122eccf3dc Merge pull request #2652 from balena-io/update-balena-sdk-17.8.0
Update balena-sdk to 17.8.0
2023-07-18 20:26:16 +00:00
bd598788dc Update balena-sdk to 17.8.0
Update balena-sdk from 17.0.0 to 17.8.0

Change-type: patch
2023-07-18 22:48:54 +03:00
406482b4da v16.7.0 2023-07-17 19:59:52 +00:00
a381c97ca9 Merge pull request #2649 from balena-io/preload-no-pin-device-to-release
preload: Add the --no-pin-device-to-release flag to avoid interactive questions
2023-07-17 19:59:04 +00:00
8ce78ba33c Rerun npm-shrinkwrap.json deduplication 2023-07-17 11:25:02 +03:00
f53f148c89 preload: Add the --no-pin-device-to-release flag to avoid interactive questions
Change-type: minor
See: https://balena.zulipchat.com/#narrow/stream/345746-aspect.2Fproduct/topic/Level.20-.20need.20thoughts.20on.20questions.20.26.20feature.20suggestions
2023-07-17 11:19:03 +03:00
0086feb645 v16.6.6 2023-07-10 17:16:08 +00:00
4ee55b049f Merge pull request #2646 from balena-io/reduce-lodash-usage
Reduce lodash usage in common user interaction patterns
2023-07-10 17:15:14 +00:00
90c6f121cc Rerun npm-shrinkwrap.json deduplication 2023-07-10 19:36:36 +03:00
d3c27ae859 Reduce lodash usage in common user interaction patterns
Change-type: patch
2023-07-10 17:19:01 +03:00
8f39c1de6c v16.6.5 2023-07-09 21:29:55 +00:00
4df1831187 Merge pull request #2645 from balena-io/application-create-hostApp-based-supported-DTs
fleet/block/app create: Fetch the supported device types using the hostApps
2023-07-09 21:29:02 +00:00
2bce761ace Rerun npm-shrinkwrap.json deduplication 2023-07-07 20:17:53 +03:00
d78b76aceb fleet/block/app create: Fetch the supported device types using the hostApps
Change-type: patch
See: https://balena.zulipchat.com/#narrow/stream/360838-balena-io.2Fos.2Fdevices/topic/state.20field.20in.20device-type.2Ejson
See: https://balena.fibery.io/Organisation/Improvements-849#Improvements/Stop-relying-on-device-types-v1-device-type.json-for-unrelated-things-257
2023-07-07 19:57:36 +03:00
f07f6b84d4 v16.6.4 2023-07-06 13:58:27 +00:00
d297a10570 Merge pull request #2643 from balena-io/bump-balena-compose
Bump balena-compose to v2.3.0
2023-07-06 13:57:37 +00:00
9d0b82122a Bump balena-compose to v2.3.0
This allows the the CLI to use docker registry config when querying the
images manifest.

Relates-to: balena-io-modules/balena-compose#31
Change-type: patch
2023-07-05 15:46:42 -04:00
338477463a v16.6.3 2023-06-30 17:07:34 +00:00
d1275760fa Merge pull request #2641 from balena-io/drop-toolbelt
Pin dockerode and drop docker-toolbelt
2023-06-30 20:06:46 +03:00
0f4054fa4d Remove redundant dependency on docker-toolbelt
Change-type: patch
2023-06-30 19:22:07 +03:00
7545fc5d6e Pin dockerode to v3.3.3
v3.3.4 introduces a regression that is fixed by https://github.com/apocas/dockerode/pull/695 but Dockerode has not published a version that includes the fix yet. Pin the dependency to ensure we don’t ever update to a broken version.

Change-type: patch
2023-06-30 19:19:52 +03:00
a1f25809cb v16.6.2 2023-06-29 12:27:14 +00:00
e0a3c4bd95 Merge pull request #2640 from balena-io/make-apple-notarization-team-id-public
macos notarization: Expose team ID instead of keeping it in secrets
2023-06-29 12:26:27 +00:00
d843e75512 macos notarization: Expose team ID instead of keeping it in secrets
Change-type: patch
2023-06-29 07:41:07 -04:00
72c57608d5 v16.6.1 2023-06-28 12:47:56 +00:00
d9de7636db Merge pull request #2639 from balena-io/update-electron-notarize
Drop `electron-notarize` dependency in favor of `@electron/notarize`
2023-06-28 12:47:13 +00:00
10b5af6967 Drop electron-notarize dependency in favor of @electron/notarize
Change-type: patch
2023-06-28 08:01:39 -04:00
51c050c725 v16.6.0 2023-06-26 13:05:11 +00:00
eb52c47de5 Merge pull request #2631 from balena-io/add-api-key-revoke-command
api-key: Add `revoke` command which accepts a list of API key ids
2023-06-26 09:04:21 -04:00
4b1378dfbc api-key: Add revoke command which accepts a list of API key ids
Change-type: minor
2023-06-26 08:21:23 -04:00
1a77d86347 v16.5.2 2023-06-09 16:03:10 +00:00
bd5188f4b9 Merge pull request #2637 from balena-io/klutchell-patch-2
Re-enable automatic github final releases
2023-06-09 16:02:22 +00:00
034f459bfa Update npm-shrinkwrap
Signed-off-by: Kyle Harding <kyle@balena.io>
2023-06-09 11:23:49 -04:00
bc405d997e Re-enable automatic github final releases
Change-type: patch
2023-06-09 11:05:01 -04:00
af27cf2cbe v16.5.1 2023-06-02 06:30:00 +00:00
83b9bf67c2 Merge pull request #2636 from balena-io/bump-ts
Update TypeScript to 5.1.3
2023-06-02 06:29:11 +00:00
abd73b805b Update TypeScript to 5.1.3
Change-type: patch
2023-06-01 21:58:34 +03:00
37bfd4db98 v16.5.0 2023-05-25 16:45:02 +00:00
be74143d5f Merge pull request #2633 from balena-io/add-block-create-command
Add `balena block create` command for creating Blocks
2023-05-25 16:44:19 +00:00
9975e5d9ac Add balena block create command for creating Blocks
Change-type: minor
2023-05-25 12:00:39 -04:00
1341413966 v16.4.0 2023-05-25 15:53:23 +00:00
1a5b914a6f Merge pull request #2632 from balena-io/add-app-create-command
Add `balena app create` command for creatings Apps
2023-05-25 15:52:37 +00:00
c5e8f0d6ea Add balena app create command for creatings Apps
Change-type: minor
2023-05-25 11:09:02 -04:00
3a143fe413 v16.3.0 2023-05-25 15:06:39 +00:00
3445e4a08e Merge pull request #2630 from balena-io/add-command-to-list-user-api-keys
Add `balena api-keys` command for listing user API keys
2023-05-25 15:05:38 +00:00
166130c3df Add balena api-keys command for listing user/fleet API keys
Change-type: minor
2023-05-25 09:11:36 -04:00
c3a8a905f7 v16.2.7 2023-05-24 12:04:59 +00:00
2b878e87d8 Merge pull request #2629 from balena-io/less-requests-2
Fetch the application and its devices in one request in balena devices, device init, ssh, tunnel
2023-05-24 12:04:05 +00:00
063e9d40f0 device init: Avoid extra request when not providing the --fleet option
Change-type: patch
2023-05-24 14:22:04 +03:00
2b58143164 devices: Use a single request when providing the --fleet parameter
Reduces the response time when using
--fleet from 1.5s to 1s.

Change-type: patch
2023-05-24 14:22:04 +03:00
861d4f33b7 ssh,tunnel: Fetch the fleet & devices in one request
Change-type: patch
2023-05-24 14:22:04 +03:00
81f4aae7d2 v16.2.6 2023-05-24 11:17:30 +00:00
46ab335407 Merge pull request #2626 from balena-io/less-requests
Stop fetching unnecessary fields
2023-05-24 14:16:42 +03:00
07cb0cbfcd device init: Stop fetching unnecessary app fields
Change-type: patch
2023-05-24 13:37:19 +03:00
a2392dc580 device move: Stop fetching unnecessary device & app fields
Reduces the amount of device data retrieved
by 66%.

Change-type: patch
2023-05-24 13:37:19 +03:00
8b3235ab2b device register: Stop fetching unnecessary app fields
Change-type: patch
2023-05-24 13:37:19 +03:00
15dac6f194 config generate: Stop fetching unnecessary app fields
Change-type: patch
2023-05-24 13:37:19 +03:00
3c93db8449 devices: Stop fetching unnecessary device fields
Change-type: patch
2023-05-24 13:37:19 +03:00
9d8df0b781 fleet rm,restart,rename,purge: Stop fetching unnecessary app fields
Halves the amout of application data retrieved.

Change-type: patch
2023-05-24 13:37:19 +03:00
bcadbdbed8 push: Stop unnecessarily fetching the application name
Change-type: patch
2023-05-24 13:37:19 +03:00
05a96fa60e ssh,tunnel: Reduce the amount of application fields fetched
Halves the amout of application data retrieved.

Change-type: patch
2023-05-24 13:37:19 +03:00
2e37536e7a orgs: Stop fetching unnecessary org fields
Halves the amount of org data retrieved to
show the list of orgs to select from.

Change-type: patch
2023-05-24 13:37:19 +03:00
025c4ef7f2 fleet create: Reduce the amount of org fields fetched
Halves the amount of org data retrieved to
show the list of orgs to select from.

Change-type: patch
2023-05-24 13:37:19 +03:00
ecbc660bf5 Fetch only the app slug when resolving an app by name
Affects many env & tags commands, as well
as support, releases & preload.

Change-type: patch
2023-05-24 13:37:19 +03:00
ba1f17d537 v16.2.5 2023-05-24 10:30:21 +00:00
3ab8f7500e Merge pull request #2628 from balena-io/73-fix-device-init-os-initialize
Fix device int & os initialize failing to initialize the drive list
2023-05-24 13:29:36 +03:00
0a25bec010 Fix device int & os initialize failing to initialize the drive list
Resolves: #2627
Change-type: patch
2023-05-24 12:47:06 +03:00
01e765e670 v16.2.4 2023-05-23 20:31:27 +00:00
61844f2386 Merge pull request #2625 from balena-io/app-select-reduce-requests
Remove extra request when filling the application selection list
2023-05-23 20:30:32 +00:00
46aa08c953 Remove extra request when filling the application selection list
Saves one request of about 450ms on the
init and move commands.

Change-type: patch
2023-05-23 22:47:10 +03:00
b6c7fb82c3 v16.2.3 2023-05-23 19:46:47 +00:00
fcda09009a Merge pull request #2624 from balena-io/improve-typings
Use stricter typings
2023-05-23 19:46:02 +00:00
1a6fe1f3de Use stricter typings
Change-type: patch
2023-05-23 18:57:54 +03:00
98e91c0607 v16.2.2 2023-05-23 14:02:36 +00:00
bed2387d83 Merge pull request #2623 from balena-io/fix-setting-service-env-vars-by-app-name
env add: Fix accepting fleet names when setting service vars
2023-05-23 14:01:21 +00:00
50e852acee env add: Fix accepting fleet names when setting service vars
Change-type: patch
2023-05-23 15:59:41 +03:00
da30623e4e v16.2.1 2023-05-23 12:28:25 +00:00
7a46b367a7 Merge pull request #2621 from balena-io/sdk-v17
Update balena-sdk to 17.0.0
2023-05-23 12:27:29 +00:00
d9651c7393 Update balena-settings-client to 5.0.2
Change-type: patch
2023-05-23 13:22:38 +03:00
e371b1e759 Update balena-preload & balena-image-manager
Update balena-image-manager from 8.0.1 to 9.0.0
Update balena-preload from 13.0.0 to 14.0.0

Change-type: patch
2023-05-23 13:22:38 +03:00
77cf4af166 Update balena-sdk to 17.0.0
Update balena-sdk from 16.45.1 to 17.0.0

Change-type: patch
2023-05-23 13:22:38 +03:00
9d197317ca v16.2.0 2023-05-19 18:11:57 +00:00
9a8b0b4a0d Merge pull request #2619 from balena-io/alexgg/sb
os configure, config generate: Add '--secureBoot' option to opt-in secure boot
2023-05-19 18:11:02 +00:00
0c62b9ef08 Deduplicate npm-shrinkwra.json 2023-05-19 20:28:12 +03:00
83a5e7392a secureboot: Retrieve the OS release & contract in one request
Change-type: patch
2023-05-19 19:22:23 +03:00
f0c8c37022 os configure, config generate: Add '--secureBoot' option to opt-in secure boot
Allow to generate a config file with `installer.secureboot` set so that
a secure boot and disk encrypted system can be installed.

Change-type: minor
Signed-off-by: Alex Gonzalez <alexg@balena.io>
2023-05-19 18:10:00 +02:00
ba26d3204d package.json: Update balena-sdk to 16.44.2
Update balena-sdk from 16.40.0 to 16.44.2

Change-type: patch
Signed-off-by: Alex Gonzalez <alexg@balena.io>
2023-05-19 18:10:00 +02:00
d53542975e flowzone: update custom runs to use macos-12
After the flowzone update to use zstd as compression algorithm for sources
there is an error on macos-11 as tar does not support it.

Change-type: patch
Signed-off-by: Alex Gonzalez <alexg@balena.io>
2023-05-19 11:33:31 +02:00
632296a271 v16.1.0 2023-05-16 18:26:10 +00:00
3e089fcdb2 Merge pull request #2616 from balena-io/ab77/operational
build linux/arm packages
2023-05-16 18:25:27 +00:00
d61c300750 build linux/arm packages
change-type: minor
2023-05-16 10:39:00 -07:00
a0a97c5f40 v16.0.0 2023-05-16 00:02:37 +00:00
165f3b83ca Merge pull request #2618 from balena-io/node-16
Update to Node 16
2023-05-16 00:01:48 +00:00
5bf95300ee support: Change the printed support expiry date in ISO 8601 UTC format
Change-type: major
2023-05-12 19:00:10 +03:00
adb460b270 logs: Change the timestamp format to ISO 8601 UTC
Resolves: #2608
Change-type: major
2023-05-12 19:00:10 +03:00
ca80bd52fe Pin flowzone to v4.7.1
The macos-11 runners apparently do not support zst compression format as
added in flowzone 4.7.2. While support is rolled out, we can keep
the flowzone branch to the previous working version.

Change-type: patch
2023-05-11 19:10:10 -04:00
281f8abb9a Update etcher-sdk to v8.5.3
This removes the dependency on our custom fork of [node-usb](https://github.com/balena-io-modules/node-usb)
and uses the maintained building method of the official node-usb repo

Change-type: patch
2023-05-11 18:10:52 -04:00
2cf2918d73 Update vercel/pkg to v5.8.1
This seems to be needed to build the binaries for node v16 since earlier
versions failed with

```
Error: Could not detect abi for version 16.13.0 and runtime node.  Updating "node-abi" might help solve this issue if it is a new release of node
```

Change-type: patch
2023-05-11 17:49:49 -04:00
7dfb7474f5 Update to Node 16
This also drops support for Node 14

Change-type: major
2023-05-11 17:49:37 -04:00
6ee0b48c9a v15.2.3 2023-05-03 20:03:49 +00:00
bd01fbf90c Merge pull request #2614 from balena-io/local-release-uuid
Use valid release uuid for local releases
2023-05-03 20:02:59 +00:00
cd19845b6b Use valid release uuid for local releases
On local push, the CLI uses `localrelease` as the `commit` property for
the development application. This is not a valid uuid and will not be
read properly by the supervisor, as seen in

https://github.com/balena-os/balena-supervisor/blob/master/src/compose/service.ts#L652

While this is not a problem right now, the commit is becoming the main
way to identify a service release (replacing `releaseId` and `imageId`),
and the invalid release uuid could cause update issues when pushing a
local release on when using some API endpoints.

Change-type: patch
Relates-to: balena-os/balena-supervisor#2136
2023-05-03 15:08:19 -04:00
5545883c3f v15.2.2 2023-04-28 16:16:44 +00:00
75a380b0ba Merge pull request #2615 from balena-io/remove-nvmrc
Remove nvmrc
2023-04-28 16:15:57 +00:00
35fe7c6a58 Remove nvmrc
There is not a lot of benefit to using `.nvmrc` as it still requires
`nvm use`, and not everybody uses `nvm`. The call to `npm install` will
already warn about using the wrong version.

Change-type: patch
2023-04-28 10:27:15 -04:00
69249b3139 v15.2.1 2023-04-28 09:25:50 +00:00
bf897fd56d Merge pull request #2612 from balena-io/sync-tslib
Fix tslib going out of sync causing HUP to fail
2023-04-28 09:25:01 +00:00
150c6e75f5 Fix tslib going out of sync causing HUP to fail
Change-type: patch
2023-04-27 14:07:57 +03:00
e8bc43dc64 v15.2.0 2023-04-05 13:09:24 +00:00
1213689de2 Merge pull request #2606 from balena-io/update-balena-sdk-16.40.0
Add support for device restarts in open-balena
2023-04-05 13:08:14 +00:00
c1017e8e27 Add support for device restarts in open-balena
Update balena-sdk from 16.28.2 to 16.40.0

Change-type: minor
2023-04-05 12:57:33 +03:00
7ad9e685f6 v15.1.3 2023-04-05 08:06:56 +00:00
c778aaffaf Merge pull request #2607 from balena-io/update-balena-sdk-16.28.2
devices supported: Fix showing types without a valid & finalized release
2023-04-05 08:06:05 +00:00
b98047cacf devices supported: Fix showing types without a valid & finalized release
Update balena-sdk from 16.28.0 to 16.28.2

Resolves: #2524
Change-type: patch
2023-04-05 10:19:39 +03:00
03ace6e4b2 v15.1.2 2023-03-27 15:14:47 +00:00
9b4701bcb7 Merge pull request #2601 from balena-io/use-satisfies
Improve type checking by using the satisfies operator
2023-03-27 18:13:56 +03:00
174312977a Improve type checking by using the satisfies operator
Change-type: patch
2023-03-27 16:39:09 +03:00
963d9af817 v15.1.1 2023-03-17 10:20:03 +00:00
af5ec51232 Merge pull request #2600 from balena-io/bump-ts
Update TypeScript to 5.0.2
2023-03-17 12:19:13 +02:00
1cd9fbf6a0 Update TypeScript to 5.0.2
Change-type: patch
2023-03-16 20:53:08 +02:00
72639e9e59 v15.1.0 2023-03-14 20:19:08 +00:00
447dcc1480 Merge pull request #2599 from balena-io/kyle/balena-compose-v2.2.x
Update balena-compose to v2.2.1
2023-03-14 16:18:19 -04:00
564716faa7 Update balena-compose to v2.2.1
Update balena-compose from 2.1.1 to 2.2.1

Change-type: minor
Signed-off-by: Kyle Harding <kyle@balena.io>
2023-03-14 14:59:52 -04:00
3e5b4457c2 v15.0.6 2023-03-13 14:03:48 +00:00
793e70d909 Merge pull request #2597 from balena-io/explicitly-select-devices-fields
Devices: explicitly fetches only used fields
2023-03-13 11:02:51 -03:00
5761a306be Devices: explicitly fetches only used fields
Change-type: patch
2023-03-13 09:35:43 -03:00
adff0f2a0a v15.0.5 2023-03-10 16:25:40 +00:00
4ec45a0c43 Merge pull request #2596 from balena-io/fix-is-legacy-check
Fix isLegacy check which should always relay on the slug
2023-03-10 18:24:48 +02:00
ecf4b046b5 Fix application isLegacy check for rename and deploy
Change-type: patch
2023-03-10 16:33:00 +01:00
b0cae93ac9 v15.0.4 2023-02-21 07:24:19 +00:00
53b66678d4 Merge pull request #2583 from balena-io/hraftery-patch-1-1
Clarify update rate of update-notifier info
2023-02-21 09:23:30 +02:00
0b9b65ef88 patch: Clarify update rate of update notifier info
If the cli has not been run in a while, it will show old update information. It's not obvious why, and this might lead to confusion. So this commit just adds a comment to clarify that out-of-date update notifier info is expected behaviour, and why.
2023-01-26 14:15:43 +11:00
8a84d9d792 v15.0.3 2023-01-18 16:16:39 +00:00
c535b8e1ea Merge pull request #2582 from balena-io/https-npm
Use https for the npm deprecation check, avoiding a redirect
2023-01-18 16:15:01 +00:00
234fb6cd39 Use https for the npm deprecation check, avoiding a redirect
Change-type: patch
2023-01-18 13:11:31 +00:00
8714830b48 v15.0.2 2023-01-14 07:35:13 +00:00
0e07b36691 Merge pull request #2580 from balena-io/joshbwlng/fix-typo
Fix push --nolive doc typo
2023-01-14 09:33:56 +02:00
ba80d3c38c Fix push --nolive doc typo
Change-type: patch
2023-01-13 13:36:44 +09:00
e65dc82cfe v15.0.1 2023-01-10 13:43:24 +00:00
bc727521c6 Merge pull request #2571 from balena-io/nodejs-14
Update to Node 14
2023-01-10 08:41:54 -05:00
a8c0c884d3 Extra linting 2023-01-03 16:08:10 -03:00
b11c7157d3 Update to node 14 2023-01-03 16:04:24 -03:00
578de7bcd4 Process livepush build logs inline
When using livepush, the CLI parses the build logs to obtain the stage
image ids, which are necessary for properly running livepush.

This process used to store the full log output in memory before parsing
the logs for obtaining the stage ids. We have seen this cause issues
before because of the excessive memory usage and it is one the suspects
of #2165, which is blocking the update to Node 14

Change-type: patch
2023-01-03 12:29:54 -03:00
cfc6b3ce9e v15.0.0 2023-01-02 15:21:59 +00:00
1c7a354fe7 Merge pull request #2573 from balena-io/balena-preload-13
Upgrade balena-preload to 13.0.0
2023-01-02 10:20:01 -05:00
40a0941ca3 preload: Drops ability to preload Intel Edison (EOL 2017)
Upgrade balena-preload from 12.2.0 to 13.0.0

Change-type: major
Signed-off-by: Edwin Joassart <edwin.joassart@balena.io>
2023-01-02 15:34:32 +01:00
0ab4760272 v14.5.18 2022-12-29 07:20:50 +00:00
42b2269e81 Merge pull request #2576 from balena-io/flowzone-npm-ci
Update flowzone tests to use npm ci
2022-12-29 02:19:24 -05:00
c818d846b3 Update flowzone tests to use npm ci
Will also make sure that the shrinkwrap is
matching the committed package.json.

Change-type: patch
2022-12-29 08:24:24 +02:00
3328f40416 v14.5.17 2022-12-28 23:56:12 +00:00
58d10c1908 Merge pull request #2575 from balena-io/drop-balena-sync
Stop using the deprecated balena-sync module
2022-12-29 01:54:48 +02:00
2fd0ca6a02 Stop using the deprecated balena-sync module
Change-type: patch
2022-12-29 01:05:51 +02:00
173028fd0d v14.5.16 2022-12-28 23:01:25 +00:00
62d5bf4436 Merge pull request #2574 from balena-io/align-package-json-shrinkwrap
Update the npm-shrinkwrap.json dependencies to match the package.json
2022-12-29 01:00:11 +02:00
63a0d19770 Update the npm-shrinkwrap.json dependencies to match the package.json
Change-type: patch
2022-12-28 21:22:48 +02:00
8244636bf2 v14.5.15 2022-12-12 13:41:15 +00:00
6a01fb361c Merge pull request #2570 from balena-io/aethernet-preload-12.2.0
patch: update balena-preload to 12.2.0
2022-12-12 08:39:46 -05:00
ca637b3fb6 patch: update balena-preload to 12.2.0 2022-12-12 13:16:22 +01:00
006293bd01 v14.5.14 2022-12-11 21:46:38 +00:00
338b5d79d3 Merge pull request #2535 from balena-io/multicast-dns-bump
Bump multicast-dns to rebased commit (again)
2022-12-11 16:45:14 -05:00
60dd0daae5 Bump multicast-dns to rebased commit (again)
A recent PR reverted the multicast-dns commit bump from PR #2401. This means that
under some conditions, `npm install` will fail.

See: https://github.com/balena-io-modules/multicast-dns/pull/1
See: https://github.com/balena-io/balena-cli/pull/2401

Change-type: patch
2022-12-11 12:45:49 -08:00
662b8283a6 v14.5.13 2022-12-08 14:00:28 +00:00
cfc866cf41 Merge pull request #2569 from balena-io/gh-runners
Specify gh runner versions for compatibility reasons
2022-12-08 13:58:56 +00:00
e566badfff Build on macos-11 for library compatibility reasons
Change-type: patch
2022-12-08 10:58:40 +00:00
69834c417e Build on ubuntu-20.04 for library compatibility reasons
Change-type: patch
2022-12-08 10:58:25 +00:00
8aa9c62afd v14.5.12 2022-11-21 18:46:49 +00:00
4f29e37fe7 Merge pull request #2565 from balena-io/ab77/operational
Move GH publishing to FZ core
2022-11-21 18:45:25 +00:00
99e8a36bb5 Move GH publishing to FZ core
Change-type: patch
2022-11-21 09:48:09 -08:00
669cbe227f v14.5.11 2022-11-17 18:32:48 +00:00
e9156d77f1 Merge pull request #2532 from balena-io/nvmrc
Adding .nvmrc so we can use nvm use instead of hunting for version
2022-11-17 18:31:28 +00:00
767216c842 Adding .nvmrc so we can use nvm use instead of hunting for version
Change-type: patch
2022-11-16 17:54:42 -08:00
d3018f9061 v14.5.10 2022-11-11 11:24:21 +00:00
37c6ad855b Merge pull request #2557 from balena-io/surface-sdk-incompatible-dt-errors
Surface sdk incompatible dt errors
2022-11-11 11:23:05 +00:00
ca97678358 Fix surfacing incompatible device type errors as not recognized
Change-type: patch
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2022-11-10 16:52:14 -08:00
3bb0036ba8 v14.5.9 2022-11-11 00:49:26 +00:00
ac9e2a9e7e Merge pull request #2562 from balena-io/ab77/operational
Prevent git from existing with 141
2022-11-11 00:47:34 +00:00
52e95e6d0a Prevent git from existing with 141
Change-type: patch
2022-11-10 15:52:13 -08:00
c5d2aa7eec v14.5.8 2022-11-10 23:32:21 +00:00
683220e303 Merge pull request #2561 from balena-io/ab77/operational
Replace missing input
2022-11-10 23:30:51 +00:00
44f09b32fa Replace missing input
Change-type: patch
2022-11-10 14:33:13 -08:00
d1a0660a3d v14.5.7 2022-11-10 22:19:22 +00:00
ee1987f188 Merge pull request #2560 from balena-io/ab77/operational
Just ignore errors during publish
2022-11-10 22:17:59 +00:00
39e9997d9e Just ignore errors during publish
Change-type: patch
2022-11-10 13:22:29 -08:00
97b8c75043 v14.5.6 2022-11-10 21:07:35 +00:00
7cb8349f29 Merge pull request #2559 from balena-io/ab77/operational
Ignore PIPE signal
2022-11-10 21:06:18 +00:00
6063f4c776 Ignore PIPE signal
Change-type: patch
2022-11-10 12:13:03 -08:00
4899d545f1 v14.5.5 2022-11-10 20:07:12 +00:00
115bf6433d Merge pull request #2558 from balena-io/ab77/operational
Don't pipefail
2022-11-10 20:05:33 +00:00
e5ce1ade89 Don't pipefail
Change-type: patch
2022-11-10 11:13:37 -08:00
9c4174ea8a v14.5.4 2022-11-10 18:31:21 +00:00
cf16957195 Merge pull request #2556 from balena-io/2537-error-on-incompatible-resolved-device-types
Error when the device type and image parameters do not match
2022-11-10 18:30:05 +00:00
4de369ff95 Error when the device type and image parameters do not match
Resolves: #2537
Change-type: patch
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2022-11-10 09:37:22 -08:00
ac3ebff8ee v14.5.3 2022-11-10 17:20:19 +00:00
76b01d92d3 Merge pull request #2555 from balena-io/ab77/operational
Switch to Flowzone
2022-11-10 17:18:49 +00:00
19144163ee Switch to Flowzone
Change-type: patch
2022-11-08 20:56:47 -08:00
535ffccbad v14.5.2 2022-10-21 20:15:35 +03:00
6f5ada9692 Merge pull request #2553 from balena-io/stop-waiting-for-the-analytics-response
Stop waiting for the analytics response
2022-10-21 17:09:08 +00:00
1c7d9255ae Stop waiting for the analytics response
Change-type: patch
See: https://balena.zulipchat.com/#narrow/stream/345884-aspect.2Fanalytics/topic/Balena.20CLI.20analytics-performance
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2022-10-21 19:07:39 +03:00
807e6ea2ad v14.5.1 2022-10-21 15:48:13 +03:00
c76f019fd0 Merge pull request #2552 from balena-io/bump-parse-link-header-2.0.0
Bump parse-link-header from 1.0.1 to 2.0.0
2022-10-21 12:45:48 +00:00
3c2c925eed Bump parse-link-header from 1.0.1 to 2.0.0
Bumps [parse-link-header](https://github.com/thlorenz/parse-link-header) from 1.0.1 to 2.0.0.
- [Release notes](https://github.com/thlorenz/parse-link-header/releases)
- [Commits](https://github.com/thlorenz/parse-link-header/compare/v1.0.1...v2.0.0)

---
updated-dependencies:
- dependency-name: parse-link-header
  dependency-type: direct:development
...

Change-type: patch
Signed-off-by: dependabot[bot] <support@github.com>
2022-10-20 20:10:53 +03:00
14b54be15e v14.5.0 2022-10-18 15:17:13 +03:00
7fb82f7447 Merge pull request #2539 from balena-io/send-tracking-to-analytics-backend
changes analytics endpoint to analytics-backend
2022-10-18 12:14:05 +00:00
4a5d44a0f1 Merge branch 'master' into send-tracking-to-analytics-backend 2022-10-18 08:15:33 -03:00
1cba0284df v14.4.4 2022-10-18 13:36:52 +03:00
6e4fe229bf Merge pull request #2546 from balena-io/update-simple-git
Update simple git
2022-10-18 10:33:17 +00:00
7033075900 Update simple-git to 3.14.1
Change-type: patch
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2022-10-18 09:45:24 +03:00
ded268ff3c automation/check-doc: Convert to typescript 2022-10-18 09:45:24 +03:00
a366f0b7eb automation/check-doc: Rename to .ts 2022-10-18 09:45:24 +03:00
507c8a1bfd v14.4.3 2022-10-18 00:24:29 +03:00
1fb46bfa5d Merge pull request #2545 from balena-io/config-generate-incompatible-dt-error
config generate: Fix the incompatible arch errors showing as not found
2022-10-17 21:20:21 +00:00
2e115968d5 config generate: Fix the incompatible arch errors showing as not found
Change-type: patch
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2022-10-17 23:44:08 +03:00
83020797b0 v14.4.2 2022-10-17 21:15:25 +03:00
0c4647e980 Merge pull request #2544 from balena-io/no-device-type-json-arch-aliases
Stop relying on device-type.json for resolving the cpu architecture
2022-10-17 18:06:05 +00:00
a20d2a04a8 Stop relying on device-type.json for resolving the device type aliases
Resolves: #2541
Change-type: patch
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2022-10-17 19:09:09 +03:00
57b0dccc7d Stop relying on device-type.json for resolving the cpu architecture
Resolves: #2542
Change-type: patch
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2022-10-17 19:09:09 +03:00
d1e3bdf29a keeps events loggiging with default message
change-type: minor
2022-10-17 10:07:51 -03:00
bdf7fedd7a uses amplitude data events format
Change-type: minor
2022-10-14 10:50:12 -03:00
c163662f4a changes analytics endpoint to analytics-backend
change-type: minor
2022-10-13 19:32:55 -03:00
a2823fd3ec v14.4.1 2022-10-12 18:19:30 +03:00
d717352b84 Merge pull request #2530 from balena-io/hraftery-patch-1
Add to description that command is device specific
2022-10-12 14:59:41 +00:00
e46902e683 balena os initialize: Clarify that the process includes flashing
Change-type: patch
2022-10-12 16:45:16 +03:00
e96ef6697e v14.4.0 2022-10-12 16:37:05 +03:00
6f54197b7b Merge pull request #2533 from balena-io/2531-device-register-dt-param
device register: Add support for the `--deviceType` option
2022-10-12 13:31:08 +00:00
34b4ac2d9f device register: Add support for the --deviceType option
Resolves: #2531
Change-type: minor
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2022-10-12 11:15:00 +03:00
f99244603a Update balena-sdk to 16.28.0
Update balena-sdk from 16.22.0 to 16.28.0

Change-type: patch
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2022-10-12 11:15:00 +03:00
523c0af0fb v14.3.1 2022-09-06 08:49:41 -04:00
2206b475c6 Merge pull request #2526 from balena-io/unified-os-release-examples
Add unified OS versions in the examples of the device & os commands
2022-09-06 12:48:06 +00:00
a117dc0382 Add unified OS versions in the examples of the device & os commands
Change-type: patch
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2022-09-06 13:26:26 +03:00
cf3e8ff909 v14.3.0 2022-08-17 15:26:40 +03:00
36d1af1e33 Merge pull request #2523 from balena-io/add-release-validate-and-invalidate-commands
release: Add `invalidate` and `validate` commands for invalidating and validating releases (respectively)
2022-08-17 12:24:14 +00:00
18f83092fe v14.2.0 2022-08-16 23:32:01 +03:00
ee3c796787 Merge pull request #2522 from balena-io/add-fleet-pin-and-track-latest-commands
Add fleet `pin` and `track-latest` commands
2022-08-16 20:29:51 +00:00
934c3ddf38 release: Add validate command for validating releases
Change-type: minor
Signed-off-by: Matthew Yarmolinsky <matthew-timothy@balena.io>
2022-08-15 19:44:54 +00:00
66e6daf78c release: Add invalidate command for invalidating releases
Change-type: minor
Signed-off-by: Matthew Yarmolinsky <matthew-timothy@balena.io>
2022-08-15 19:41:25 +00:00
97eb107de4 fleet: Add track-latest command for tracking the latest release
Change-type: minor
Signed-off-by: Matthew Yarmolinsky <matthew-timothy@balena.io>
2022-08-15 15:34:54 -04:00
def205f1fb fleet: Add pin command for pinning fleets to a specific release
Change-type: minor
Signed-off-by: Matthew Yarmolinsky <matthew-timothy@balena.io>
2022-08-15 15:06:10 -04:00
5c8f78678b v14.1.0 2022-08-04 17:52:53 +04:00
769f1ca5b4 Merge pull request #2493 from balena-io/add-device-track-command
Add device track command
2022-08-04 13:51:04 +00:00
cb26a736fc Add device track command for pinning a device to the latest release or a specific release
Change-type: minor
Signed-off-by: Matthew Yarmolinsky <matthew-timothy@balena.io>
2022-08-03 14:42:25 -04:00
d28847d5aa v14.0.0 2022-08-02 19:08:30 +04:00
c0902bb119 Merge pull request #2514 from balena-io/v14
Release v14
2022-08-02 15:05:58 +00:00
26aae0afab v13.10.1 2022-08-02 02:07:46 +04:00
5f3cf75c1a Merge pull request #2516 from balena-io/2515-fix-balena-deploy-jsesc-dependency
Fix balena deploy missing dependency error
2022-08-01 22:05:55 +00:00
8a7fbdb55d Drop undocumented support for numeric ids in balena device commands
Change-type: major
Signed-off-by: Matthew Yarmolinsky <matthew-timothy@balena.io>
2022-08-01 18:52:30 +00:00
b260f80bcc Drop support for the deprecated balena device public-url <enable|disable|status> <uuid> and related format
Resolves: #2501
Change-type: major
Signed-off-by: Matthew Yarmolinsky <matthew-timothy@balena.io>
2022-08-01 18:52:30 +00:00
9ec37975f3 Drop support for numeric fleet id parameters from all commands
Resolves: #2500
Change-type: major
Signed-off-by: Matthew Yarmolinsky <matthew-timothy@balena.io>
2022-08-01 18:52:25 +00:00
73c487c2f5 Fix balena deploy missing dependency error
Resolves: #2515
Change-type: patch
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2022-08-01 19:35:07 +03:00
3cb35ea318 fleet: Add --filter, --no-header, --no-truncate, and --sort options
Resolves: #2503
Change-type: minor
Signed-off-by: Matthew Yarmolinsky <matthew-timothy@balena.io>
2022-08-01 15:35:13 +00:00
efe6fd22ce fleet: Add --fields and --json options
Change-type: minor
Signed-off-by: Matthew Yarmolinsky <matthew-timothy@balena.io>
2022-08-01 15:35:13 +00:00
6ee8d8a899 fleet: Use the oclif output formatter
Change-type: major
Signed-off-by: Matthew Yarmolinsky <matthew-timothy@balena.io>
2022-08-01 15:35:13 +00:00
c735f13636 config: Drop optional and ignored --type flag
Change-type: patch
Signed-off-by: Matthew Yarmolinsky <matthew-timothy@balena.io>
2022-08-01 15:35:13 +00:00
edb0fdc3c1 Drop deprecated --logs flag
Resolves: #2499
Change-type: major
Signed-off-by: Matthew Yarmolinsky <matthew-timothy@balena.io>
2022-08-01 15:35:13 +00:00
14a07ac7f7 Drop support for open-balena-api < v0.131.0
Resolves: #2502
Change-type: major
Signed-off-by: Matthew Yarmolinsky <matthew-timothy@balena.io>
2022-08-01 15:35:13 +00:00
264cd94be5 v13.10.0 2022-07-20 12:25:47 +00:00
2664f4e7fb Merge pull request #2504 from balena-io/add-device-view-option
Add `--view` flag to `device` command for opening a device's dashboard page
2022-07-20 12:23:37 +00:00
3ce2653881 v13.9.0 2022-07-19 12:03:18 +03:00
719860366f Merge pull request #2476 from balena-io/switch-to-compose
Switch to balena-compose
2022-07-19 08:58:59 +00:00
21ded85c7a v13.8.0 2022-07-18 22:55:10 +03:00
c91f67d27e Merge pull request #2505 from balena-io/add-note-option-for-push-and-deploy
Add `--note` option for `push` and `deploy`
2022-07-18 18:26:43 +00:00
18eedfec7f Add --note option for push and deploy
Change-type: minor
Signed-off-by: Matthew Yarmolinsky <matthew-timothy@balena.io>
2022-07-14 15:44:55 -04:00
1fe0480a8a Add --view flag to device command for opening a device's dashboard page
Change-type: minor
Signed-off-by: Matthew Yarmolinsky <matthew-timothy@balena.io>
2022-07-14 18:56:51 +00:00
c7f56d92dd Switch to balena-compose
Removes a bunch of individual dependencies by switching to `@balena/compose` which (currently) groups and manages those dependencies together in one package.

Change-type: minor
2022-07-14 13:05:21 +00:00
a92f58134f v13.7.1 2022-07-13 10:50:45 +03:00
cc6a8ef76e Merge pull request #2498 from balena-io/2462-bump-image-manager
os download: Fix resolving to draft releases
2022-07-13 07:48:41 +00:00
88f4a3d88e os download: Fix resolving to draft releases
Resolves: #2462
Change-type: patch
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2022-07-13 10:14:30 +03:00
f6d668684a v13.7.0 2022-07-07 11:01:22 +03:00
be7c0dc897 Merge pull request #2496 from balena-io/add-fleet-view-command
Add `--view` flag to `fleet` command for opening a fleet's dashboard page
2022-07-07 07:58:02 +00:00
566b7f97e0 Add --view flag to fleet command for opening a fleet's dashboard page
Change-type: minor
Signed-off-by: Matthew Yarmolinsky <matthew-timothy@balena.io>
2022-07-05 13:14:18 -04:00
f55dd81a19 v13.6.1 2022-06-13 21:40:58 +03:00
dba5349390 Merge pull request #2491 from balena-io/update-balena-sdk-16.22.0
Update balena-sdk to use the native OS release phase & variant fields
2022-06-13 17:25:54 +00:00
6a8dfcc664 Update balena-sdk to use the native OS release phase & variant fields
Update balena-sdk from 16.20.4 to 16.22.0

Change-type: patch
2022-06-09 17:51:55 +03:00
59e35d866f v13.6.0 2022-06-07 17:44:48 +03:00
9235c928f1 Merge pull request #2490 from balena-io/kyle/qemu-v7.0.0
Update QEMU to v7.0.0
2022-06-07 14:38:37 +00:00
3d88f0144a Update QEMU to v7.0.0
Change-type: minor
Signed-off-by: Kyle Harding <kyle@balena.io>
2022-06-06 14:56:10 -04:00
a6b461ba91 v13.5.3 2022-05-31 13:31:31 +03:00
b96da951db Merge pull request #2485 from balena-io/drop-needspasswordreset
Drop the needsPasswordReset property from the tests
2022-05-31 10:26:22 +00:00
8235cead07 Drop the needsPasswordReset property from the tests
Change-type: patch
See: https://github.com/balena-io/balena-api/pull/3665
2022-05-31 12:46:49 +03:00
30b9d9141d v13.5.2 2022-05-31 12:34:18 +03:00
03b41d9989 Merge pull request #2486 from balena-io/npm-dd
Deduplicate npm-shrinkwrap.json
2022-05-31 09:28:29 +00:00
aab3af2153 Deduplicate npm-shrinkwrap.json
Change-type: patch
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2022-05-31 11:46:49 +03:00
600457de61 v13.5.1 2022-05-27 02:21:54 +03:00
17db857e10 Merge pull request #2483 from balena-io/bump-preload-to-v3
Bump balena-preload to 12.1.0
2022-05-26 23:20:10 +00:00
eb45ae2a30 preload: Fix issue where balenaOS v2.98.3+ required an Internet connection to start apps
Devices with a v13+ supervisor will fail to start preloaded apps with a
v2 target state format if connectivity is not available since migration
of apps.json is not possible without API access.

This enables support for preloading v3 target state format in
images with supervisor v13 or above.

Change-type: patch
2022-05-26 20:48:07 +00:00
2eaf70bff3 v13.5.0 2022-05-25 15:01:45 +03:00
226f45f732 Merge pull request #2482 from balena-io/key-expiry
Add provisioning key expiry date option to config generate options
2022-05-25 11:59:06 +00:00
c4990f3a26 Update balena-sdk to 16.20.4
Update balena-sdk from 16.9.0 to 16.20.4

Change-type: patch
2022-05-24 21:53:12 +05:30
0195a3b18c Add provisioning key expiry date option to config generate options
Change-Type: minor
Signed-off-by: Nitish Agarwal <1592163+nitishagar@users.noreply.github.com>
2022-05-22 21:50:48 +05:30
3d90aeb122 v13.4.3 2022-05-19 21:10:42 +03:00
0571039bfe Merge pull request #2481 from balena-io/update-docker-progress
Update docker-progress to 5.1.3
2022-05-19 17:56:34 +00:00
ee668a4c5c Update docker-progress to 5.1.3
Update docker-progress from 5.0.1 to 5.1.3

Change-type: patch
2022-05-18 15:01:27 +01:00
ead4dbfab1 v13.4.2 2022-05-10 21:02:45 +03:00
0b498d09df Merge pull request #2479 from balena-io/kyle/balena-preload
preload: Fix detection of supervisor version for balenaOS v2.93.0
2022-05-10 17:08:59 +00:00
2b2c40c22d preload: Fix detection of supervisor version for balenaOS v2.93.0
Update balena-preload from 12.0.0 to 12.0.1

Change-type: patch
Signed-off-by: Kyle Harding <kyle@balena.io>
2022-05-10 11:29:14 -04:00
ba3a3865b5 v13.4.1 2022-04-29 04:45:30 +03:00
f8402bc40c Merge pull request #2444 from balena-io/balena-leave-clearer-message
patch: Tell user that balena leave command does not remove the device…
2022-04-11 16:33:26 +00:00
c667ffa8eb leave: Update log message to advise that device still needs deleting
Change-type: patch
2022-04-11 17:04:45 +01:00
6d6065ddf5 v13.4.0 2022-04-11 17:18:27 +03:00
44f55f8e7b Merge pull request #2473 from balena-io/2337-support-all-valid-sermer-on-releases
deploy: Support all valid semver versions in balena.yml
2022-04-11 14:05:16 +00:00
d2c77760b3 deploy: Support all valid semver versions in balena.yml
Resolves: #2337
Change-type: minor
Depends-on: https://github.com/balena-io/open-balena-api/pull/982
Depends-on: https://github.com/balena-io/balena-api/pull/3584
See: https://jel.ly.fish/product-improvement-draft-releases-and-release-versioning-d0391f45-c2f9-4f4e-b964-1a7e9023a3f4
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2022-04-08 18:16:45 +03:00
7496710c85 v13.3.3 2022-04-08 14:39:33 +03:00
be6a468507 Merge pull request #2471 from balena-io/patches-contributing
Document the 'patches' folder in CONTRIBUTING.md
2022-04-08 11:36:49 +00:00
88835e63bd Document the 'patches' folder in CONTRIBUTING.md
Change-type: patch
2022-04-08 01:16:28 +01:00
3572cb3cd6 v13.3.2 2022-04-07 13:25:31 +03:00
7fbd1de063 Merge pull request #2470 from balena-io/2469-build-docker-tls
build: Ensure HTTPS is used with dockerPort 2376 or with ca/cert/key
2022-04-07 10:22:54 +00:00
a4ab07cd08 Skip Alpine tests until Concourse + Alpine v3.14 issues are resolved
See:
* https://github.com/concourse/concourse/issues/7905
* https://github.com/product-os/balena-concourse/issues/631
* https://github.com/product-os/ci-images/pull/116/files#r844508619

Change-type: patch
2022-04-07 00:29:55 +01:00
9185eaa2b7 build: Ensure HTTPS is used with dockerPort 2376 or with ca/cert/key
Change-type: patch
2022-04-07 00:14:03 +01:00
ff3abe1fba v13.3.1 2022-03-08 22:33:20 +02:00
1ac3b70b81 Merge pull request #2463 from balena-io/update-notifier-release-notes
Include link to Wiki release notes in version update notifications
2022-03-08 20:31:39 +00:00
e946178953 Include link to Wiki release notes in version update notifications
Change-type: patch
2022-03-08 18:25:08 +00:00
6589589bee v13.3.0 2022-03-01 00:35:10 +02:00
6ae598b55e Merge pull request #2461 from balena-io/2458-ssh-ipaddr-service
ssh: Allow ssh to service with IP address and production balenaOS image
2022-02-28 22:33:02 +00:00
915f7e3763 ssh: Allow ssh to service with IP address and production balenaOS image
Also remove 'balena ssh' dependency on the device supervisor (that may
be down because of device issues or a supervisor bug) when opening a
ssh shell on a container (#1560).

Resolves: #2458
Resolves: #1560
Change-type: minor
2022-02-28 21:39:49 +00:00
cd17d79067 ssh: Advise use of 'balena login' if root authentication fails
Change-type: patch
2022-02-24 21:48:40 +00:00
7e4f4392e9 v13.2.1 2022-02-24 23:45:39 +02:00
3c0e998616 Merge pull request #2460 from balena-io/uuid-log
Correctly use the device uuid when logging the tunnel target
2022-02-24 21:43:28 +00:00
bd1bf8153d Remove unnecessary fetch of device info in balena tunnel
Change-type: patch
2022-02-24 21:02:27 +00:00
f2528dcd18 Correctly use the device uuid when logging the tunnel target
The "vpn address" is only relevant on the device/vpn server themselves
and makes no sense from a CLI context as it uses the uuid to specify
the target

Change-type: patch
2022-02-24 21:00:58 +00:00
ec26433925 v13.2.0 2022-02-18 23:40:57 +02:00
43cddd2e5d Merge pull request #2457 from balena-io/2292-ssh-username
ssh: Attempt cloud username if 'root' authentication fails
2022-02-18 21:38:27 +00:00
eeb2be2912 ssh: Attempt cloud username if 'root' authentication fails
Also refactor several files to avoid code duplication.

Change-type: minor
2022-02-12 02:40:35 +00:00
3bf8befb1d Replace occurrence of through2 dependency with standard stream module
Change-type: patch
2022-02-11 17:04:32 +00:00
948095ce4d Refactor cached username logic from events.ts to bootstrap.ts for reuse
Change-type: patch
2022-02-11 15:23:36 +00:00
d2330f9ed1 v13.1.13 2022-02-10 14:29:18 +02:00
cc19b00998 Merge pull request #2455 from balena-io/lucianbuzzo/drop-unused-fn-awaitdevice
Drop unused awaitDevice utility function
2022-02-10 12:27:25 +00:00
ed5ac75a10 v13.1.12 2022-02-09 09:24:17 +02:00
465b8a1b5e Merge pull request #2451 from balena-io/bump-preload-v12
Update balena-preload to v12
2022-02-09 07:22:04 +00:00
eccadbdcb9 Drop unused awaitDevice utility function
Change-type: patch
Signed-off-by: Lucian Buzzo <lucian.buzzo@gmail.com>
2022-02-01 17:43:28 +00:00
31eb734af1 Update balena-preload to v12
Update balena-preload from 11.0.0 to 12.0.0

Change-type: patch
Changelog-entry: preload: Stop using the deprecated /device-types/v1 API endpoints
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2022-01-27 19:27:58 +02:00
fa7b59d64f v13.1.11 2022-01-20 01:55:52 +02:00
1e42bfa0d5 Merge pull request #2450 from balena-io/types-node-v12
chore: Update @types/node to v12.20.42
2022-01-19 23:53:08 +00:00
5464e550e7 chore: lib/auth/utils.ts: Replace deprecated url.resolve, use async/await
Change-type: patch
2022-01-19 22:48:46 +00:00
c0f27a663d chore: Update @types/node to v12.20.42
Change-type: patch
2022-01-19 22:48:46 +00:00
d1c61c62ab v13.1.10 2022-01-16 21:29:44 +02:00
a9691bff57 Merge pull request #2446 from balena-io/2445-min-node-version-12.8.0
Update docs and package.json re min Node.js supported version (12.8.0)
2022-01-16 19:27:33 +00:00
f5d09a43cd Update docs and package.json re min Node.js supported version (12.8.0)
Resolves: #2445
Change-type: patch
2022-01-16 18:44:45 +00:00
d11e547e11 v13.1.9 2022-01-14 03:00:53 +02:00
bd462aee02 Merge pull request #2443 from balena-io/colors-action
Update packages in response to colors package issues
2022-01-14 00:58:59 +00:00
f633c0468b Update packages in response to colors package issues
Change-type: patch
Signed-off-by: Scott Lowe <scott@balena.io>
2022-01-12 22:12:06 +01:00
e4f61a1242 v13.1.8 2022-01-11 03:28:31 +02:00
96142a002e Merge pull request #2442 from balena-io/2440-pin-docker-modem
local push: Fix "invalid character '/' looking for beginning of value"
2022-01-11 01:26:51 +00:00
6b9a5cd89c local push: Fix "invalid character '/' looking for beginning of value"
Change-type: patch
2022-01-11 00:15:10 +00:00
ba2d3d60ec Merge pull request #2441 from balena-io/v14-TypeError-type-undefined
v14 preparations: Fix TypeError produced by 'npx oclif manifest'
2022-01-08 01:55:40 +00:00
d1e66bc1a5 v14 preparations: Fix TypeError produced by 'npx oclif manifest'
Change-type: patch
2022-01-08 01:16:45 +00:00
58799915a9 v13.1.7 2022-01-06 19:29:35 +02:00
5f2d55f569 Merge pull request #2436 from balena-io/update-pkg
Update to pkg 5
2022-01-06 17:27:47 +00:00
8d6e51391c v13.1.6 2022-01-04 18:53:40 +02:00
8454b02988 Merge pull request #2435 from balena-io/enforce-js-types
Automation: enforce noImplicitAny for the type-checked javascript
2022-01-04 16:51:05 +00:00
879d98ef98 Update to pkg 5
Change-type: patch
2022-01-04 16:31:08 +00:00
c4e317a290 Automation: enforce noImplicitAny for the type-checked javascript
Change-type: patch
2022-01-04 16:27:06 +00:00
7ca4d2d720 v13.1.5 2022-01-04 17:34:55 +02:00
e1e88ec56d Merge pull request #2434 from balena-io/remove-gulp
Build: switch from using inline-source via gulp to using it directly
2022-01-04 15:33:01 +00:00
33f7fa3829 Build: switch from using inline-source via gulp to using it directly
Change-type: patch
2022-01-04 15:03:05 +00:00
3d516e7c5f v13.1.4 2022-01-04 13:25:57 +02:00
a8507508b7 Merge pull request #2428 from balena-io/update-pkg
Update pkg
2022-01-04 11:24:14 +00:00
008972b3d3 Update pkg
Change-type: patch
2022-01-03 17:35:13 +00:00
92b86330a0 v13.1.3 2022-01-03 18:52:42 +02:00
2563c07c6a Merge pull request #2433 from balena-io/ts-deploy-legacy
Convert lib/utils/deploy-legacy to typescript
2022-01-03 16:50:06 +00:00
1d4b949cf3 Convert lib/utils/deploy-legacy to typescript
Change-type: patch
2022-01-03 16:10:17 +00:00
d17e02a930 v13.1.2 2022-01-03 18:03:55 +02:00
a355cbaa79 Merge pull request #2432 from balena-io/compose-ts
Convert lib/utils/compose to typescript
2022-01-03 16:01:18 +00:00
bd021c0a2d Convert lib/utils/compose to typescript
Change-type: patch
2022-01-03 15:26:19 +00:00
a80f676804 v13.1.1 2021-12-30 15:08:14 +02:00
f723c58089 Merge pull request #2430 from balena-io/update-deps
Update dependencies
2021-12-30 13:05:58 +00:00
e27a4e2e31 Update dependencies
Update docker-progress from 5.0.0 to 5.0.1

Change-type: patch
2021-12-30 12:36:12 +00:00
b91b72c408 v13.1.0 2021-12-29 16:47:51 +02:00
5cf84d3f1d Merge pull request #2431 from balena-io/os-configure-dev-flag
os.getConfig MVP (os configure, config generate, local configure)
2021-12-29 14:45:46 +00:00
7d58b8c120 os configure, config generate: Add '--dev' option for OS developmentMode
Change-type: minor
2021-12-29 00:28:04 +00:00
851301a336 local configure: Allow configuring 'developmentMode' in config.json
Change-type: minor
2021-12-25 02:26:52 +00:00
ec6fd050f6 os build-config: Clarify command purpose in help output
Change-type: patch
2021-12-25 02:26:47 +00:00
6f81053882 device os-update: Add support for unified dev/prod balenaOS releases
Update balena-sdk from 16.8.1 to 16.9.0

Change-type: minor
2021-12-24 23:52:57 +00:00
dbd8a9a08c v13.0.2 2021-12-24 20:12:14 +02:00
256f1abf1b Merge pull request #2427 from balena-io/update-oclif
Update oclif
2021-12-24 18:10:10 +00:00
acd352cb3c Update oclif
Change-type: patch
2021-12-24 17:20:50 +00:00
31f927c27c v13.0.1 2021-12-24 18:58:28 +02:00
3d0f16168a Merge pull request #2429 from balena-io/os-versions-recommended
os versions, os download: Replace deprecated version fields
2021-12-24 16:55:58 +00:00
b2d932afab os versions, os download: Replace deprecated version fields
Replace deprecated `rawVersion` and `formattedVersion` fields and use
alternative overload of `getAvailableOsVersions`. As a result, the word
'recommended' is no longer printed next to any OS versions.

Change-type: patch
2021-12-24 16:01:51 +00:00
398175f0b3 Update balena-sdk to v16.8.1
Update balena-sdk from 16.8.0 to 16.8.1

Change-type: patch
2021-12-24 14:45:12 +00:00
2fb9c6c773 v13.0.0 2021-12-23 21:48:02 +02:00
66608b32e9 Merge pull request #2420 from balena-io/v13
Release CLI v13
2021-12-23 19:45:50 +00:00
c403683edf v13 RELEASE NOTES: see https://git.io/JDHxG
Change-type: patch
2021-12-23 18:47:34 +00:00
1e6ab46ca3 Add tips for removed commands
Signed-off-by: Scott Lowe <scott@balena.io>
2021-12-23 18:40:05 +00:00
02d3220f2d Fix some app/fleet terminology issues
Signed-off-by: Scott Lowe <scott@balena.io>
2021-12-23 18:40:05 +00:00
c86cdc8f84 balena SDK v16: Ensure all SDK calls use fleet slug rather than name
Change-type: patch
2021-12-23 18:40:05 +00:00
84f02dc063 Update balena-sdk to v16.8.0
Update balena-sdk from 15.51.1 to 16.8.0

Change-type: patch
2021-12-23 18:40:05 +00:00
9145f2fb28 device, devices: Print the fleet's slug in 'org/fleetName' format
Change-type: major
2021-12-23 15:34:09 +00:00
1164388d78 envs: Print the fleet's slug in 'org/fleetName' format
Change-type: major
2021-12-23 15:34:08 +00:00
06f6094401 os configure: Remove deprecated '--device-api-key' option
Change-type: major
2021-12-23 15:34:08 +00:00
67e11467f7 Clean up unused v13 feature switch code
Change-type: patch
2021-12-23 15:34:08 +00:00
c8dfd0ca65 config read/write/inject/reconfigure: Place '--type' option behind v14 switch
Change-type: patch
2021-12-23 15:34:08 +00:00
8b110a835a fleet create: Don't print fleet's numeric database ID in confirmation msg
Change-type: major
2021-12-23 15:34:08 +00:00
7564d95f82 devices supported: Remove deprecated '--verbose' and '--discontinued' options
Change-type: major
2021-12-23 15:34:08 +00:00
f12f2b79ef build/deploy/push: Remove deprecated '--convert-eol' option
Change-type: major
2021-12-23 15:34:08 +00:00
176d731f9e Move some v13 features behind v14 switch.
Change-type: patch
Signed-off-by: Scott Lowe <scott@balena.io>
2021-12-23 15:34:08 +00:00
1ed39d1d37 Remove deprecated '--app' and '--application' options (renamed to '--fleet')
Change-type: major
2021-12-23 15:34:08 +00:00
580ca0d584 Remove deprecated commands 'app' and 'apps' (renamed to 'fleet' and 'fleets')
Change-type: major
2021-12-23 15:34:08 +00:00
73572df7cf build/deploy/push: Remove deprecated '--[no]gitignore' option
Change-type: major
2021-12-23 15:34:08 +00:00
23b42b1a2b v13 release: Flip the v13 feature switch
Change-type: major
2021-12-23 15:34:08 +00:00
632322e3c2 v13 release: Drop support for Node.js v10 (package.json engines.node)
Change-type: major
2021-12-23 15:34:08 +00:00
4faa5d7f57 v12.55.11 2021-12-23 17:19:56 +02:00
9b967592a9 Merge pull request #2426 from balena-io/typescript-4.5
Update to typescript 4.5
2021-12-23 15:18:13 +00:00
e01483cd2b Update to typescript 4.5
Change-type: patch
2021-12-23 13:59:42 +00:00
6d89ff4bbf v12.55.10 2021-12-23 15:04:02 +02:00
126e731117 Merge pull request #2423 from balena-io/update-dev-deps
Update dev dependencies
2021-12-23 13:01:42 +00:00
32d26ad074 v12.55.9 2021-12-22 18:02:30 +02:00
2bcfec9d0f Merge pull request #2413 from balena-io/os-download-prod-suffix-revisited
os download: Future-proof unified dev/prod balenaOS versioning
2021-12-22 16:00:49 +00:00
c04e63ab7d os download: Future-proof '--version' format for unified dev/prod variants
Do not append the '.prod' suffix by default to balenaOS versions.

Change-type: patch
2021-12-22 15:37:24 +00:00
79be06820c Update dev dependencies
Change-type: patch
2021-12-21 18:24:26 +00:00
ffb94c380f v12.55.8 2021-12-21 18:59:32 +02:00
385b5e9ec6 Merge pull request #2422 from balena-io/windows-version-info
Include version info when installed on windows
2021-12-21 16:55:54 +00:00
8d3a4343cb Include version info when installed on windows
Change-type: patch
2021-12-21 16:22:21 +00:00
6eeb16245b Switch from the deprecated oclif-dev commands to the oclif commands
Change-type: patch
2021-12-21 16:21:58 +00:00
3961060f90 v12.55.7 2021-12-14 23:32:04 +02:00
a6dfc9126a Merge pull request #2412 from balena-io/pp/issue-2411
Removed hardcoded 'balenaCloud' in console message.
2021-12-14 21:30:06 +00:00
e7ddd07b7b push: Remove hardcoded 'balenaCloud' in console message
Change-type: patch
Signed-off-by: Pranav Peshwe <pranav@balena.io>
2021-12-14 19:54:30 +05:30
fea351d960 v12.55.6 2021-12-14 03:57:53 +02:00
40e0b2dbed Merge pull request #2410 from balena-io/fix-symlink-push-deploy
Fix symbolic link regression in push & deploy
2021-12-14 01:56:06 +00:00
3def4d0e4a Fix symbolic link regression in push & deploy
Change-type: patch
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2021-12-14 02:30:08 +02:00
aa286cc0e7 v12.55.5 2021-12-14 01:58:31 +02:00
8abeb6aed7 Merge pull request #2409 from balena-io/drop-unused-dir-traversal-list
Drop unnecessary directory list created during balena deploy & push
2021-12-13 23:56:00 +00:00
f285880135 Drop unnecessary directory list created during balena deploy & push
Change-type: patch
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2021-12-14 00:29:15 +02:00
2b5c387313 v12.55.4 2021-12-10 03:29:22 +02:00
8babf4c908 Merge pull request #2405 from balena-io/2386-os-download-dt-aliases
os download, os versions: Accept device type aliases
2021-12-10 01:26:38 +00:00
bfc995e948 os download, os versions: Accept device type aliases
Change-type: patch
2021-12-10 00:52:02 +00:00
c6a0bc0fba os download: Don't append '.prod' if the OS version does not match regex 2021-12-10 00:38:56 +00:00
ae69accf0f v12.55.3 2021-12-10 00:01:36 +02:00
cfcace4c99 Merge pull request #2404 from balena-io/2387-os-download-prod-suffix
os download: Assume '.prod' suffix by default for all balenaOS versions
2021-12-09 21:59:27 +00:00
6e07db0813 os download: Improve error message when not logged in (balenaOS ESR versions)
Change-type: patch
2021-12-09 18:06:26 +00:00
5c40c8d51f os download: Assume '.prod' suffix by default for all balenaOS versions
Resolves: #2387
Change-type: patch
2021-12-09 17:56:16 +00:00
d827005154 v12.55.2 2021-12-08 14:05:10 +02:00
76081343cc Merge pull request #2147 from balena-io/rework-tables
v13 preparations: Standardize command data output
2021-12-08 12:02:36 +00:00
f3fb9b6bdf v13 preparations: Standardize command data output
Change-type: patch
Signed-off-by: Scott Lowe <scott@balena.io>
2021-12-08 12:10:08 +01:00
c125e0b38d v12.55.1 2021-12-01 01:49:43 +02:00
73b2f6b4b1 Merge pull request #2401 from balena-io/klutchell-patch-2
chore: Bump multicast-dns to rebased commit
2021-11-30 23:46:39 +00:00
fdc0d08e96 chore: Bump multicast-dns to rebased commit
Otherwise npm install fails due to the missing commit in npm-shrinkwrap.json

Change-type: patch
Signed-off-by: Kyle Harding <kyle@balena.io>
See: https://github.com/balena-io-modules/multicast-dns/pull/1
2021-11-30 11:07:39 -05:00
e431a59af7 v12.55.0 2021-11-30 00:12:46 +02:00
41a2dbe60c Merge pull request #2294 from balena-io/provisioning-names-keys
Add provisioning key name option to config generate options
2021-11-29 22:11:05 +00:00
6ba67eefdb Add provisioning key name option to config generate options
Change-Type: minor
Signed-off-by: Nitish Agarwal 1592163+nitishagar@users.noreply.github.com
2021-11-29 16:15:55 +05:30
3b885ad906 v12.54.5 2021-11-27 03:45:02 +02:00
5574dc0318 Merge pull request #2398 from balena-io/reuse-getbootpartition
os configure, local configure: Reuse disk partition scanning logic
2021-11-27 01:42:53 +00:00
fcea91bfb6 os configure, local configure: Reuse disk partition scanning logic
Change-type: patch
2021-11-27 01:10:53 +00:00
7316c4e075 v12.54.4 2021-11-26 18:39:20 +02:00
389b7a1463 Merge pull request #2389 from balena-io/balena-lint-no-floating-promises
Bump 'balena-lint' and fix 'no-floating-promises' warnings
2021-11-26 16:36:16 +00:00
09d004423c Bump 'balena-lint' and fix 'no-floating-promises' warnings
Change-type: patch
2021-11-26 15:59:33 +00:00
97978ff812 v12.54.3 2021-11-26 17:38:17 +02:00
498e21f0ab Merge pull request #2376 from balena-io/lucianbuzzo/fast-scan
Improve directory scan speed prior to tarballing
2021-11-26 15:35:56 +00:00
257dd514ed Improve directory scan speed prior to tarballing
This changes improves the speed that the project is tarballed by switching from
`klaw` to `recursive-fs` and not running `lstat` on files that are ignored.
Whilst testing with the Jellyfish repository, which contains a number of
sub directories, each with their own node_modules folder, I was able to
reduce the time taken to scan and tarball the project from 70s to 11s,
which is a massive improvement.

Change-type: patch
Signed-off-by: Lucian Buzzo <lucian.buzzo@gmail.com>
2021-11-26 13:55:41 +00:00
85cbdd4947 v12.54.2 2021-11-26 14:01:22 +02:00
73625611da Merge pull request #2395 from balena-io/lucianbuzzo/2394-push-image
Set the correct target state when using Compose "image" field
2021-11-26 11:59:17 +00:00
d2a5a9ba86 Set the correct target state when using Compose "image" field
Fixes #2394

When pushing to a device in local mode, if a service is not external, and uses
an `image` field, that value should be used for tags and target state, otherwise
it won't match the image name generated on the device by balenaEngine.

Change-type: patch
Signed-off-by: Lucian Buzzo <lucian.buzzo@gmail.com>
2021-11-26 10:11:07 +00:00
1cd78215e0 v12.54.1 2021-11-26 00:59:24 +02:00
6d744d0b07 Merge pull request #2393 from balena-io/fix-config-usage
Fix mistake in `config generate` examples
2021-11-25 22:56:40 +00:00
9d312bcd12 v12.54.0 2021-11-25 23:47:55 +02:00
e22aa847e3 Merge pull request #2378 from balena-io/events-timeout
Improve UX for offline usage
2021-11-25 21:45:47 +00:00
0d1ca67d5b v12.53.2 2021-11-25 20:58:34 +02:00
c4a5a25f03 Merge pull request #2391 from balena-io/drop-custom-device-api-key-generation-code
Stop creating an extra provisioning API key in each config generation
2021-11-25 18:56:53 +00:00
b183d88400 Fix mistake in config generate examples
Change-type: patch
Signed-off-by: Scott Lowe <scott@balena.io>
2021-11-25 16:05:37 +00:00
2b6a2142eb Improve UX for offline usage
Change-type: minor
Resolves: #2372
Signed-off-by: Scott Lowe <scott@balena.io>
2021-11-25 15:14:39 +00:00
58b29bf4bb Stop creating an extra provisioning API key in each config generation
Change-type: patch
Changelog-entry: Avoid creating an extra provisioning API key in os configure & config generate
See: https://github.com/balena-io/balena-cli/pull/2294#discussion_r756499196
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2021-11-25 16:40:53 +02:00
fc0903a414 v12.53.1 2021-11-25 12:18:50 +02:00
cea23f5d5e Merge pull request #2388 from balena-io/docs-changes
Transitional changes to doc files for landr implementation
2021-11-25 10:15:37 +00:00
5a9b5e3b08 Transitional changes to doc files for landr implementation
Change-type: patch
Signed-off-by: Scott Lowe <scott@balena.io>
2021-11-25 10:44:38 +01:00
52138d41eb v12.53.0 2021-11-25 04:49:54 +02:00
5acdc63068 Merge pull request #2369 from balena-io/2349-config-inject-scan-partition
config read/write/inject: Avoid need for internet access
2021-11-25 02:47:56 +00:00
b546e4dd97 config read/write/inject: Avoid need for internet access
Change-type: minor
2021-11-25 02:05:40 +00:00
e4870916e2 config read: Add '--json' option for JSON output
Change-type: minor
2021-11-24 23:03:37 +00:00
3ca93448cd v12.52.2 2021-11-24 20:59:10 +02:00
f66395e2d5 Merge pull request #2390 from balena-io/device-init-docs
Delete 'doc/automated-init.md' and improve 'balena help device init'
2021-11-24 18:57:11 +00:00
952d782e90 Delete 'doc/automated-init.md' and improve 'balena help device init'
Change-type: patch
2021-11-24 18:24:14 +00:00
d53c9b3c50 v12.52.1 2021-11-22 04:25:10 +02:00
2f706c0200 Merge pull request #2384 from balena-io/2376-dockerignore-corner-cases
push/build: Add test cases for .dockerignore filtering corner cases
2021-11-22 02:23:22 +00:00
d64b6deb81 push/build: Add test cases for .dockerignore filtering corner cases
Change-type: patch
2021-11-22 01:50:27 +00:00
55fc9b2ade v12.52.0 2021-11-20 03:19:00 +02:00
6c29d0ae27 Merge pull request #2334 from balena-io/2005-os-esr-versions-hostapp
os versions, os download: Add support for balenaOS ESR versions
2021-11-20 01:17:17 +00:00
f46452f6de os download: Display OS version actually downloaded (range or 'recommended')
Change-type: patch
2021-11-20 00:43:15 +00:00
c166ec7597 os versions, os download: Add support for balenaOS ESR versions
Change-type: minor
2021-11-20 00:43:15 +00:00
7325c79888 v12.51.3 2021-11-16 19:10:36 +02:00
2a29b386eb Merge pull request #2375 from balena-io/missing-digest
deploy: Ensure the release fails if an image's digest (hash) is missing
2021-11-16 17:08:41 +00:00
23b07f8a41 deploy: Ensure the release fails if an image's digest (hash) is missing
Change-type: patch
2021-11-16 11:55:07 +00:00
6d641b4841 v12.51.2 2021-11-16 13:50:52 +02:00
7b498149b1 Merge pull request #2379 from balena-io/remove-node10-from-resinci.yml
Update balena CI configuration (remove Node v10 from npm pipeline list)
2021-11-16 11:49:36 +00:00
ae5ea0f4e8 Update balena CI configuration (remove Node v10 from npm pipeline list)
Change-type: patch
2021-11-15 23:51:15 +00:00
f635f648da v12.51.1 2021-10-25 20:54:12 +03:00
3d4e2cf823 Merge pull request #2256 from balena-io/forum-link
Fix forums support link in README.md
2021-10-25 17:52:15 +00:00
ef3b630887 v12.51.0 2021-10-22 22:13:44 +03:00
19040ccb6c Merge pull request #2367 from balena-io/support-for-fragments
Add support for YAML anchors and aliases in 'docker-compose.yml'
2021-10-22 19:11:47 +00:00
8e712ac910 Add support for YAML anchors and aliases in 'docker-compose.yml'
This allows project files to define services from generic fragments by leveraging YAML's anchors and aliases. See here for an example: 43f6537b2c/spec.md (fragments)

Removing the FAILSAFE_SCHEMA flag is not expected to break existing project files, since the default behaviour is more liberal, or cause problems down the road given we perform validation immediately after. Docs for the flag: https://github.com/nodeca/js-yaml#load-string---options-

Change-type: minor
2021-10-22 16:42:29 +03:00
c401ed35ac v12.50.3 2021-10-20 19:33:37 +03:00
94be97313b Merge pull request #2359 from balena-io/klutchell/preload-11
preload: Avoid possible ValueError when parsing storage driver
2021-10-20 16:31:34 +00:00
48053ecefc preload: Avoid possible ValueError when parsing storage driver
Update balena-preload from 10.5.0 to 11.0.0

Change-type: patch
Signed-off-by: Kyle Harding <kyle@balena.io>
2021-10-15 09:44:27 -04:00
cc60e86507 v12.50.2 2021-10-05 13:32:02 +03:00
bd774e8553 Merge pull request #2357 from balena-io/fix-fleet-rename-error-message
Error message when renaming a fleet now mentions the target name.
2021-10-05 10:27:51 +00:00
c493c33e38 Error message when renaming a fleet now mentions the target name.
Change-type: patch
Signed-off-by: Carlo Miguel F. Cruz <carloc@balena.io>
2021-10-05 17:01:07 +08:00
112a7b8194 Fix forums support link in README.md
Change-type: patch
Signed-off-by: Scott Lowe <scott@balena.io>
2021-04-21 06:57:28 +00:00
262 changed files with 53924 additions and 30707 deletions

2
.eslintignore Normal file
View File

@ -0,0 +1,2 @@
/completion/*
/bin/*

21
.eslintrc.js Normal file
View File

@ -0,0 +1,21 @@
module.exports = {
extends: ['./node_modules/@balena/lint/config/.eslintrc.js'],
parserOptions: {
project: 'tsconfig.dev.json',
},
root: true,
rules: {
ignoreDefinitionFiles: 0,
// to avoid the `warning Forbidden non-null assertion @typescript-eslint/no-non-null-assertion`
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-shadow': 'off',
'@typescript-eslint/no-var-requires': 'off',
'no-restricted-imports': [
'error',
{
paths: ['resin-cli-visuals', 'chalk', 'common-tags', 'resin-cli-form'],
},
],
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
},
};

6
.gitattributes vendored
View File

@ -4,9 +4,13 @@
*.* -eol
*.sh text eol=lf
.dockerignore eol=lf
Dockerfile eol=lf
Dockerfile.* eol=lf
* text=auto eol=lf
# lf for the docs as it's auto-generated and will otherwise trigger an uncommited error on windows
doc/cli.markdown text eol=lf
docs/balena-cli.md text eol=lf
# crlf for the eol conversion test files
tests/test-data/projects/docker-compose/basic/service2/file2-crlf.sh eol=crlf
tests/test-data/projects/no-docker-compose/basic/src/windows-crlf.sh eol=crlf

143
.github/actions/publish/action.yml vendored Normal file
View File

@ -0,0 +1,143 @@
---
name: package and draft GitHub release
# https://github.com/product-os/flowzone/tree/master/.github/actions
inputs:
json:
description: 'JSON stringified object containing all the inputs from the calling workflow'
required: true
secrets:
description: 'JSON stringified object containing all the secrets from the calling workflow'
required: true
variables:
description: 'JSON stringified object containing all the variables from the calling workflow'
required: true
# --- custom environment
XCODE_APP_LOADER_EMAIL:
type: string
default: 'accounts+apple@balena.io'
NODE_VERSION:
type: string
default: '20.x'
VERBOSE:
type: string
default: 'true'
runs:
# https://docs.github.com/en/actions/creating-actions/creating-a-composite-action
using: 'composite'
steps:
- name: Download custom source artifact
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
with:
name: custom-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ runner.os }}-${{ runner.arch }}
path: ${{ runner.temp }}
- name: Extract custom source artifact
shell: pwsh
working-directory: .
run: tar -xf ${{ runner.temp }}/custom.tgz
- name: Setup Node.js
uses: actions/setup-node@1a4442cacd436585916779262731d5b162bc6ec7 # v3
with:
node-version: ${{ inputs.NODE_VERSION }}
cache: npm
- name: Set up Python 3.11
if: runner.os == 'macOS'
uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # v4
with:
python-version: "3.11"
- name: Install additional tools
if: runner.os == 'Windows'
shell: bash
run: |
choco install yq
- name: Install additional tools
if: runner.os == 'macOS'
shell: bash
run: |
brew install coreutils
# https://www.electron.build/code-signing.html
# https://github.com/Apple-Actions/import-codesign-certs
- name: Import Apple code signing certificate
if: runner.os == 'macOS'
uses: apple-actions/import-codesign-certs@253ddeeac23f2bdad1646faac5c8c2832e800071 # v1
with:
p12-file-base64: ${{ fromJSON(inputs.secrets).APPLE_SIGNING }}
p12-password: ${{ fromJSON(inputs.secrets).APPLE_SIGNING_PASSWORD }}
- name: Import Windows code signing certificate
if: runner.os == 'Windows'
shell: powershell
run: |
Set-Content -Path ${{ runner.temp }}/certificate.base64 -Value $env:SM_CLIENT_CERT_FILE_B64
certutil -decode ${{ runner.temp }}/certificate.base64 ${{ runner.temp }}/Certificate_pkcs12.p12
Remove-Item -path ${{ runner.temp }} -include certificate.base64
env:
SM_CLIENT_CERT_FILE_B64: ${{ fromJSON(inputs.secrets).SM_CLIENT_CERT_FILE_B64 }}
# https://github.com/product-os/scripts/tree/master/shared
# https://github.com/product-os/balena-concourse/blob/master/pipelines/github-events/template.yml
- name: Package release
shell: bash
run: |
set -ea
[[ '${{ inputs.VERBOSE }}' =~ on|On|Yes|yes|true|True ]] && set -x
runner_os="$(echo "${RUNNER_OS}" | tr '[:upper:]' '[:lower:]')"
runner_arch="$(echo "${RUNNER_ARCH}" | tr '[:upper:]' '[:lower:]')"
if [[ $runner_os =~ darwin|macos|osx ]]; then
CSC_KEY_PASSWORD=${{ fromJSON(inputs.secrets).APPLE_SIGNING_PASSWORD }}
CSC_KEYCHAIN=signing_temp
CSC_LINK=${{ fromJSON(inputs.secrets).APPLE_SIGNING }}
elif [[ $runner_os =~ windows|win ]]; then
SM_HOST=${{ fromJSON(inputs.secrets).SM_HOST }}
SM_API_KEY=${{ fromJSON(inputs.secrets).SM_API_KEY }}
SM_CLIENT_CERT_FILE='${{ runner.temp }}\Certificate_pkcs12.p12'
SM_CLIENT_CERT_PASSWORD=${{ fromJSON(inputs.secrets).SM_CLIENT_CERT_PASSWORD }}
SM_CODE_SIGNING_CERT_SHA1_HASH=${{ fromJSON(inputs.secrets).SM_CODE_SIGNING_CERT_SHA1_HASH }}
curl --silent --retry 3 --fail https://one.digicert.com/signingmanager/api-ui/v1/releases/smtools-windows-x64.msi/download \
-H "x-api-key:$SM_API_KEY" \
-o smtools-windows-x64.msi
msiexec -i smtools-windows-x64.msi -qn
PATH="/c/Program Files/DigiCert/DigiCert One Signing Manager Tools:${PATH}"
smksp_registrar.exe list
smctl.exe keypair ls
/c/Windows/System32/certutil.exe -csp "DigiCert Signing Manager KSP" -key -user
smksp_cert_sync.exe
# (signtool.exe) https://github.com/actions/runner-images/blob/main/images/win/Windows2019-Readme.md#installed-windows-sdks
PATH="/c/Program Files (x86)/Windows Kits/10/bin/${runner_arch}:${PATH}"
fi
npm run package
find dist -type f -maxdepth 1
env:
# https://github.blog/2020-08-03-github-actions-improvements-for-fork-and-pull-request-workflows/#improvements-for-public-repository-forks
# https://docs.github.com/en/actions/managing-workflow-runs/approving-workflow-runs-from-public-forks#about-workflow-runs-from-public-forks
CSC_FOR_PULL_REQUEST: true
# https://docs.digicert.com/es/software-trust-manager/ci-cd-integrations/plugins/github-custom-action-for-keypair-signing.html
TIMESTAMP_SERVER: http://timestamp.digicert.com
# Apple notarization (automation/build-bin.ts)
XCODE_APP_LOADER_EMAIL: ${{ inputs.XCODE_APP_LOADER_EMAIL }}
XCODE_APP_LOADER_PASSWORD: ${{ fromJSON(inputs.secrets).XCODE_APP_LOADER_PASSWORD }}
XCODE_APP_LOADER_TEAM_ID: ${{ inputs.XCODE_APP_LOADER_TEAM_ID }}
- name: Upload artifacts
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4
with:
name: gh-release-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ strategy.job-index }}
path: dist
retention-days: 1
if-no-files-found: error

65
.github/actions/test/action.yml vendored Normal file
View File

@ -0,0 +1,65 @@
---
name: test release
# https://github.com/product-os/flowzone/tree/master/.github/actions
inputs:
json:
description: "JSON stringified object containing all the inputs from the calling workflow"
required: true
secrets:
description: "JSON stringified object containing all the secrets from the calling workflow"
required: true
variables:
description: "JSON stringified object containing all the variables from the calling workflow"
required: true
# --- custom environment
NODE_VERSION:
type: string
default: '20.x'
VERBOSE:
type: string
default: "true"
runs:
# https://docs.github.com/en/actions/creating-actions/creating-a-composite-action
using: "composite"
steps:
# https://github.com/actions/setup-node#caching-global-packages-data
- name: Setup Node.js
uses: actions/setup-node@1a4442cacd436585916779262731d5b162bc6ec7 # v3
with:
node-version: ${{ inputs.NODE_VERSION }}
cache: npm
- name: Set up Python 3.11
if: runner.os == 'macOS'
uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # v4
with:
python-version: "3.11"
- name: Test release
shell: bash
run: |
set -ea
[[ '${{ inputs.VERBOSE }}' =~ on|On|Yes|yes|true|True ]] && set -x
if [[ -e package-lock.json ]] || [[ -e npm-shrinkwrap.json ]]; then
npm ci
else
npm i
fi
npm run build
npm run test:core
- name: Compress custom source
shell: pwsh
run: tar --exclude-vcs -acf ${{ runner.temp }}/custom.tgz .
- name: Upload custom artifact
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4
with:
name: custom-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ runner.os }}-${{ runner.arch }}
path: ${{ runner.temp }}/custom.tgz
retention-days: 1

4
.github/renovate.json vendored Normal file
View File

@ -0,0 +1,4 @@
{
"extends": ["github>balena-io/renovate-config"],
"postUpdateOptions": ["npmDedupe"]
}

45
.github/workflows/flowzone.yml vendored Normal file
View File

@ -0,0 +1,45 @@
name: Flowzone
on:
pull_request:
types: [opened, synchronize, closed]
branches: [main, master]
pull_request_target:
types: [opened, synchronize, closed]
branches: [main, master]
jobs:
flowzone:
name: Flowzone
uses: product-os/flowzone/.github/workflows/flowzone.yml@master
# prevent duplicate workflow executions for pull_request and pull_request_target
if: |
(
github.event.pull_request.head.repo.full_name == github.repository &&
github.event_name == 'pull_request'
) || (
github.event.pull_request.head.repo.full_name != github.repository &&
github.event_name == 'pull_request_target'
)
secrets: inherit
with:
custom_test_matrix: >
{
"os": [
["actuated-4cpu-8gb"],
["actuated-arm64-4cpu-8gb"],
["macos-12"],
["windows-2019"],
["macos-latest-xlarge"]
]
}
custom_publish_matrix: >
{
"os": [
["actuated-4cpu-8gb"],
["actuated-arm64-4cpu-8gb"],
["macos-12"],
["windows-2019"],
["macos-latest-xlarge"]
]
}
github_prerelease: false
restrict_custom_actions: false

View File

@ -3,5 +3,8 @@ module.exports = {
require: 'ts-node/register/transpile-only',
file: './tests/config-tests',
timeout: 12000,
// To test only, say, 'push.spec.ts', do it as follows so that
// requests are authenticated:
// spec: ['tests/auth/*.spec.ts', 'tests/**/deploy.spec.ts'],
spec: 'tests/**/*.spec.ts',
};

View File

@ -1,17 +0,0 @@
---
npm:
platforms:
- name: linux
os: ubuntu
architecture: x86_64
node_versions:
- "10"
- "12"
- "14"
- name: linux
os: alpine
architecture: x86_64
node_versions:
- "10"
- "12"
- "14"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -105,12 +105,12 @@ npm run update balena-sdk ^13.0.0 major
## Editing documentation files (README, INSTALL, Reference website...)
The `doc/cli.markdown` file is automatically generated by running `npm run build:doc` (which also
The `docs/balena-cli.md` file is automatically generated by running `npm run build:doc` (which also
runs as part of `npm run build`). That file is then pulled by scripts in the
[balena-io/docs](https://github.com/balena-io/docs/) GitHub repo for publishing at the [CLI
Documentation page](https://www.balena.io/docs/reference/cli/).
The content sources for the auto generation of `doc/cli.markdown` are:
The content sources for the auto generation of `docs/balena-cli.md` are:
* [Selected
sections](https://github.com/balena-io/balena-cli/blob/v12.23.0/automation/capitanodoc/capitanodoc.ts#L199-L204)
@ -120,11 +120,58 @@ The content sources for the auto generation of `doc/cli.markdown` are:
* `lib/commands/env/add.ts`
The README file is manually edited, but subsections are automatically extracted for inclusion in
`doc/cli.markdown` by the `getCapitanoDoc()` function in
`docs/balena-cli.md` by the `getCapitanoDoc()` function in
[`automation/capitanodoc/capitanodoc.ts`](https://github.com/balena-io/balena-cli/blob/master/automation/capitanodoc/capitanodoc.ts).
**IMPORTANT**
The file [`capitanodoc.ts`](https://github.com/balena-io/balena-cli/blob/master/automation/capitanodoc/capitanodoc.ts) lists
commands to generate documentation from. At the moment, it's manually updated and maintained alphabetically.
To add a new command to be documented,
1. Find the resource which it is part of or create a new one.
2. List the location of the build file
3. Make sure to add your files in alphabetical order
4. Resources with plural names needs to have 2 sections if they have commands like: "fleet, fleets" or "device, devices" or "tag, tags"
Once added, run the command `npm run build` to generate the documentation
The `INSTALL*.md` and `TROUBLESHOOTING.md` files are also manually edited.
## Patches folder
The `patches` folder contains patch files created with the
[patch-package](https://www.npmjs.com/package/patch-package) tool. Small code changes to
third-party modules can be made by directly editing Javascript files under the `node_modules`
folder and then running `patch-package` to create the patch files. The patch files are then
applied immediately after `npm install`, through the `postinstall` script defined in
`package.json`.
The subfolders of the `patches` folder are documented in the
[apply-patches.js](https://github.com/balena-io/balena-cli/blob/master/patches/apply-patches.js)
script.
To make changes to the patch files under the `patches` folder, **do not edit them directly,**
not even for a "single character change" because the hash values in the patch files also need
to be recomputed by `patch-packages`. Instead, edit the relevant files under `node_modules`
directly, and then run `patch-packages` with the `--patch-dir` option to specify the subfolder
where the patch should be saved. For example, edit `node_modules/exit-hook/index.js` and then
run:
```sh
$ npx patch-package --patch-dir patches/all exit-hook
```
That said, these kinds of patches should be avoided in favour of creating pull requests
upstream. Patch files create additional maintenance work over time as the patches need to be
updated when the dependencies are updated, and they prevent the compounding community benefit
that sharing fixes upstream have on open source projects like the balena CLI. The typical
scenario where these patches are used is when the upstream maintainers are unresponsive or
unwilling to merge the required fixes, the fixes are very small and specific to the balena CLI,
and creating a fork of the upstream repo is likely to be more long-term effort than maintaining
the patches.
## Windows
Besides the regular npm installation dependencies, the `npm run build:installer` script
@ -267,4 +314,3 @@ gotchas to bear in mind:
replace: `spec: 'tests/**/*.spec.ts',`
with: `spec: ['tests/auth/*.spec.ts', 'tests/**/deploy.spec.ts'],`

View File

@ -78,8 +78,8 @@ 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
some development tools to be installed first, as follows.
> **The balena CLI currently requires Node.js version 10 (min 10.20.0) or 12.**
> **Versions 13 and later are not yet fully supported.**
> **The balena CLI currently requires Node.js version ^20.6.0**
> **Versions 21 and later are not yet fully supported.**
### Install development tools
@ -89,7 +89,7 @@ some development tools to be installed first, as follows.
$ sudo apt-get update && sudo apt-get -y install curl python3 git make g++
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash
$ . ~/.bashrc
$ nvm install 12
$ nvm install 20
```
The `curl` command line above uses
@ -106,15 +106,15 @@ recommended.
```sh
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash
$ . ~/.bashrc
$ nvm install 12
$ nvm install 20
```
#### **Windows** (not WSL)
Install:
* Node.js v12 from the [Nodejs.org releases page](https://nodejs.org/en/download/releases/).
* If you'd like the ability to switch between Node.js versions, install
- Node.js v20 from the [Nodejs.org releases page](https://nodejs.org/en/download/releases/).
[nvm-windows](https://github.com/coreybutler/nvm-windows#node-version-manager-nvm-for-windows)
instead.
* The [MSYS2 shell](https://www.msys2.org/), which provides `git`, `make`, `g++` and more:

View File

@ -144,7 +144,7 @@ To learn more, troubleshoot issues, or to contact us for support:
* Check the [masterclass tutorials](https://www.balena.io/docs/learn/more/masterclasses/overview/)
* Check our [FAQ / troubleshooting document](https://github.com/balena-io/balena-cli/blob/master/TROUBLESHOOTING.md)
* Ask us a question through the [balenaCloud forum](https://forums.balena.io/c/balena-cloud)
* Ask us a question in the [balena forums](https://forums.balena.io/c/product-support)
For CLI bug reports or feature requests, check the
[CLI GitHub issues](https://github.com/balena-io/balena-cli/issues/).

View File

@ -15,21 +15,21 @@
* limitations under the License.
*/
import type { JsonVersions } from '../lib/commands/version';
import type { JsonVersions } from '../lib/commands/version/index';
import { run as oclifRun } from '@oclif/dev-cli';
import { run as oclifRun } from '@oclif/core';
import * as archiver from 'archiver';
import * as Bluebird from 'bluebird';
import { execFile } from 'child_process';
import { exec, execFile } from 'child_process';
import * as filehound from 'filehound';
import { Stats } from 'fs';
import type { Stats } from 'fs';
import * as fs from 'fs-extra';
import * as klaw from 'klaw';
import * as _ from 'lodash';
import * as path from 'path';
import * as rimraf from 'rimraf';
import * as semver from 'semver';
import { promisify } from 'util';
import { notarize } from '@electron/notarize';
import { stripIndent } from '../build/utils/lazy';
import {
@ -41,12 +41,11 @@ import {
} from './utils';
const execFileAsync = promisify(execFile);
const execAsync = promisify(exec);
export const packageJSON = loadPackageJson();
export const version = 'v' + packageJSON.version;
const arch = process.arch;
const MSYS2_BASH =
process.env.MSYSSHELLPATH || 'C:\\msys64\\usr\\bin\\bash.exe';
function dPath(...paths: string[]) {
return path.join(ROOT, 'dist', ...paths);
@ -62,9 +61,13 @@ const standaloneZips: PathByPlatform = {
win32: dPath(`balena-cli-${version}-windows-${arch}-standalone.zip`),
};
const oclifInstallers: PathByPlatform = {
darwin: dPath('macos', `balena-${version}.pkg`),
win32: dPath('win', `balena-${version}-${arch}.exe`),
const getOclifInstallersOriginalNames = async (): Promise<PathByPlatform> => {
const { stdout } = await execAsync('git rev-parse --short HEAD');
const sha = stdout.trim();
return {
darwin: dPath('macos', `balena-${version}-${sha}-${arch}.pkg`),
win32: dPath('win32', `balena-${version}-${sha}-${arch}.exe`),
};
};
const renamedOclifInstallers: PathByPlatform = {
@ -89,13 +92,14 @@ async function diffPkgOutput(pkgOut: string) {
'tests',
'test-data',
'pkg',
`expected-warnings-${process.platform}.txt`,
`expected-warnings-${process.platform}-${arch}.txt`,
);
const absSavedPath = path.join(ROOT, relSavedPath);
const ignoreStartsWith = [
'> pkg@',
'> Fetching base Node.js binaries',
' fetched-',
'prebuild-install WARN install No prebuilt binaries found',
];
const modulesRE =
process.platform === 'win32'
@ -156,7 +160,7 @@ ${sep}
* messages (stdout and stderr) in order to call diffPkgOutput().
*/
async function execPkg(...args: any[]) {
const { exec: pkgExec } = await import('pkg');
const { exec: pkgExec } = await import('@yao-pkg/pkg');
const outTap = new StdOutTap(true);
try {
outTap.tap();
@ -181,9 +185,18 @@ async function execPkg(...args: any[]) {
* to be directly executed from inside another binary executable.)
*/
async function buildPkg() {
// https://github.com/vercel/pkg#targets
let targets = `linux-${arch}`;
if (process.platform === 'darwin') {
targets = `macos-${arch}`;
}
// TBC: not yet possible to build for Windows arm64 on x64 nodes
if (process.platform === 'win32') {
targets = `win-x64`;
}
const args = [
'--target',
'host',
'--targets',
targets,
'--output',
'build-bin/balena',
'package.json',
@ -198,7 +211,6 @@ async function buildPkg() {
const paths: Array<[string, string[], string[]]> = [
// [platform, [source path], [destination path]]
['*', ['open', 'xdg-open'], ['xdg-open']],
['*', ['opn', 'xdg-open'], ['xdg-open-402']],
['darwin', ['denymount', 'bin', 'denymount'], ['denymount']],
];
await Promise.all(
@ -310,11 +322,15 @@ async function zipPkg() {
archive.on('warning', console.warn);
archive.pipe(outputStream);
archive.finalize();
archive.finalize().catch(reject);
});
}
async function signFilesForNotarization() {
export async function signFilesForNotarization() {
console.log('Signing files for notarization');
if (process.platform !== 'darwin') {
return;
}
console.log('Deleting unneeded zip files...');
await new Promise((resolve, reject) => {
klaw('node_modules/')
@ -322,6 +338,10 @@ async function signFilesForNotarization() {
if (!item.stats.isFile()) {
return;
}
if (path.basename(item.path).endsWith('.node.bak')) {
console.log('Removing pkg .node.bak file', item.path);
fs.unlinkSync(item.path);
}
if (
path.basename(item.path).endsWith('.zip') &&
path.dirname(item.path).includes('test')
@ -410,6 +430,7 @@ export async function buildStandaloneZip() {
}
async function renameInstallerFiles() {
const oclifInstallers = await getOclifInstallersOriginalNames();
if (await fs.pathExists(oclifInstallers[process.platform])) {
await fs.rename(
oclifInstallers[process.platform],
@ -420,20 +441,30 @@ async function renameInstallerFiles() {
/**
* If the CSC_LINK and CSC_KEY_PASSWORD env vars are set, digitally sign the
* executable installer by running the balena-io/scripts/shared/sign-exe.sh
* script (which must be in the PATH) using a MSYS2 bash shell.
* executable installer using Microsoft SignTool.exe (Sign Tool)
* https://learn.microsoft.com/en-us/dotnet/framework/tools/signtool-exe
*/
async function signWindowsInstaller() {
if (process.env.CSC_LINK && process.env.CSC_KEY_PASSWORD) {
if (process.env.SM_CODE_SIGNING_CERT_SHA1_HASH) {
const exeName = renamedOclifInstallers[process.platform];
console.log(`Signing installer "${exeName}"`);
await execFileAsync(MSYS2_BASH, [
'sign-exe.sh',
'-f',
exeName,
// trust ...
await execFileAsync('signtool.exe', [
'sign',
'-sha1',
process.env.SM_CODE_SIGNING_CERT_SHA1_HASH,
'-tr',
process.env.TIMESTAMP_SERVER || 'http://timestamp.comodoca.com',
'-td',
'SHA256',
'-fd',
'SHA256',
'-d',
`balena-cli ${version}`,
exeName,
]);
// ... but verify
await execFileAsync('signtool.exe', ['verify', '-pa', '-v', exeName]);
} else {
console.log(
'Skipping installer signing step because CSC_* env vars are not set',
@ -445,18 +476,24 @@ async function signWindowsInstaller() {
* Wait for Apple Installer Notarization to continue
*/
async function notarizeMacInstaller(): Promise<void> {
const appleId = 'accounts+apple@balena.io';
const { notarize } = await import('electron-notarize');
await notarize({
appBundleId: 'io.balena.etcher',
appPath: renamedOclifInstallers.darwin,
appleId,
appleIdPassword: '@keychain:CLI_PASSWORD',
});
const teamId = process.env.XCODE_APP_LOADER_TEAM_ID || '66H43P8FRG';
const appleId =
process.env.XCODE_APP_LOADER_EMAIL || 'accounts+apple@balena.io';
const appleIdPassword = process.env.XCODE_APP_LOADER_PASSWORD;
if (appleIdPassword && teamId) {
await notarize({
tool: 'notarytool',
teamId,
appPath: renamedOclifInstallers.darwin,
appleId,
appleIdPassword,
});
}
}
/**
* 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
* the 'dist' folder). There are some harcoded options such as selecting only
* 64-bit binaries under Windows.
@ -466,9 +503,10 @@ export async function buildOclifInstaller() {
let packOpts = ['-r', ROOT];
if (process.platform === 'darwin') {
packOS = 'macos';
packOpts = packOpts.concat('--targets', `darwin-${arch}`);
} else if (process.platform === 'win32') {
packOS = 'win';
packOpts = packOpts.concat('-t', 'win32-x64');
packOpts = packOpts.concat('--targets', 'win32-x64');
}
if (packOS) {
console.log(`Building oclif installer for CLI ${version}`);
@ -481,15 +519,12 @@ export async function buildOclifInstaller() {
console.log(`rimraf(${dir})`);
await Bluebird.fromCallback((cb) => rimraf(dir, cb));
}
if (process.platform === 'darwin') {
console.log('Signing files for notarization...');
await signFilesForNotarization();
}
console.log('=======================================================');
console.log(`oclif-dev "${packCmd}" "${packOpts.join('" "')}"`);
console.log(`oclif ${packCmd} ${packOpts.join(' ')}`);
console.log(`cwd="${process.cwd()}" ROOT="${ROOT}"`);
console.log('=======================================================');
await oclifRun([packCmd].concat(...packOpts));
const oclifPath = path.join(ROOT, 'node_modules', 'oclif');
await oclifRun([packCmd].concat(...packOpts), oclifPath);
await renameInstallerFiles();
// The Windows installer is explicitly signed here (oclif doesn't do it).
// The macOS installer is automatically signed by oclif (which runs the

View File

@ -17,6 +17,7 @@
import * as path from 'path';
import { MarkdownFileParser } from './utils';
import { GlobSync } from 'glob';
/**
* This is the skeleton of CLI documentation/reference web page at:
@ -24,180 +25,120 @@ import { MarkdownFileParser } from './utils';
*
* The `getCapitanoDoc` function in this module parses README.md and adds
* some content to this object.
*
* IMPORTANT
*
* All commands need to be stored under a folder in lib/commands to maintain uniformity
* Generating docs will error out if directive not followed
* To add a custom heading for command docs, add the heading next to the folder name
* in the `commandHeadings` dictionary.
*
* This dictionary is the source of truth that creates the docs config which is used
* to generate the CLI documentation. By default, the folder name will be used.
*
* Resources with plural names needs to have 2 sections if they have commands like:
* "fleet, fleets" or "device, devices" or "tag, tags"
*
*/
const capitanoDoc = {
title: 'balena CLI Documentation',
introduction: '',
categories: [
{
title: 'API keys',
files: ['build/commands/api-key/generate.js'],
},
{
title: 'Fleet',
files: [
'build/commands/apps.js',
'build/commands/fleets.js',
'build/commands/app/index.js',
'build/commands/fleet/index.js',
'build/commands/app/create.js',
'build/commands/fleet/create.js',
'build/commands/app/purge.js',
'build/commands/fleet/purge.js',
'build/commands/app/rename.js',
'build/commands/fleet/rename.js',
'build/commands/app/restart.js',
'build/commands/fleet/restart.js',
'build/commands/app/rm.js',
'build/commands/fleet/rm.js',
],
},
{
title: 'Authentication',
files: [
'build/commands/login.js',
'build/commands/logout.js',
'build/commands/whoami.js',
],
},
{
title: 'Device',
files: [
'build/commands/devices/index.js',
'build/commands/devices/supported.js',
'build/commands/device/index.js',
'build/commands/device/deactivate.js',
'build/commands/device/identify.js',
'build/commands/device/init.js',
'build/commands/device/local-mode.js',
'build/commands/device/move.js',
'build/commands/device/os-update.js',
'build/commands/device/public-url.js',
'build/commands/device/purge.js',
'build/commands/device/reboot.js',
'build/commands/device/register.js',
'build/commands/device/rename.js',
'build/commands/device/restart.js',
'build/commands/device/rm.js',
'build/commands/device/shutdown.js',
],
},
{
title: 'Releases',
files: [
'build/commands/releases.js',
'build/commands/release/index.js',
'build/commands/release/finalize.js',
],
},
{
title: 'Environment Variables',
files: [
'build/commands/envs.js',
'build/commands/env/add.js',
'build/commands/env/rename.js',
'build/commands/env/rm.js',
],
},
{
title: 'Tags',
files: [
'build/commands/tags.js',
'build/commands/tag/rm.js',
'build/commands/tag/set.js',
],
},
{
title: 'Help and Version',
files: ['help', 'build/commands/version.js'],
},
{
title: 'Keys',
files: [
'build/commands/keys.js',
'build/commands/key/index.js',
'build/commands/key/add.js',
'build/commands/key/rm.js',
],
},
{
title: 'Logs',
files: ['build/commands/logs.js'],
},
{
title: 'Network',
files: [
'build/commands/scan.js',
'build/commands/ssh.js',
'build/commands/tunnel.js',
],
},
{
title: 'Notes',
files: ['build/commands/note.js'],
},
{
title: 'OS',
files: [
'build/commands/os/build-config.js',
'build/commands/os/configure.js',
'build/commands/os/versions.js',
'build/commands/os/download.js',
'build/commands/os/initialize.js',
],
},
{
title: 'Config',
files: [
'build/commands/config/generate.js',
'build/commands/config/inject.js',
'build/commands/config/read.js',
'build/commands/config/reconfigure.js',
'build/commands/config/write.js',
],
},
{
title: 'Preload',
files: ['build/commands/preload.js'],
},
{
title: 'Push',
files: ['build/commands/push.js'],
},
{
title: 'Settings',
files: ['build/commands/settings.js'],
},
{
title: 'Local',
files: [
'build/commands/local/configure.js',
'build/commands/local/flash.js',
],
},
{
title: 'Deploy',
files: ['build/commands/build.js', 'build/commands/deploy.js'],
},
{
title: 'Platform',
files: ['build/commands/join.js', 'build/commands/leave.js'],
},
{
title: 'Utilities',
files: ['build/commands/util/available-drives.js'],
},
{
title: 'Support',
files: ['build/commands/support.js'],
},
],
interface Category {
title: string;
files: string[];
}
interface Documentation {
title: string;
introduction: string;
categories: Category[];
}
// Mapping folders names to custom headings in the docs
const commandHeadings: { [key: string]: string } = {
'api-key': 'API Keys',
'api-keys': 'API Keys',
login: 'Authentication',
whoami: 'Authentication',
logout: 'Authentication',
env: 'Environment Variables',
envs: 'Environment Variables',
help: 'Help and Version',
key: 'SSH Keys',
keys: 'SSH Keys',
orgs: 'Organizations',
os: 'OS',
util: 'Utilities',
ssh: 'Network',
scan: 'Network',
tunnel: 'Network',
build: 'Deploy',
join: 'Platform',
leave: 'Platform',
app: 'Apps',
block: 'Blocks',
device: 'Devices',
fleet: 'Fleets',
release: 'Releases',
tag: 'Tags',
};
// Fetch all available commands
const allCommandsPaths = new GlobSync('build/commands/**/*.js', {
ignore: 'build/commands/internal/**',
}).found;
// Throw error if any commands found outside of command directories
const illegalCommandPaths = allCommandsPaths.filter((commandPath: string) =>
/^build\/commands\/[^/]+\.js$/.test(commandPath),
);
if (illegalCommandPaths.length !== 0) {
throw new Error(
`Found the following commands without a command directory: ${illegalCommandPaths}\n
To resolve this error, move the respective commands to their resource directories or create new ones.\n
Refer to the automation/capitanodoc/capitanodoc.ts file for more information.`,
);
}
// Docs config template
const capitanoDoc: Documentation = {
title: 'balena CLI Documentation',
introduction: '',
categories: [],
};
// Helper function to capitalize each word of directory name
function formatTitle(dir: string): string {
return dir.replace(/(^\w|\s\w)/g, (word) => word.toUpperCase());
}
// Create a map to track the categories for faster lookup
const categoriesMap: { [key: string]: Category } = {};
for (const commandPath of allCommandsPaths) {
const commandDir = path.basename(path.dirname(commandPath));
const heading = commandHeadings[commandDir] || formatTitle(commandDir);
if (!categoriesMap[heading]) {
categoriesMap[heading] = { title: heading, files: [] };
capitanoDoc.categories.push(categoriesMap[heading]);
}
categoriesMap[heading].files.push(commandPath);
}
// Sort Category titles alphabetically
capitanoDoc.categories = capitanoDoc.categories.sort((a, b) =>
a.title.localeCompare(b.title),
);
// Sort Category file paths alphabetically
capitanoDoc.categories.forEach((category) => {
category.files.sort((a, b) => a.localeCompare(b));
});
/**
* Modify and return the `capitanoDoc` object above in order to render the
* CLI documentation/reference web page at:
* https://www.balena.io/docs/reference/cli/
* Modify and return the `capitanoDoc` object above in order to generate the
* CLI documentation at docs/balena-cli.md
*
* This function parses the README.md file to extract relevant sections
* for the documentation web page.

View File

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Command as OclifCommandClass } from '@oclif/command';
import type { Command as OclifCommandClass } from '@oclif/core';
type OclifCommand = typeof OclifCommandClass;

View File

@ -16,7 +16,7 @@
*/
import * as path from 'path';
import { getCapitanoDoc } from './capitanodoc';
import { Category, Document, OclifCommand } from './doc-types';
import type { Category, Document, OclifCommand } from './doc-types';
import * as markdown from './markdown';
import { stripIndent } from '../../lib/utils/lazy';
@ -62,12 +62,11 @@ class FakeHelpCommand {
'$ balena help os download',
];
args = [
{
name: 'command',
args = {
command: {
description: 'command to show help for',
},
];
};
usage = 'help [command]';
@ -105,4 +104,5 @@ async function printMarkdown() {
}
}
// eslint-disable-next-line @typescript-eslint/no-floating-promises
printMarkdown();

View File

@ -14,13 +14,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { flagUsages } from '@oclif/parser';
import { Parser } from '@oclif/core';
import * as ent from 'ent';
import * as _ from 'lodash';
import { getManualSortCompareFunction } from '../../lib/utils/helpers';
import { capitanoizeOclifUsage } from '../../lib/utils/oclif-utils';
import { Category, Document, OclifCommand } from './doc-types';
import type { Category, Document, OclifCommand } from './doc-types';
function renderOclifCommand(command: OclifCommand): string[] {
const result = [`## ${ent.encode(command.usage || '')}`];
@ -37,8 +37,8 @@ function renderOclifCommand(command: OclifCommand): string[] {
if (!_.isEmpty(command.args)) {
result.push('### Arguments');
for (const arg of command.args!) {
result.push(`#### ${arg.name.toUpperCase()}`, arg.description || '');
for (const [name, arg] of Object.entries(command.args!)) {
result.push(`#### ${name.toUpperCase()}`, arg.description || '');
}
}
@ -49,7 +49,7 @@ function renderOclifCommand(command: OclifCommand): string[] {
continue;
}
flag.name = name;
const flagUsage = flagUsages([flag])
const flagUsage = Parser.flagUsages([flag])
.map(([usage, _description]) => usage)
.join()
.trim();

View File

@ -15,24 +15,25 @@
* limitations under the License.
*/
const stripIndent = require('common-tags/lib/stripIndent');
const _ = require('lodash');
const { promises: fs } = require('fs');
const path = require('path');
const simplegit = require('simple-git/promise');
// eslint-disable-next-line no-restricted-imports
import { stripIndent } from 'common-tags';
import * as _ from 'lodash';
import { promises as fs } from 'fs';
import * as path from 'path';
import { simpleGit } from 'simple-git';
const ROOT = path.normalize(path.join(__dirname, '..'));
/**
* Compare the timestamp of cli.markdown with the timestamp of staged files,
* issuing an error if cli.markdown is older.
* If cli.markdown does not require updating and the developer cannot run
* Compare the timestamp of balena-cli.md with the timestamp of staged files,
* issuing an error if balena-cli.md is older.
* If balena-cli.md does not require updating and the developer cannot run
* `npm run build` on their laptop, the error message suggests a workaround
* using `touch`.
*/
async function checkBuildTimestamps() {
const git = simplegit(ROOT);
const docFile = path.join(ROOT, 'doc', 'cli.markdown');
const git = simpleGit(ROOT);
const docFile = path.join(ROOT, 'docs', 'balena-cli.md');
const [docStat, gitStatus] = await Promise.all([
fs.stat(docFile),
git.status(),
@ -81,4 +82,5 @@ async function run() {
}
}
// eslint-disable-next-line @typescript-eslint/no-floating-promises
run();

View File

@ -6,6 +6,8 @@
*
* 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.
*
* @param {string} version
*/
function parseSemver(version) {
const match = /v?(\d+)\.(\d+).(\d+)/.exec(version);
@ -16,9 +18,13 @@ function parseSemver(version) {
return [parseInt(major, 10), parseInt(minor, 10), parseInt(patch, 10)];
}
/**
* @param {string} v1
* @param {string} v2
*/
function semverGte(v1, v2) {
let v1Array = parseSemver(v1);
let v2Array = parseSemver(v2);
const v1Array = parseSemver(v1);
const v2Array = parseSemver(v2);
for (let i = 0; i < 3; i++) {
if (v1Array[i] < v2Array[i]) {
return false;

View File

@ -30,7 +30,7 @@ const { GITHUB_TOKEN } = process.env;
export async function createGitHubRelease() {
console.log(`Publishing release ${version} to GitHub`);
const publishRelease = await import('publish-release');
const ghRelease = await Bluebird.fromCallback(
const ghRelease = (await Bluebird.fromCallback(
publishRelease.bind(null, {
token: GITHUB_TOKEN || '',
owner: 'balena-io',
@ -40,7 +40,7 @@ export async function createGitHubRelease() {
reuseRelease: true,
assets: finalReleaseAssets[process.platform],
}),
);
)) as { html_url: any };
console.log(`Release ${version} successful: ${ghRelease.html_url}`);
}
@ -154,7 +154,7 @@ async function updateGitHubReleaseDescriptions(
) {
const perPage = 30;
const octokit = getOctokit();
const options = await octokit.repos.listReleases.endpoint.merge({
const options = octokit.repos.listReleases.endpoint.merge({
owner,
repo,
per_page: perPage,

View File

@ -21,6 +21,7 @@ import {
buildOclifInstaller,
buildStandaloneZip,
catchUncommitted,
signFilesForNotarization,
testShrinkwrap,
} from './build-bin';
import {
@ -54,32 +55,18 @@ async function parse(args?: string[]) {
const commands: { [cmd: string]: () => void | Promise<void> } = {
'build:installer': buildOclifInstaller,
'build:standalone': buildStandaloneZip,
'sign:binaries': signFilesForNotarization,
'catch-uncommitted': catchUncommitted,
'test-shrinkwrap': testShrinkwrap,
fix1359: updateDescriptionOfReleasesAffectedByIssue1359,
release,
};
for (const arg of args) {
if (!commands.hasOwnProperty(arg)) {
if (!Object.hasOwn(commands, arg)) {
throw new Error(`command unknown: ${arg}`);
}
}
// The BUILD_TMP env var is used as an alternative location for oclif
// (patched) to copy/extract the CLI files, run npm install and then
// create the NSIS executable installer for Windows. This was necessary
// to avoid issues with a 260-char limit on Windows paths (possibly a
// limitation of some library used by NSIS), as the "current working dir"
// provided by balena CI is a rather long path to start with.
if (process.platform === 'win32' && !process.env.BUILD_TMP) {
const randID = (await import('crypto'))
.randomBytes(6)
.toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_'); // base64url (RFC 4648)
process.env.BUILD_TMP = `C:\\tmp\\${randID}`;
}
for (const arg of args) {
try {
const cmdFunc = commands[arg];
@ -103,4 +90,5 @@ export async function run(args?: string[]) {
}
}
// eslint-disable-next-line @typescript-eslint/no-floating-promises
run();

View File

@ -36,8 +36,8 @@ const run = async (cmd: string) => {
}
resolve({ stdout, stderr });
});
p.stdout.pipe(process.stdout);
p.stderr.pipe(process.stderr);
p.stdout?.pipe(process.stdout);
p.stderr?.pipe(process.stderr);
});
};
@ -107,11 +107,11 @@ async function $main() {
const changeType = process.argv[4]
? // if the caller specified a change type, use that one
validateChangeType(process.argv[4])
validateChangeType(process.argv[4])
: // use the same change type as in the dependency, but avoid major bumps
semverChangeType && semverChangeType !== 'major'
? semverChangeType
: 'minor';
semverChangeType && semverChangeType !== 'major'
? semverChangeType
: 'minor';
console.log(`Using Change-type: ${changeType}`);
let { stdout: currentBranch } = await run('git rev-parse --abbrev-ref HEAD');
@ -136,4 +136,5 @@ async function main() {
}
}
// eslint-disable-next-line @typescript-eslint/no-floating-promises
main();

View File

@ -16,7 +16,6 @@
*/
import { spawn } from 'child_process';
import * as _ from 'lodash';
import * as path from 'path';
export const ROOT = path.join(__dirname, '..');
@ -77,8 +76,8 @@ export function diffLines(str1: string, str2: string): string {
return part.added
? prefix(part.value, '+')
: part.removed
? prefix(part.value, '-')
: prefix(part.value, ' ');
? prefix(part.value, '-')
: prefix(part.value, ' ');
})
.join('\n');
return diffStr;
@ -117,7 +116,7 @@ export async function which(program: string): Promise<string> {
*/
export async function whichSpawn(
programName: string,
args?: string[],
args: string[] = [],
): Promise<void> {
const program = await which(programName);
let error: Error | undefined;

View File

@ -1,23 +0,0 @@
#!/usr/bin/env node
// tslint:disable:no-var-requires
// We boost the threadpool size as ext2fs can deadlock with some
// operations otherwise, if the pool runs out.
process.env.UV_THREADPOOL_SIZE = '64';
// Disable oclif registering ts-node
process.env.OCLIF_TS_NODE = 0;
async function run() {
// Use fast-boot to cache require lookups, speeding up startup
await require('../build/fast-boot').start();
// Set the desired es version for downstream modules that support it
require('@balena/es-version').set('es2018');
// Run the CLI
await require('../build/app').run();
}
run();

1
bin/balena Symbolic link
View File

@ -0,0 +1 @@
run.js

View File

@ -1,89 +0,0 @@
#!/usr/bin/env node
// ****************************************************************************
// THIS IS FOR DEV PURPOSES ONLY AND WILL NOT BE PART OF THE PUBLISHED PACKAGE
// Before opening a PR you should build and test your changes using bin/balena
// ****************************************************************************
// tslint:disable:no-var-requires
// We boost the threadpool size as ext2fs can deadlock with some
// operations otherwise, if the pool runs out.
process.env.UV_THREADPOOL_SIZE = '64';
// Note on `fast-boot2`: We do not use `fast-boot2` with `balena-dev` because:
// * fast-boot2's cacheKiller option is configured to include the timestamps of
// the package.json and npm-shrinkwrap.json files, to avoid unexpected CLI
// behavior when changes are made to dependencies during development. This is
// generally a good thing, however, `balena-dev` (a few lines below) edits
// `package.json` to modify oclif paths, and this results in cache
// invalidation and a performance hit rather than speedup.
// * Even if the timestamps are removed from cacheKiller, so that there is no
// cache invalidation, fast-boot's speedup is barely noticeable when ts-node
// is used, e.g. 1.43s vs 1.4s when running `balena version`.
// * `fast-boot` causes unexpected behavior when used with `npm link` or
// when the `node_modules` folder is manually modified (affecting transitive
// dependencies) during development (e.g. bug investigations). A workaround
// is to use `balena-dev` without `fast-boot`. See also notes in
// `CONTRIBUTING.md`.
const path = require('path');
const rootDir = path.join(__dirname, '..');
// Allow balena-dev to work with oclif by temporarily
// pointing oclif config options to lib/ instead of build/
modifyOclifPaths();
// Undo changes on exit
process.on('exit', function () {
modifyOclifPaths(true);
});
// Undo changes in case of ctrl-c
process.on('SIGINT', function () {
modifyOclifPaths(true);
// Note process exit here will interfere with commands that do their own SIGINT handling,
// but without it commands can not be exited.
// So currently using balena-dev does not guarantee proper exit behaviour when using ctrl-c.
// Ideally a better solution is needed.
process.exit();
});
// Set the desired es version for downstream modules that support it
require('@balena/es-version').set('es2018');
// Note: before ts-node v6.0.0, 'transpile-only' (no type checking) was the
// default option. We upgraded ts-node and found that adding 'transpile-only'
// was necessary to avoid a mysterious 'null' error message. On the plus side,
// it is supposed to run faster. We still benefit from type checking when
// running 'npm run build'.
require('ts-node').register({
project: path.join(rootDir, 'tsconfig.json'),
transpileOnly: true,
});
require('../lib/app').run();
// Modify package.json oclif paths from build/ -> lib/, or vice versa
function modifyOclifPaths(revert) {
const fs = require('fs');
const packageJsonPath = path.join(rootDir, 'package.json');
const packageJson = fs.readFileSync(packageJsonPath, 'utf8');
const packageObj = JSON.parse(packageJson);
if (!packageObj.oclif) {
return;
}
let oclifSectionText = JSON.stringify(packageObj.oclif);
if (!revert) {
oclifSectionText = oclifSectionText.replace(/\/build\//g, '/lib/');
} else {
oclifSectionText = oclifSectionText.replace(/\/lib\//g, '/build/');
}
packageObj.oclif = JSON.parse(oclifSectionText);
fs.writeFileSync(
packageJsonPath,
`${JSON.stringify(packageObj, null, 2)}\n`,
'utf8',
);
}

1
bin/balena-dev Symbolic link
View File

@ -0,0 +1 @@
dev.js

3
bin/dev.cmd Normal file
View File

@ -0,0 +1,3 @@
@echo off
node "%~dp0\run" %*

87
bin/dev.js Executable file
View File

@ -0,0 +1,87 @@
#!/usr/bin/env node
// ****************************************************************************
// THIS IS FOR DEV PURPOSES ONLY AND WILL NOT BE PART OF THE PUBLISHED PACKAGE
// Before opening a PR you should build and test your changes using bin/balena
// ****************************************************************************
// We boost the threadpool size as ext2fs can deadlock with some
// operations otherwise, if the pool runs out.
process.env.UV_THREADPOOL_SIZE = '64';
// Note on `fast-boot2`: We do not use `fast-boot2` with `balena-dev` because:
// * fast-boot2's cacheKiller option is configured to include the timestamps of
// the package.json and npm-shrinkwrap.json files, to avoid unexpected CLI
// behavior when changes are made to dependencies during development. This is
// generally a good thing, however, `balena-dev` (a few lines below) edits
// `package.json` to modify oclif paths, and this results in cache
// invalidation and a performance hit rather than speedup.
// * Even if the timestamps are removed from cacheKiller, so that there is no
// cache invalidation, fast-boot's speedup is barely noticeable when ts-node
// is used, e.g. 1.43s vs 1.4s when running `balena version`.
// * `fast-boot` causes unexpected behavior when used with `npm link` or
// when the `node_modules` folder is manually modified (affecting transitive
// dependencies) during development (e.g. bug investigations). A workaround
// is to use `balena-dev` without `fast-boot`. See also notes in
// `CONTRIBUTING.md`.
const path = require('path');
const rootDir = path.join(__dirname, '..');
// Allow balena-dev to work with oclif by temporarily
// pointing oclif config options to lib/ instead of build/
modifyOclifPaths();
// Undo changes on exit
process.on('exit', function () {
modifyOclifPaths(true);
});
// Undo changes in case of ctrl-c
process.on('SIGINT', function () {
modifyOclifPaths(true);
// Note process exit here will interfere with commands that do their own SIGINT handling,
// but without it commands can not be exited.
// So currently using balena-dev does not guarantee proper exit behaviour when using ctrl-c.
// Ideally a better solution is needed.
process.exit();
});
// Set the desired es version for downstream modules that support it
require('@balena/es-version').set('es2018');
// Note: before ts-node v6.0.0, 'transpile-only' (no type checking) was the
// default option. We upgraded ts-node and found that adding 'transpile-only'
// was necessary to avoid a mysterious 'null' error message. On the plus side,
// it is supposed to run faster. We still benefit from type checking when
// running 'npm run build'.
require('ts-node').register({
project: path.join(rootDir, 'tsconfig.json'),
transpileOnly: true,
});
require('../lib/app').run(undefined, { dir: __dirname, development: true });
// Modify package.json oclif paths from build/ -> lib/, or vice versa
function modifyOclifPaths(revert) {
const fs = require('fs');
const packageJsonPath = path.join(rootDir, 'package.json');
const packageJson = fs.readFileSync(packageJsonPath, 'utf8');
const packageObj = JSON.parse(packageJson);
if (!packageObj.oclif) {
return;
}
let oclifSectionText = JSON.stringify(packageObj.oclif);
if (!revert) {
oclifSectionText = oclifSectionText.replace(/\/build\//g, '/lib/');
} else {
oclifSectionText = oclifSectionText.replace(/\/lib\//g, '/build/');
}
packageObj.oclif = JSON.parse(oclifSectionText);
fs.writeFileSync(
packageJsonPath,
`${JSON.stringify(packageObj, null, 2)}\n`,
'utf8',
);
}

3
bin/run.cmd Normal file
View File

@ -0,0 +1,3 @@
@echo off
node "%~dp0\run" %*

21
bin/run.js Executable file
View File

@ -0,0 +1,21 @@
#!/usr/bin/env node
// We boost the threadpool size as ext2fs can deadlock with some
// operations otherwise, if the pool runs out.
process.env.UV_THREADPOOL_SIZE = '64';
// Disable oclif registering ts-node
process.env.OCLIF_TS_NODE = 0;
async function run() {
// Use fast-boot to cache require lookups, speeding up startup
await require('../build/fast-boot').start();
// Set the desired es version for downstream modules that support it
require('@balena/es-version').set('es2018');
// Run the CLI
await require('../build/app').run(undefined, { dir: __dirname });
}
run();

View File

@ -8,20 +8,21 @@ _balena() {
local context state line curcontext="$curcontext"
# 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=( api-key api-keys app block build config deploy device device devices env envs fleet fleet fleets internal join key key keys leave local login logout logs notes orgs os preload push release release releases scan settings ssh support tag tags tunnel util version whoami )
# Sub-completions
api_key_cmds=( generate )
app_cmds=( create purge rename restart rm )
api_key_cmds=( generate revoke )
app_cmds=( create )
block_cmds=( create )
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 pin public-url purge reboot register rename restart rm shutdown start-service stop-service track-fleet )
devices_cmds=( supported )
env_cmds=( add rename rm )
fleet_cmds=( create purge rename restart rm )
fleet_cmds=( create pin purge rename restart rm track-latest )
internal_cmds=( osinit )
key_cmds=( add rm )
local_cmds=( configure flash )
os_cmds=( build-config configure download initialize versions )
release_cmds=( finalize )
release_cmds=( finalize invalidate validate )
tag_cmds=( rm set )
@ -47,6 +48,9 @@ _balena_sec_cmds() {
"app")
_describe -t app_cmds 'app_cmd' app_cmds "$@" && ret=0
;;
"block")
_describe -t block_cmds 'block_cmd' block_cmds "$@" && ret=0
;;
"config")
_describe -t config_cmds 'config_cmd' config_cmds "$@" && ret=0
;;

View File

@ -7,20 +7,21 @@ _balena_complete()
local cur prev
# 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="api-key api-keys app block build config deploy device device devices env envs fleet fleet fleets internal join key key keys leave local login logout logs notes orgs os preload push release release releases scan settings ssh support tag tags tunnel util version whoami"
# Sub-completions
api_key_cmds="generate"
app_cmds="create purge rename restart rm"
api_key_cmds="generate revoke"
app_cmds="create"
block_cmds="create"
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 pin public-url purge reboot register rename restart rm shutdown start-service stop-service track-fleet"
devices_cmds="supported"
env_cmds="add rename rm"
fleet_cmds="create purge rename restart rm"
fleet_cmds="create pin purge rename restart rm track-latest"
internal_cmds="osinit"
key_cmds="add rm"
local_cmds="configure flash"
os_cmds="build-config configure download initialize versions"
release_cmds="finalize"
release_cmds="finalize invalidate validate"
tag_cmds="rm set"
@ -41,6 +42,9 @@ _balena_complete()
app)
COMPREPLY=( $(compgen -W "$app_cmds" -- $cur) )
;;
block)
COMPREPLY=( $(compgen -W "$block_cmds" -- $cur) )
;;
config)
COMPREPLY=( $(compgen -W "$config_cmds" -- $cur) )
;;

View File

@ -31,8 +31,8 @@ if (fs.existsSync(commandsFilePath)) {
const commandsJson = JSON.parse(fs.readFileSync(commandsFilePath, 'utf8'));
var mainCommands = [];
var additionalCommands = [];
const mainCommands = [];
const additionalCommands = [];
for (const key of Object.keys(commandsJson.commands)) {
const cmd = key.split(':');
if (cmd.length > 1) {
@ -72,8 +72,8 @@ fs.readFile(bashFilePathIn, 'utf8', function (err, data) {
/\$main_commands\$/g,
'main_commands="' + mainCommandsStr + '"',
);
var subCommands = [];
var prevElement = additionalCommands[0][0];
let subCommands = [];
let prevElement = additionalCommands[0][0];
additionalCommands.forEach(function (element) {
if (element[0] === prevElement) {
subCommands.push(element[1]);
@ -134,8 +134,8 @@ fs.readFile(zshFilePathIn, 'utf8', function (err, data) {
/\$main_commands\$/g,
'main_commands=( ' + mainCommandsStr + ' )',
);
var subCommands = [];
var prevElement = additionalCommands[0][0];
let subCommands = [];
let prevElement = additionalCommands[0][0];
additionalCommands.forEach(function (element) {
if (element[0] === prevElement) {
subCommands.push(element[1]);

View File

@ -1,112 +0,0 @@
# Provisioning balena devices in automated (non-interactive) mode
This document describes how to run the `device init` command in non-interactive mode.
It requires collecting some preliminary information _once_.
The final command to provision the device looks like this:
```bash
balena device init --fleet FLEET_ID --os-version OS_VERSION --drive DRIVE --config CONFIG_FILE --yes
```
You can run this command as many times as you need, putting the new medium (SD card / USB stick) each time.
But before you can run it you need to collect the parameters and build the configuration file. Keep reading to figure out how to do it.
## Collect all the required parameters.
1. `DEVICE_TYPE`. Run
```bash
balena devices supported
```
and find the _slug_ for your target device type, like _raspberrypi3_.
1. `FLEET_ID`. Create a fleet (`balena fleet create FLEET_NAME --type DEVICE_TYPE`) or find an existing one (`balena fleets`) and notice its ID.
1. `OS_VERSION`. Run
```bash
balena os versions DEVICE_TYPE
```
and pick the version that you need, like _v2.0.6+rev1.prod_.
_Note_ that even though we support _semver ranges_ it's recommended to use the exact version when doing the automated provisioning as it
guarantees full compatibility between the steps.
1. `DRIVE`. Plug in your target medium (SD card or the USB stick, depending on your device type) and run
```bash
balena util available-drives
```
and get the drive name, like _/dev/sdb_ or _/dev/mmcblk0_.
The balena CLI will not display the system drives to protect you,
but still please check very carefully that you've picked the correct drive as it will be erased during the provisioning process.
Now we have all the parameters -- time to build the config file.
## Build the config file
Interactive device provisioning process often includes collecting some extra device configuration, like the networking mode and wifi credentials.
To skip this interactive step we need to buid this configuration once and save it to the JSON file for later reuse.
Let's say we will place it into the `CONFIG_FILE` path, like _./balena-os/raspberrypi3-config.json_.
We also need to put the OS image somewhere, let's call this path `OS_IMAGE_PATH`, it can be something like _./balena-os/raspberrypi3-v2.0.6+rev1.prod.img_.
1. First we need to download the OS image once. That's needed for building the config, and will speedup the subsequent operations as the downloaded OS image is placed into the local cache.
Run:
```bash
balena os download DEVICE_TYPE --output OS_IMAGE_PATH --version OS_VERSION
```
1. Now we're ready to build the config:
```bash
balena os build-config OS_IMAGE_PATH DEVICE_TYPE --output CONFIG_FILE
```
This will run you through the interactive configuration wizard and in the end save the generated config as `CONFIG_FILE`. You can then verify it's not empty:
```bash
cat CONFIG_FILE
```
## Done
Now you're ready to run the command in the beginning of this guide.
Please note again that all of these steps only need to be done once (unless you need to change something), and once all the parameters are collected the main init command can be run unchanged.
But there are still some nuances to cover, please read below.
## Nuances
### `sudo` password on *nix systems
In order to write the image to the raw device we need the root permissions, this is unavoidable.
To improve the security we only run the minimal subcommand with `sudo`.
This means that with the default setup you're interrupted closer to the end of the device init process to enter your sudo password for this subcommand to work.
There are several ways to eliminate it and make the process fully non-interactive.
#### Option 1: make passwordless sudo.
Obviously you shouldn't do that if the machine you're working on has access to any sensitive resources or information.
But if you're using a machine dedicated to balena provisioning this can be fine, and also the simplest thing to do.
#### Option 2: `NOPASSWD` directive
You can configure the `balena` CLI command to be sudo-runnable without the password. Check [this post](https://askubuntu.com/questions/159007/how-do-i-run-specific-sudo-commands-without-a-password) for an example.
### Extra initialization config
As of June 2017 all the supported devices should not require any other interactive configuration.
But by the design of our system it is _possible_ (though it doesn't look very likely it's going to happen any time soon) that some extra initialization options may be requested for the specific device types.
If that is the case please raise the issue in the balena CLI repository and the maintainers will add the necessary options to build the similar JSON config for this step.

File diff suppressed because it is too large Load Diff

View File

@ -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')),
);

View File

@ -16,14 +16,15 @@
*/
import * as packageJSON from '../package.json';
import type { AppOptions } from './preparser';
import {
AppOptions,
checkDeletedCommand,
preparseArgs,
unsupportedFlag,
} from './preparser';
import { CliSettings } from './utils/bootstrap';
import { onceAsync } from './utils/lazy';
import { run as mainRun, settings } from '@oclif/core';
/**
* Sentry.io setup
@ -77,11 +78,13 @@ export function setMaxListeners(maxListeners: number) {
/** Selected CLI initialization steps */
async function init() {
if (process.env.BALENARC_NO_SENTRY) {
console.error(`WARN: disabling Sentry.io error reporting`);
if (process.env.DEBUG) {
console.error(`WARN: disabling Sentry.io error reporting`);
}
} else {
await setupSentry();
}
checkNodeVersion();
await checkNodeVersion();
const settings = new CliSettings();
@ -91,7 +94,9 @@ async function init() {
setupBalenaSdkSharedOptions(settings);
// check for CLI updates once a day
(await import('./utils/update')).notify();
if (!process.env.BALENARC_OFFLINE_MODE) {
(await import('./utils/update')).notify();
}
}
/** Execute the oclif parser and the CLI command. */
@ -110,10 +115,16 @@ async function oclifRun(command: string[], options: AppOptions) {
}
const runPromise = (async function (shouldFlush: boolean) {
const { CustomMain } = await import('./utils/oclif-utils');
let isEEXIT = false;
try {
await CustomMain.run(command);
if (options.development) {
// In dev mode -> use ts-node and dev plugins
process.env.NODE_ENV = 'development';
settings.debug = true;
}
// For posteriority: We can't use default oclif 'execute' as
// We customize error handling and flushing
await mainRun(command, options.loadOptions ?? options.dir);
} catch (error) {
// oclif sometimes exits with ExitError code EEXIT 0 (not an error),
// for example the `balena help` command.
@ -126,7 +137,7 @@ async function oclifRun(command: string[], options: AppOptions) {
}
}
if (shouldFlush) {
await import('@oclif/command/flush');
await import('@oclif/core/flush');
}
// TODO: figure out why we need to call fast-boot stop() here, in
// addition to calling it in the main `run()` function in this file.
@ -146,10 +157,13 @@ async function oclifRun(command: string[], options: AppOptions) {
await Promise.all([trackPromise, deprecationPromise, runPromise]);
}
/** CLI entrypoint. Called by the `bin/balena` and `bin/balena-dev` scripts. */
export async function run(cliArgs = process.argv, options: AppOptions = {}) {
/** CLI entrypoint. Called by the `bin/run.js` and `bin/dev.js` scripts. */
export async function run(cliArgs = process.argv, options: AppOptions) {
try {
const { normalizeEnvVars, pkgExec } = await import('./utils/bootstrap');
const { setOfflineModeEnvVars, normalizeEnvVars, pkgExec } = await import(
'./utils/bootstrap'
);
setOfflineModeEnvVars();
normalizeEnvVars();
// The 'pkgExec' special/internal command provides a Node.js interpreter

View File

@ -56,7 +56,7 @@ export async function login({ host = '127.0.0.1', port = 0 }) {
console.info(`Opening web browser for URL:\n${loginUrl}`);
const open = await import('open');
open(loginUrl, { wait: false });
await open(loginUrl, { wait: false });
const balena = getBalenaSdk();
const token = await loginServer.awaitForToken();

View File

@ -14,57 +14,44 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import * as _ from 'lodash';
import * as url from 'url';
import { getBalenaSdk } from '../utils/lazy';
/**
* @summary Get dashboard CLI login URL
* @function
* @protected
* Get dashboard CLI login URL
*
* @param {String} callbackUrl - callback url
* @fulfil {String} - dashboard login url
* @returns {Promise}
*
* @example
* utils.getDashboardLoginURL('http://127.0.0.1:3000').then (url) ->
* console.log(url)
* @param callbackUrl - Callback url, e.g. 'http://127.0.0.1:3000'
* @returns Dashboard login URL, e.g.:
* 'https://dashboard.balena-cloud.com/login/cli/http%253A%252F%252F127.0.0.1%253A59581%252Fauth'
*/
export const getDashboardLoginURL = (callbackUrl: string) => {
export async function getDashboardLoginURL(
callbackUrl: string,
): Promise<string> {
// Encode percentages signs from the escaped url
// characters to avoid angular getting confused.
callbackUrl = encodeURIComponent(callbackUrl).replace(/%/g, '%25');
return getBalenaSdk()
.settings.get('dashboardUrl')
.then((dashboardUrl) =>
url.resolve(dashboardUrl, `/login/cli/${callbackUrl}`),
);
};
const [{ URL }, dashboardUrl] = await Promise.all([
import('url'),
getBalenaSdk().settings.get('dashboardUrl'),
]);
return new URL(`/login/cli/${callbackUrl}`, dashboardUrl).href;
}
/**
* @summary Log in using a token, but only if the token is valid
* @function
* @protected
* Log in using a token, but only if the token is valid.
*
* @description
* This function checks that the token is not only well-structured
* but that it also authenticates with the server successfully.
*
* If authenticated, the token is persisted, if not then the previous
* login state is restored.
*
* @param {String} token - session token or api key
* @fulfil {Boolean} - whether the login was successful or not
* @returns {Promise}
*
* utils.loginIfTokenValid('...').then (loggedIn) ->
* if loggedIn
* console.log('Token is valid!')
* @param token - session token or api key
* @returns whether the login was successful or not
*/
export const loginIfTokenValid = async (token: string): Promise<boolean> => {
if (_.isEmpty(token?.trim())) {
export async function loginIfTokenValid(token?: string): Promise<boolean> {
token = (token || '').trim();
if (!token) {
return false;
}
const balena = getBalenaSdk();
@ -86,4 +73,4 @@ export const loginIfTokenValid = async (token: string): Promise<boolean> => {
}
}
return isLoggedIn;
};
}

View File

@ -15,8 +15,13 @@
* limitations under the License.
*/
import Command from '@oclif/command';
import { InsufficientPrivilegesError } from './errors';
import { Command } from '@oclif/core';
import {
InsufficientPrivilegesError,
NotAvailableInOfflineModeError,
} from './errors';
import { stripIndent } from './utils/lazy';
import * as output from './framework/output';
export default abstract class BalenaCommand extends Command {
/**
@ -40,6 +45,13 @@ export default abstract class BalenaCommand extends Command {
*/
public static authenticated = false;
/**
* Require an internet connection to run.
* When set to true, command will exit with an error
* if user is running in offline mode (BALENARC_OFFLINE_MODE).
*/
public static offlineCompatible = false;
/**
* Accept piped input.
* When set to true, command will read from stdin during init
@ -97,6 +109,29 @@ export default abstract class BalenaCommand extends Command {
}
}
/**
* Throw NotAvailableInOfflineModeError if in offline mode.
*
* Called automatically if `onlineOnly=true`.
* Can be called explicitly by command implementation, if e.g.:
* - check should only be done conditionally
* - other code needs to execute before check
*
* Note, currently public to allow use outside of derived commands
* (as some command implementations require this. Can be made protected
* if this changes).
*
* @throws {NotAvailableInOfflineModeError}
*/
public static checkNotUsingOfflineMode() {
if (process.env.BALENARC_OFFLINE_MODE) {
throw new NotAvailableInOfflineModeError(stripIndent`
This command requires an internet connection, and cannot be used in offline mode.
To leave offline mode, unset the BALENARC_OFFLINE_MODE environment variable.
`);
}
}
/**
* Read stdin contents and make available to command.
*
@ -125,8 +160,15 @@ export default abstract class BalenaCommand extends Command {
await BalenaCommand.checkLoggedIn();
}
if (!ctr.offlineCompatible) {
BalenaCommand.checkNotUsingOfflineMode();
}
if (ctr.readStdin) {
await this.getStdin();
}
}
protected outputMessage = output.outputMessage;
protected outputData = output.outputData;
}

View File

@ -15,20 +15,12 @@
* limitations under the License.
*/
import { flags } from '@oclif/command';
import { Args } from '@oclif/core';
import Command from '../../command';
import { ExpectedError } from '../../errors';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
interface FlagsDef {
help: void;
}
interface ArgsDef {
name: string;
}
export default class GenerateCmd extends Command {
public static description = stripIndent`
Generate a new balenaCloud API key.
@ -41,24 +33,23 @@ export default class GenerateCmd extends Command {
`;
public static examples = ['$ balena api-key generate "Jenkins Key"'];
public static args = [
{
name: 'name',
public static args = {
name: Args.string({
description: 'the API key name',
required: true,
},
];
}),
};
public static usage = 'api-key generate <name>';
public static flags: flags.Input<FlagsDef> = {
public static flags = {
help: cf.help,
};
public static authenticated = true;
public async run() {
const { args: params } = this.parse<FlagsDef, ArgsDef>(GenerateCmd);
const { args: params } = await this.parse(GenerateCmd);
let key;
try {

View File

@ -0,0 +1,67 @@
/**
* @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 { Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
export default class RevokeCmd extends Command {
public static description = stripIndent`
Revoke balenaCloud API keys.
Revoke balenaCloud API keys with the given
comma-separated list of ids.
The given balenaCloud API keys will no longer be usable.
`;
public static examples = [
'$ balena api-key revoke 123',
'$ balena api-key revoke 123,124,456',
];
public static args = {
ids: Args.string({
description: 'the API key ids',
required: true,
}),
};
public static usage = 'api-key revoke <ids>';
public static flags = {
help: cf.help,
};
public static authenticated = true;
public async run() {
const { args: params } = await this.parse(RevokeCmd);
const apiKeyIds = params.ids.split(',');
if (apiKeyIds.filter((apiKeyId) => !apiKeyId.match(/^\d+$/)).length > 0) {
console.log('API key ids must be positive integers');
return;
}
await Promise.all(
apiKeyIds.map(
async (id) => await getBalenaSdk().models.apiKey.revoke(Number(id)),
),
);
console.log('Successfully revoked the given API keys');
}
}

View File

@ -0,0 +1,83 @@
/**
* @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 { Flags } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
export default class ApiKeysCmd extends Command {
public static description = stripIndent`
Print a list of balenaCloud API keys.
Print a list of balenaCloud API keys.
Print a list of balenaCloud API keys for the current user or for a specific fleet with the \`--fleet\` option.
`;
public static examples = ['$ balena api-keys'];
public static usage = 'api-keys';
public static flags = {
help: cf.help,
user: Flags.boolean({
char: 'u',
description: 'show API keys for your user',
}),
fleet: cf.fleet,
};
public static authenticated = true;
public async run() {
const { flags: options } = await this.parse(ApiKeysCmd);
const { getApplication } = await import('../../utils/sdk');
const actorId = options.fleet
? (
await getApplication(getBalenaSdk(), options.fleet, {
$select: 'actor',
})
).actor
: await getBalenaSdk().auth.getActorId();
const keys = await getBalenaSdk().pine.get({
resource: 'api_key',
options: {
$select: ['id', 'created_at', 'name', 'description', 'expiry_date'],
$filter: {
is_of__actor: actorId,
...(options.user
? {
name: {
$ne: null,
},
}
: {}),
},
$orderby: 'name asc',
},
});
const fields = ['id', 'name', 'created_at', 'description', 'expiry_date'];
const _ = await import('lodash');
console.log(
getVisuals().table.horizontal(
keys.map((key) => _.mapValues(key, (val) => val ?? 'N/a')),
fields,
),
);
}
}

View File

@ -15,38 +15,24 @@
* limitations under the License.
*/
import { flags } from '@oclif/command';
import type { Output as ParserOutput } from '@oclif/parser';
import type { Application } from 'balena-sdk';
import { Flags, Args } from '@oclif/core';
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';
import { stripIndent } from '../../utils/lazy';
interface FlagsDef {
organization?: string;
type?: string; // application device type
help: void;
}
interface ArgsDef {
name: string;
}
export class FleetCreateCmd extends Command {
export default class AppCreateCmd extends Command {
public static description = stripIndent`
Create a fleet.
Create an app.
Create a new balena fleet.
Create a new balena app.
You can specify the organization the fleet should belong to using
You can specify the organization the app 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 app's default device type is specified with the \`--type\` option.
The \`balena devices supported\` command can be used to list the available
device types.
@ -58,124 +44,40 @@ export class FleetCreateCmd extends Command {
`;
public static examples = [
'$ balena fleet create MyFleet',
'$ balena fleet create MyFleet --organization mmyorg',
'$ balena fleet create MyFleet -o myorg --type raspberry-pi',
'$ balena app create MyApp',
'$ balena app create MyApp --organization mmyorg',
'$ balena app create MyApp -o myorg --type raspberry-pi',
];
public static args = [
{
name: 'name',
description: 'fleet name',
public static args = {
name: Args.string({
description: 'app 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({
};
public static usage = 'app create <name>';
public static flags = {
organization: Flags.string({
char: 'o',
description: 'handle of the organization the app should belong to',
}),
type: Flags.string({
char: 't',
description:
'fleet device type (Check available types with `balena devices supported`)',
'app 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);
const { args: params, flags: options } = await this.parse(AppCreateCmd);
await (
await import('../../utils/application-create')
).applicationCreateBase('app', options, params);
}
}

View File

@ -1,124 +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';
interface FlagsDef {
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,
};
public static authenticated = true;
public static primary = true;
public async run(parserOutput?: ParserOutput<FlagsDef, ArgsDef>) {
const { args: params } =
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;
// 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);
}
}

View File

@ -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);
}
}

View File

@ -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 ${params.fleet} 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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -1,134 +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';
interface ExtendedApplication extends ApplicationWithDeviceType {
device_count?: number;
online_devices?: number;
}
interface FlagsDef {
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> = {
help: cf.help,
verbose: flags.boolean({
default: false,
char: 'v',
description: 'No-op since release v12.0.0',
}),
};
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[];
const _ = await import('lodash');
// Add extended properties
applications.forEach((application) => {
application.device_count = application.owns__device?.length ?? 0;
application.online_devices = _.sumBy(application.owns__device, (d) =>
d.is_online === true ? 1 : 0,
);
// @ts-expect-error
application.device_type = application.is_for__device_type[0].slug;
});
// Display
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);
}
}

View File

@ -0,0 +1,83 @@
/**
* @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, Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { stripIndent } from '../../utils/lazy';
export default class BlockCreateCmd extends Command {
public static description = stripIndent`
Create an block.
Create a new balena block.
You can specify the organization the block 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 block'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 block create MyBlock',
'$ balena block create MyBlock --organization mmyorg',
'$ balena block create MyBlock -o myorg --type raspberry-pi',
];
public static args = {
name: Args.string({
description: 'block name',
required: true,
}),
};
public static usage = 'block create <name>';
public static flags = {
organization: Flags.string({
char: 'o',
description: 'handle of the organization the block should belong to',
}),
type: Flags.string({
char: 't',
description:
'block device type (Check available types with `balena devices supported`)',
}),
help: cf.help,
};
public static authenticated = true;
public async run() {
const { args: params, flags: options } = await this.parse(BlockCreateCmd);
await (
await import('../../utils/application-create')
).applicationCreateBase('block', options, params);
}
}

View File

@ -15,38 +15,39 @@
* limitations under the License.
*/
import { flags } from '@oclif/command';
import Command from '../command';
import { getBalenaSdk } from '../utils/lazy';
import * as cf from '../utils/common-flags';
import * as compose from '../utils/compose';
import type { Application, ApplicationType, BalenaSDK } from 'balena-sdk';
import { Args, Flags } from '@oclif/core';
import Command from '../../command';
import { getBalenaSdk } from '../../utils/lazy';
import * as cf from '../../utils/common-flags';
import * as compose from '../../utils/compose';
import type {
ApplicationType,
BalenaSDK,
DeviceType,
PineOptions,
PineTypedResult,
} from 'balena-sdk';
import {
appToFleetFlagMsg,
buildArgDeprecation,
dockerignoreHelp,
registrySecretsHelp,
warnify,
} from '../utils/messages';
import type { ComposeCliFlags, ComposeOpts } from '../utils/compose-types';
import { buildProject, composeCliFlags } from '../utils/compose_ts';
import type { BuildOpts, DockerCliFlags } from '../utils/docker';
import { dockerCliFlags } from '../utils/docker';
import { isV13 } from '../utils/version';
} from '../../utils/messages';
import type { ComposeCliFlags, ComposeOpts } from '../../utils/compose-types';
import { buildProject, composeCliFlags } from '../../utils/compose_ts';
import type { BuildOpts, DockerCliFlags } from '../../utils/docker';
import { dockerCliFlags } from '../../utils/docker';
// TODO: For this special one we can't use Interfaces.InferredFlags/InferredArgs
// because of the 'registry-secrets' type which is defined in the actual code
// as a path (string | undefined) but then the cli turns it into an object
interface FlagsDef extends ComposeCliFlags, DockerCliFlags {
arch?: string;
deviceType?: string;
application?: string;
fleet?: string;
source?: string; // Not part of command profile - source param copied here.
help: void;
}
interface ArgsDef {
source?: string;
}
export default class BuildCmd extends Command {
public static description = `\
Build a project locally.
@ -72,52 +73,42 @@ ${dockerignoreHelp}
public static examples = [
'$ balena build --fleet myFleet',
'$ balena build ./source/ --fleet myorg/myfleet',
'$ balena build --deviceType raspberrypi3 --emulated',
'$ balena build --deviceType raspberrypi3 --arch armv7hf --emulated',
'$ balena build --docker /var/run/docker.sock --fleet myFleet # Linux, Mac',
'$ balena build --docker //./pipe/docker_engine --fleet myFleet # Windows',
'$ balena build --dockerHost my.docker.host --dockerPort 2376 --ca ca.pem --key key.pem --cert cert.pem -f myFleet',
];
public static args = [
{
name: 'source',
description: 'path of project source directory',
},
];
public static args = {
source: Args.string({ description: 'path of project source directory' }),
};
public static usage = 'build [source]';
public static flags: flags.Input<FlagsDef> = {
arch: flags.string({
public static flags = {
arch: Flags.string({
description: 'the architecture to build for',
char: 'A',
}),
deviceType: flags.string({
deviceType: Flags.string({
description: 'the type of device this build is for',
char: 'd',
}),
...(isV13() ? {} : { application: cf.application }),
fleet: cf.fleet,
...composeCliFlags,
...dockerCliFlags,
// NOTE: Not supporting -h for help, because of clash with -h in DockerCliFlags
// Revisit this in future release.
help: flags.help({}),
help: Flags.help({}),
};
public static primary = true;
public async run() {
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
BuildCmd,
);
const { args: params, flags: options } = await this.parse(BuildCmd);
if (options.application && process.stderr.isTTY) {
console.error(warnify(appToFleetFlagMsg));
}
options.application ||= options.fleet;
await Command.checkLoggedInIf(!!options.application);
await Command.checkLoggedInIf(!!options.fleet);
(await import('events')).defaultMaxListeners = 1000;
@ -130,6 +121,8 @@ ${dockerignoreHelp}
options.source = params.source;
delete params.source;
await this.resolveArchFromDeviceType(sdk, options);
await this.validateOptions(options, sdk);
// Build args are under consideration for removal - warn user
@ -143,7 +136,7 @@ ${dockerignoreHelp}
try {
await this.buildProject(docker, logger, composeOpts, {
app,
appType: app?.application_type?.[0],
arch: options.arch!,
deviceType: options.deviceType!,
buildEmulated: options.emulated,
@ -161,19 +154,17 @@ ${dockerignoreHelp}
protected async validateOptions(opts: FlagsDef, sdk: BalenaSDK) {
// Validate option combinations
if (
(opts.application == null &&
(opts.arch == null || opts.deviceType == null)) ||
(opts.application != null &&
(opts.arch != null || opts.deviceType != null))
(opts.fleet == null && (opts.arch == null || opts.deviceType == null)) ||
(opts.fleet != null && (opts.arch != null || opts.deviceType != null))
) {
const { ExpectedError } = await import('../errors');
const { ExpectedError } = await import('../../errors');
throw new ExpectedError(
'You must specify either a fleet (-f), or the device type (-d) and architecture (-A)',
'You must specify either a fleet (-f), or the device type (-d) and optionally the architecture (-A)',
);
}
// Validate project directory
const { validateProjectDirectory } = await import('../utils/compose_ts');
const { validateProjectDirectory } = await import('../../utils/compose_ts');
const { dockerfilePath, registrySecrets } = await validateProjectDirectory(
sdk,
{
@ -188,10 +179,43 @@ ${dockerignoreHelp}
opts['registry-secrets'] = registrySecrets;
}
protected async resolveArchFromDeviceType(sdk: BalenaSDK, opts: FlagsDef) {
if (opts.deviceType != null && opts.arch == null) {
try {
const deviceTypeOpts = {
$select: 'is_of__cpu_architecture',
$expand: {
is_of__cpu_architecture: {
$select: 'slug',
},
},
} satisfies PineOptions<DeviceType>;
opts.arch = (
(await sdk.models.deviceType.get(
opts.deviceType,
deviceTypeOpts,
)) as PineTypedResult<DeviceType, typeof deviceTypeOpts>
).is_of__cpu_architecture[0].slug;
} catch (err) {
const { ExpectedError } = await import('../../errors');
if (err instanceof sdk.errors.BalenaInvalidDeviceType) {
let message = err.message;
if (!(await sdk.auth.isLoggedIn())) {
message = `${message}. In case you are trying to use a private device type, please try to log in first.`;
}
throw new ExpectedError(message);
}
throw new ExpectedError(
'Failed to resolve the architecture of the provided device type. If you are in an air-gapped environment please also define the architecture (-A) parameter.',
);
}
}
}
protected async getAppAndResolveArch(opts: FlagsDef) {
if (opts.application) {
const { getAppWithArch } = await import('../utils/helpers');
const app = await getAppWithArch(opts.application);
if (opts.fleet) {
const { getAppWithArch } = await import('../../utils/helpers');
const app = await getAppWithArch(opts.fleet);
opts.arch = app.arch;
opts.deviceType = app.is_for__device_type[0].slug;
return app;
@ -199,7 +223,7 @@ ${dockerignoreHelp}
}
protected async prepareBuild(options: FlagsDef) {
const { getDocker, generateBuildOpts } = await import('../utils/docker');
const { getDocker, generateBuildOpts } = await import('../../utils/docker');
const [docker, buildOpts, composeOpts] = await Promise.all([
getDocker(options),
generateBuildOpts(options),
@ -220,24 +244,24 @@ ${dockerignoreHelp}
* buildEmulated
* buildOpts: arguments to forward to docker build command
*
* @param {DockerToolbelt} docker
* @param {Dockerode} docker
* @param {Logger} logger
* @param {ComposeOpts} composeOpts
* @param opts
*/
protected async buildProject(
docker: import('dockerode'),
logger: import('../utils/logger'),
logger: import('../../utils/logger'),
composeOpts: ComposeOpts,
opts: {
app?: Application;
appType?: Pick<ApplicationType, 'supports_multicontainer'>;
arch: string;
deviceType: string;
buildEmulated: boolean;
buildOpts: BuildOpts;
},
) {
const { loadProject } = await import('../utils/compose_ts');
const { loadProject } = await import('../../utils/compose_ts');
const project = await loadProject(
logger,
@ -246,11 +270,10 @@ ${dockerignoreHelp}
opts.buildOpts.t,
);
const appType = (opts.app?.application_type as ApplicationType[])?.[0];
if (
appType != null &&
opts.appType != null &&
project.descriptors.length > 1 &&
!appType.supports_multicontainer
!opts.appType.supports_multicontainer
) {
logger.logWarn(
'Target fleet does not support multiple containers.\n' +
@ -271,7 +294,6 @@ ${dockerignoreHelp}
inlineLogs: composeOpts.inlineLogs,
convertEol: composeOpts.convertEol,
dockerfilePath: composeOpts.dockerfilePath,
nogitignore: composeOpts.nogitignore, // v13: delete this line
multiDockerignore: composeOpts.multiDockerignore,
});
}

View File

@ -15,35 +15,17 @@
* limitations under the License.
*/
import { flags } from '@oclif/command';
import { Flags } from '@oclif/core';
import type { Interfaces } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, getCliForm, stripIndent } from '../../utils/lazy';
import {
applicationIdInfo,
appToFleetFlagMsg,
warnify,
devModeInfo,
secureBootInfo,
} from '../../utils/messages';
import { isV13 } from '../../utils/version';
import type { PineDeferred } from 'balena-sdk';
interface FlagsDef {
version: string; // OS version
application?: string;
app?: string; // application alias
fleet?: string;
device?: string;
deviceApiKey?: string;
deviceType?: string;
'generate-device-api-key': boolean;
output?: string;
// Options for non-interactive configuration
network?: string;
wifiSsid?: string;
wifiKey?: string;
appUpdatePollInterval?: string;
help: void;
}
import type { BalenaSDK, PineDeferred } from 'balena-sdk';
export default class ConfigGenerateCmd extends Command {
public static description = stripIndent`
@ -53,6 +35,10 @@ export default class ConfigGenerateCmd extends Command {
The target balenaOS version must be specified with the --version option.
${devModeInfo.split('\n').join('\n\t\t')}
${secureBootInfo.split('\n').join('\n\t\t')}
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.
@ -65,94 +51,106 @@ export default class ConfigGenerateCmd extends Command {
public static examples = [
'$ balena config generate --device 7cf02a6 --version 2.12.7',
'$ balena config generate --device 7cf02a6 --version 2.12.7 --generate-device-api-key',
'$ balena config generate --device 7cf02a6 --version 2.12.7 --device-api-key <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 --fleet MyFleet --version 2.12.7',
'$ balena config generate --fleet myorg/myfleet --version 2.12.7',
'$ balena config generate --fleet MyFleet --version 2.12.7 --deviceType fincm3',
'$ balena config generate --fleet MyFleet --version 2.12.7 --output config.json',
'$ balena config generate --fleet MyFleet --version 2.12.7 --network wifi --wifiSsid mySsid --wifiKey abcdefgh --appUpdatePollInterval 15',
'$ balena config generate --fleet myorg/fleet --version 2.12.7 --dev',
'$ balena config generate --fleet myorg/fleet --version 2.12.7 --secureBoot',
'$ balena config generate --fleet myorg/fleet --version 2.12.7 --deviceType fincm3',
'$ balena config generate --fleet myorg/fleet --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',
];
public static usage = 'config generate';
public static flags: flags.Input<FlagsDef> = {
version: flags.string({
public static flags = {
version: Flags.string({
description: 'a balenaOS version',
required: true,
}),
...(isV13()
? {}
: {
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: { ...cf.device, exclusive: ['application', 'app', 'fleet'] },
deviceApiKey: flags.string({
fleet: { ...cf.fleet, exclusive: ['device'] },
dev: cf.dev,
secureBoot: cf.secureBoot,
device: {
...cf.device,
exclusive: [
'fleet',
'provisioning-key-name',
'provisioning-key-expiry-date',
],
},
deviceApiKey: Flags.string({
description:
'custom device key - note that this is only supported on balenaOS 2.0.3+',
char: 'k',
}),
deviceType: flags.string({
deviceType: Flags.string({
description:
"device type slug (run 'balena devices supported' for possible values)",
}),
'generate-device-api-key': flags.boolean({
'generate-device-api-key': Flags.boolean({
description: 'generate a fresh device key for the device',
}),
output: flags.string({
output: Flags.string({
description: 'path of output file',
char: 'o',
}),
// Options for non-interactive configuration
network: flags.string({
network: Flags.string({
description: 'the network type to use: ethernet or wifi',
options: ['ethernet', 'wifi'],
}),
wifiSsid: flags.string({
wifiSsid: Flags.string({
description:
'the wifi ssid to use (used only if --network is set to wifi)',
}),
wifiKey: flags.string({
wifiKey: Flags.string({
description:
'the wifi key to use (used only if --network is set to wifi)',
}),
appUpdatePollInterval: flags.string({
appUpdatePollInterval: Flags.string({
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({
description: 'custom key name assigned to generated provisioning api key',
exclusive: ['device'],
}),
'provisioning-key-expiry-date': Flags.string({
description:
'expiry date assigned to generated provisioning api key (format: YYYY-MM-DD)',
exclusive: ['device'],
}),
help: cf.help,
};
public static authenticated = true;
public async run() {
const { flags: options } = this.parse<FlagsDef, {}>(ConfigGenerateCmd);
public async getApplication(balena: BalenaSDK, fleet: string) {
const { getApplication } = await import('../../utils/sdk');
return await getApplication(balena, fleet, {
$select: 'slug',
$expand: {
is_for__device_type: { $select: 'slug' },
},
});
}
public async run() {
const { flags: options } = await this.parse(ConfigGenerateCmd);
const balena = getBalenaSdk();
await this.validateOptions(options);
let resourceDeviceType: string;
let application: ApplicationWithDeviceType | null = null;
let application: Awaited<ReturnType<typeof this.getApplication>> | null =
null;
let device:
| (DeviceWithDeviceType & { belongs_to__application: PineDeferred })
| null = null;
if (options.device != null) {
const { tryAsInteger } = await import('../../utils/validation');
const rawDevice = await balena.models.device.get(
tryAsInteger(options.device),
{ $expand: { is_of__device_type: { $select: 'slug' } } },
);
const rawDevice = await balena.models.device.get(options.device, {
$expand: { is_of__device_type: { $select: 'slug' } },
});
if (!rawDevice.belongs_to__application) {
const { ExpectedError } = await import('../../errors');
throw new ExpectedError(stripIndent`
@ -165,43 +163,51 @@ export default class ConfigGenerateCmd extends Command {
resourceDeviceType = device.is_of__device_type[0].slug;
} else {
// Disambiguate application (if is a number, it could either be an ID or a numerical name)
application = (await getApplication(balena, options.application!, {
$expand: {
is_for__device_type: { $select: 'slug' },
},
})) as ApplicationWithDeviceType;
application = await this.getApplication(balena, options.fleet!);
resourceDeviceType = application.is_for__device_type[0].slug;
}
const deviceType = options.deviceType || resourceDeviceType;
const deviceManifest = await balena.models.device.getManifestBySlug(
deviceType,
);
// Check compatibility if application and deviceType provided
if (options.application && options.deviceType) {
const appDeviceManifest = await balena.models.device.getManifestBySlug(
resourceDeviceType,
);
if (options.fleet && options.deviceType) {
const helpers = await import('../../utils/helpers');
if (
!helpers.areDeviceTypesCompatible(appDeviceManifest, deviceManifest)
!(await helpers.areDeviceTypesCompatible(
resourceDeviceType,
deviceType,
))
) {
throw new balena.errors.BalenaInvalidDeviceType(
`Device type ${options.deviceType} is incompatible with fleet ${options.application}`,
const { ExpectedError } = await import('../../errors');
throw new ExpectedError(
`Device type ${options.deviceType} is incompatible with fleet ${options.fleet}`,
);
}
}
const deviceManifest =
await balena.models.config.getDeviceTypeManifestBySlug(deviceType);
const { validateSecureBootOptionAndWarn } = await import(
'../../utils/config'
);
await validateSecureBootOptionAndWarn(
options.secureBoot,
deviceType,
options.version,
);
// Prompt for values
// 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)
const answers = await getCliForm().run(deviceManifest.options, {
override: options,
override: { ...options, app: options.fleet, application: options.fleet },
});
answers.version = options.version;
answers.developmentMode = options.dev;
answers.secureBoot = options.secureBoot;
answers.provisioningKeyName = options['provisioning-key-name'];
answers.provisioningKeyExpiryDate = options['provisioning-key-expiry-date'];
// Generate config
const { generateDeviceConfig, generateApplicationConfig } = await import(
@ -241,22 +247,21 @@ export default class ConfigGenerateCmd extends Command {
protected readonly deviceTypeNotAllowedMessage =
'The --deviceType option can only be used alongside the --fleet option';
protected async validateOptions(options: FlagsDef) {
protected async validateOptions(
options: Interfaces.InferredFlags<typeof ConfigGenerateCmd.flags>,
) {
const { ExpectedError } = await import('../../errors');
if ((options.application || options.app) && process.stderr.isTTY) {
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) {
if (options.device == null && options.fleet == null) {
throw new ExpectedError(this.missingDeviceOrAppMessage);
}
if (!options.application && options.deviceType) {
if (!options.fleet && options.deviceType) {
throw new ExpectedError(this.deviceTypeNotAllowedMessage);
}
const { normalizeOsVersion } = await import('../../utils/normalization');
options.version = normalizeOsVersion(options.version);
const { validateDevOptionAndWarn } = await import('../../utils/config');
await validateDevOptionAndWarn(options.dev, options.version);
}
}

View File

@ -15,59 +15,46 @@
* limitations under the License.
*/
import { flags } from '@oclif/command';
import { Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getVisuals, stripIndent } from '../../utils/lazy';
interface FlagsDef {
type: string;
drive?: string;
help: void;
}
interface ArgsDef {
file: string;
}
export default class ConfigInjectCmd extends Command {
public static description = stripIndent`
Inject a configuration file into a device or OS image.
Inject a config.json file to a balenaOS image or attached media.
Inject a config.json file to a mounted filesystem, e.g. the SD card of a
provisioned device or balenaOS image.
Inject a 'config.json' file to a balenaOS image file or attached SD card or
USB stick.
Note: if using a private/custom device type, please ensure you are logged in
('balena login' command). Public device types do not require logging in.
Documentation for the balenaOS 'config.json' file can be found at:
https://www.balena.io/docs/reference/OS/configuration/
`;
public static examples = [
'$ balena config inject my/config.json --type raspberrypi3',
'$ balena config inject my/config.json --type raspberrypi3 --drive /dev/disk2',
'$ balena config inject my/config.json',
'$ balena config inject my/config.json --drive /dev/disk2',
];
public static args = [
{
name: 'file',
public static args = {
file: Args.string({
description: 'the path to the config.json file to inject',
required: true,
},
];
}),
};
public static usage = 'config inject <file>';
public static flags: flags.Input<FlagsDef> = {
type: cf.deviceType,
public static flags = {
drive: cf.driveOrImg,
help: cf.help,
};
public static root = true;
public static offlineCompatible = true;
public async run() {
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
ConfigInjectCmd,
);
const { args: params, flags: options } = await this.parse(ConfigInjectCmd);
const { safeUmount } = await import('../../utils/umount');
@ -81,7 +68,7 @@ export default class ConfigInjectCmd extends Command {
);
const config = await import('balena-config-json');
await config.write(drive, options.type, configJSON);
await config.write(drive, '', configJSON);
console.info('Done');
}

View File

@ -15,44 +15,40 @@
* limitations under the License.
*/
import { flags } from '@oclif/command';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getVisuals, stripIndent } from '../../utils/lazy';
interface FlagsDef {
type: string;
drive?: string;
help: void;
}
export default class ConfigReadCmd extends Command {
public static description = stripIndent`
Read the configuration of a device or OS image.
Read the config.json file of a balenaOS image or attached media.
Read the config.json file from the mounted filesystem,
e.g. the SD card of a provisioned device or balenaOS image.
Read the 'config.json' file of a balenaOS image file or attached SD card or
USB stick.
Documentation for the balenaOS 'config.json' file can be found at:
https://www.balena.io/docs/reference/OS/configuration/
`;
public static examples = [
'$ balena config read --type raspberrypi3',
'$ balena config read --type raspberrypi3 --drive /dev/disk2',
'$ balena config read',
'$ balena config read --drive /dev/disk2',
'$ balena config read --drive balena.img',
];
public static usage = 'config read';
public static flags: flags.Input<FlagsDef> = {
type: cf.deviceType,
public static flags = {
drive: cf.driveOrImg,
help: cf.help,
json: cf.json,
};
public static authenticated = true;
public static root = true;
public static offlineCompatible = true;
public async run() {
const { flags: options } = this.parse<FlagsDef, {}>(ConfigReadCmd);
const { flags: options } = await this.parse(ConfigReadCmd);
const { safeUmount } = await import('../../utils/umount');
@ -61,9 +57,13 @@ export default class ConfigReadCmd extends Command {
await safeUmount(drive);
const config = await import('balena-config-json');
const configJSON = await config.read(drive, options.type);
const configJSON = await config.read(drive, '');
const prettyjson = await import('prettyjson');
console.info(prettyjson.render(configJSON));
if (options.json) {
console.log(JSON.stringify(configJSON, null, 4));
} else {
const prettyjson = await import('prettyjson');
console.log(prettyjson.render(configJSON));
}
}
}

View File

@ -15,48 +15,49 @@
* limitations under the License.
*/
import { flags } from '@oclif/command';
import { Flags } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getVisuals, stripIndent } from '../../utils/lazy';
interface FlagsDef {
type: string;
drive?: string;
advanced: boolean;
help: void;
}
export default class ConfigReconfigureCmd extends Command {
public static description = stripIndent`
Interactively reconfigure a device or OS image.
Interactively reconfigure a balenaOS image file or attached media.
Interactively reconfigure a provisioned device or OS image.
Interactively reconfigure a balenaOS image file or attached media.
This command extracts the device UUID from the 'config.json' file of the
chosen balenaOS image file or attached media, and then passes the UUID as
the '--device' argument to the 'balena os configure' command.
For finer-grained or scripted control of the operation, use the
'balena config read' and 'balena os configure' commands separately.
`;
public static examples = [
'$ balena config reconfigure --type raspberrypi3',
'$ balena config reconfigure --type raspberrypi3 --advanced',
'$ balena config reconfigure --type raspberrypi3 --drive /dev/disk2',
'$ balena config reconfigure',
'$ balena config reconfigure --drive /dev/disk3',
'$ balena config reconfigure --drive balena.img --advanced',
];
public static usage = 'config reconfigure';
public static flags: flags.Input<FlagsDef> = {
type: cf.deviceType,
public static flags = {
drive: cf.driveOrImg,
advanced: flags.boolean({
advanced: Flags.boolean({
description: 'show advanced commands',
char: 'v',
}),
help: cf.help,
version: Flags.string({
description: 'balenaOS version, for example "2.32.0" or "2.44.0+rev1"',
}),
};
public static authenticated = true;
public static root = true;
public async run() {
const { flags: options } = this.parse<FlagsDef, {}>(ConfigReconfigureCmd);
const { flags: options } = await this.parse(ConfigReconfigureCmd);
const { safeUmount } = await import('../../utils/umount');
@ -65,10 +66,20 @@ export default class ConfigReconfigureCmd extends Command {
await safeUmount(drive);
const config = await import('balena-config-json');
const { uuid } = await config.read(drive, options.type);
const { uuid } = await config.read(drive, '');
await safeUmount(drive);
if (!uuid) {
const { ExpectedError } = await import('../../errors');
throw new ExpectedError(
`Error: UUID not found in 'config.json' file for '${drive}'`,
);
}
const configureCommand = ['os', 'configure', drive, '--device', uuid];
if (options.version) {
configureCommand.push('--version', options.version);
}
if (options.advanced) {
configureCommand.push('--advanced');
}

View File

@ -15,65 +15,51 @@
* limitations under the License.
*/
import { flags } from '@oclif/command';
import { Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getVisuals, stripIndent } from '../../utils/lazy';
interface FlagsDef {
type: string;
drive?: string;
help: void;
}
interface ArgsDef {
key: string;
value: string;
}
export default class ConfigWriteCmd extends Command {
public static description = stripIndent`
Write a key-value pair to configuration of a device or OS image.
Write a key-value pair to the config.json file of an OS image or attached media.
Write a key-value pair to the config.json file on the mounted filesystem,
e.g. the SD card of a provisioned device or balenaOS image.
Write a key-value pair to the 'config.json' file of a balenaOS image file or
attached SD card or USB stick.
Documentation for the balenaOS 'config.json' file can be found at:
https://www.balena.io/docs/reference/OS/configuration/
`;
public static examples = [
'$ balena config write --type raspberrypi3 username johndoe',
'$ balena config write --type raspberrypi3 --drive /dev/disk2 username johndoe',
'$ balena config write --type raspberrypi3 files.network/settings "..."',
'$ balena config write ntpServers "0.resinio.pool.ntp.org 1.resinio.pool.ntp.org"',
'$ balena config write --drive /dev/disk2 hostname custom-hostname',
'$ balena config write --drive balena.img os.network.connectivity.interval 300',
];
public static args = [
{
name: 'key',
public static args = {
key: Args.string({
description: 'the key of the config parameter to write',
required: true,
},
{
name: 'value',
}),
value: Args.string({
description: 'the value of the config parameter to write',
required: true,
},
];
}),
};
public static usage = 'config write <key> <value>';
public static flags: flags.Input<FlagsDef> = {
type: cf.deviceType,
public static flags = {
drive: cf.driveOrImg,
help: cf.help,
};
public static authenticated = true;
public static root = true;
public static offlineCompatible = true;
public async run() {
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
ConfigWriteCmd,
);
const { args: params, flags: options } = await this.parse(ConfigWriteCmd);
const { denyMount, safeUmount } = await import('../../utils/umount');
@ -82,14 +68,14 @@ export default class ConfigWriteCmd extends Command {
await safeUmount(drive);
const config = await import('balena-config-json');
const configJSON = await config.read(drive, options.type);
const configJSON = await config.read(drive, '');
console.info(`Setting ${params.key} to ${params.value}`);
ConfigWriteCmd.updateConfigJson(configJSON, params.key, params.value);
await denyMount(drive, async () => {
await safeUmount(drive);
await config.write(drive, options.type, configJSON);
await config.write(drive, '', configJSON);
});
console.info('Done');

View File

@ -15,59 +15,56 @@
* limitations under the License.
*/
import { flags } from '@oclif/command';
import type { ImageDescriptor } from 'resin-compose-parse';
import { Args, Flags } from '@oclif/core';
import type { ImageDescriptor } from '@balena/compose/dist/parse';
import Command from '../command';
import { ExpectedError } from '../errors';
import { getBalenaSdk, getChalk, stripIndent } from '../utils/lazy';
import Command from '../../command';
import { ExpectedError } from '../../errors';
import { getBalenaSdk, getChalk, stripIndent } from '../../utils/lazy';
import {
dockerignoreHelp,
registrySecretsHelp,
buildArgDeprecation,
} from '../utils/messages';
import * as ca from '../utils/common-args';
import * as compose from '../utils/compose';
} from '../../utils/messages';
import * as ca from '../../utils/common-args';
import * as compose from '../../utils/compose';
import type {
BuiltImage,
ComposeCliFlags,
ComposeOpts,
Release as ComposeReleaseInfo,
} from '../utils/compose-types';
import type { BuildOpts, DockerCliFlags } from '../utils/docker';
} from '../../utils/compose-types';
import type { BuildOpts, DockerCliFlags } from '../../utils/docker';
import {
applyReleaseTagKeysAndValues,
buildProject,
composeCliFlags,
isBuildConfig,
parseReleaseTagKeysAndValues,
} from '../utils/compose_ts';
import { dockerCliFlags } from '../utils/docker';
import type {
Application,
ApplicationType,
DeviceType,
Release,
} from 'balena-sdk';
} from '../../utils/compose_ts';
import { dockerCliFlags } from '../../utils/docker';
import type { ApplicationType, DeviceType, Release } from 'balena-sdk';
interface ApplicationWithArch extends Application {
interface ApplicationWithArch {
id: number;
arch: string;
is_for__device_type: [Pick<DeviceType, 'slug'>];
application_type: [Pick<ApplicationType, 'slug' | 'supports_multicontainer'>];
}
// TODO: For this special one we can't use Interfaces.InferredFlags/InferredArgs
// because of the 'registry-secrets' type which is defined in the actual code
// as a path (string | undefined) but then the cli turns it into an object
interface FlagsDef extends ComposeCliFlags, DockerCliFlags {
source?: string;
build: boolean;
nologupload: boolean;
'release-tag'?: string[];
draft: boolean;
note?: string;
help: void;
}
interface ArgsDef {
fleet: string;
image?: string;
}
export default class DeployCmd extends Command {
public static description = `\
Deploy a single image or a multicontainer project to a balena fleet.
@ -101,35 +98,33 @@ ${dockerignoreHelp}
public static examples = [
'$ balena deploy myFleet',
'$ balena deploy myorg/myfleet --build --source myBuildDir/',
'$ balena deploy myorg/myfleet --build --source myBuildDir/ --note "this is the note for this release"',
'$ balena deploy myorg/myfleet myRepo/myImage',
'$ balena deploy myFleet myRepo/myImage --release-tag key1 "" key2 "value2 with spaces"',
];
public static args = [
ca.fleetRequired,
{
name: 'image',
description: 'the image to deploy',
},
];
public static args = {
fleet: ca.fleetRequired,
image: Args.string({ description: 'the image to deploy' }),
};
public static usage = 'deploy <fleet> [image]';
public static flags: flags.Input<FlagsDef> = {
source: flags.string({
public static flags = {
source: Flags.string({
description:
'specify an alternate source directory; default is the working directory',
char: 's',
}),
build: flags.boolean({
build: Flags.boolean({
description: 'force a rebuild before deploy',
char: 'b',
}),
nologupload: flags.boolean({
nologupload: Flags.boolean({
description:
"don't upload build logs to the dashboard with image (if building)",
}),
'release-tag': flags.string({
'release-tag': Flags.string({
description: stripIndent`
Set release tags if the image deployment is successful. Multiple
arguments may be provided, alternating tag keys and values (see examples).
@ -137,7 +132,7 @@ ${dockerignoreHelp}
`,
multiple: true,
}),
draft: flags.boolean({
draft: Flags.boolean({
description: stripIndent`
Deploy the release as a draft. Draft releases are ignored
by the 'track latest' release policy but can be used through release pinning.
@ -145,11 +140,12 @@ ${dockerignoreHelp}
as final by default unless this option is given.`,
default: false,
}),
note: Flags.string({ description: 'The notes for this release' }),
...composeCliFlags,
...dockerCliFlags,
// NOTE: Not supporting -h for help, because of clash with -h in DockerCliFlags
// Revisit this in future release.
help: flags.help({}),
help: Flags.help({}),
};
public static authenticated = true;
@ -157,9 +153,7 @@ ${dockerignoreHelp}
public static primary = true;
public async run() {
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
DeployCmd,
);
const { args: params, flags: options } = await this.parse(DeployCmd);
(await import('events')).defaultMaxListeners = 1000;
@ -181,7 +175,7 @@ ${dockerignoreHelp}
const sdk = getBalenaSdk();
const { getRegistrySecrets, validateProjectDirectory } = await import(
'../utils/compose_ts'
'../../utils/compose_ts'
);
const { releaseTagKeys, releaseTagValues } = parseReleaseTagKeysAndValues(
@ -189,7 +183,7 @@ ${dockerignoreHelp}
);
if (image) {
options['registry-secrets'] = await getRegistrySecrets(
(options as FlagsDef)['registry-secrets'] = await getRegistrySecrets(
sdk,
options['registry-secrets'],
);
@ -202,16 +196,16 @@ ${dockerignoreHelp}
registrySecretsPath: options['registry-secrets'],
});
options.dockerfile = dockerfilePath;
options['registry-secrets'] = registrySecrets;
(options as FlagsDef)['registry-secrets'] = registrySecrets;
}
const helpers = await import('../utils/helpers');
const helpers = await import('../../utils/helpers');
const app = await helpers.getAppWithArch(fleet);
const dockerUtils = await import('../utils/docker');
const dockerUtils = await import('../../utils/docker');
const [docker, buildOpts, composeOpts] = await Promise.all([
dockerUtils.getDocker(options),
dockerUtils.generateBuildOpts(options),
dockerUtils.generateBuildOpts(options as FlagsDef),
compose.generateOpts(options),
]);
@ -231,11 +225,14 @@ ${dockerignoreHelp}
releaseTagKeys,
releaseTagValues,
);
if (options.note) {
await sdk.models.release.setNote(release.id, options.note);
}
}
async deployProject(
docker: import('dockerode'),
logger: import('../utils/logger'),
logger: import('../../utils/logger'),
composeOpts: ComposeOpts,
opts: {
app: ApplicationWithArch; // the application instance to deploy to
@ -253,10 +250,10 @@ ${dockerignoreHelp}
const doodles = await import('resin-doodles');
const sdk = getBalenaSdk();
const { deployProject: $deployProject, loadProject } = await import(
'../utils/compose_ts'
'../../utils/compose_ts'
);
const appType = (opts.app?.application_type as ApplicationType[])?.[0];
const appType = opts.app.application_type[0];
try {
const project = await loadProject(
@ -313,13 +310,12 @@ ${dockerignoreHelp}
projectName: project.name,
composition: compositionToBuild,
arch: opts.app.arch,
deviceType: (opts.app?.is_for__device_type as DeviceType[])?.[0].slug,
deviceType: opts.app.is_for__device_type[0].slug,
emulated: opts.buildEmulated,
buildOpts: opts.buildOpts,
inlineLogs: composeOpts.inlineLogs,
convertEol: composeOpts.convertEol,
dockerfilePath: composeOpts.dockerfilePath,
nogitignore: composeOpts.nogitignore, // v13: delete this line
multiDockerignore: composeOpts.multiDockerignore,
});
builtImagesByService = _.keyBy(builtImages, 'serviceName');
@ -335,17 +331,17 @@ ${dockerignoreHelp}
);
let release: Release | ComposeReleaseInfo['release'];
if (appType?.is_legacy) {
const { deployLegacy } = require('../utils/deploy-legacy');
if (appType.slug === 'legacy-v1' || appType.slug === 'legacy-v2') {
const { deployLegacy } = require('../../utils/deploy-legacy');
const msg = getChalk().yellow(
'Target fleet requires legacy deploy method.',
);
logger.logWarn(msg);
const [token, username, url, options] = await Promise.all([
const [token, { username }, url, options] = await Promise.all([
sdk.auth.getToken(),
sdk.auth.whoami(),
sdk.auth.getUserInfo(),
sdk.settings.get('balenaUrl'),
{
// opts.appName may be prefixed by 'owner/', unlike opts.app.app_name
@ -368,8 +364,8 @@ ${dockerignoreHelp}
$select: ['commit'],
});
} else {
const [userId, auth, apiEndpoint] = await Promise.all([
sdk.auth.getUserId(),
const [{ id: userId }, auth, apiEndpoint] = await Promise.all([
sdk.auth.getUserInfo(),
sdk.auth.getToken(),
sdk.settings.get('apiUrl'),
]);

View File

@ -15,21 +15,11 @@
* limitations under the License.
*/
import { flags } from '@oclif/command';
import type { IArg } from '@oclif/parser/lib/args';
import { Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
interface FlagsDef {
yes: boolean;
help: void;
}
interface ArgsDef {
uuid: string;
}
export default class DeviceDeactivateCmd extends Command {
public static description = stripIndent`
Deactivate a device.
@ -44,17 +34,16 @@ export default class DeviceDeactivateCmd extends Command {
'$ balena device deactivate 7cf02a6 --yes',
];
public static args: Array<IArg<any>> = [
{
name: 'uuid',
public static args = {
uuid: Args.string({
description: 'the UUID of the device to be deactivated',
required: true,
},
];
}),
};
public static usage = 'device deactivate <uuid>';
public static flags: flags.Input<FlagsDef> = {
public static flags = {
yes: cf.yes,
help: cf.help,
};
@ -62,9 +51,8 @@ export default class DeviceDeactivateCmd extends Command {
public static authenticated = true;
public async run() {
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
DeviceDeactivateCmd,
);
const { args: params, flags: options } =
await this.parse(DeviceDeactivateCmd);
const balena = getBalenaSdk();
const patterns = await import('../../utils/patterns');

View File

@ -15,22 +15,12 @@
* limitations under the License.
*/
import { flags } from '@oclif/command';
import type { IArg } from '@oclif/parser/lib/args';
import { Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import { tryAsInteger } from '../../utils/validation';
import { ExpectedError } from '../../errors';
interface FlagsDef {
help: void;
}
interface ArgsDef {
uuid: string;
}
export default class DeviceIdentifyCmd extends Command {
public static description = stripIndent`
Identify a device.
@ -39,25 +29,23 @@ export default class DeviceIdentifyCmd extends Command {
`;
public static examples = ['$ balena device identify 23c73a1'];
public static args: Array<IArg<any>> = [
{
name: 'uuid',
public static args = {
uuid: Args.string({
description: 'the uuid of the device to identify',
parse: (dev) => tryAsInteger(dev),
required: true,
},
];
}),
};
public static usage = 'device identify <uuid>';
public static flags: flags.Input<FlagsDef> = {
public static flags = {
help: cf.help,
};
public static authenticated = true;
public async run() {
const { args: params } = this.parse<FlagsDef, ArgsDef>(DeviceIdentifyCmd);
const { args: params } = await this.parse(DeviceIdentifyCmd);
const balena = getBalenaSdk();

View File

@ -15,21 +15,18 @@
* limitations under the License.
*/
import { flags } from '@oclif/command';
import { IArg } from '@oclif/parser/lib/args';
import { Flags, Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { expandForAppName } from '../../utils/helpers';
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
import { appToFleetOutputMsg, warnify } from '../../utils/messages';
import { tryAsInteger } from '../../utils/validation';
import { isV13 } from '../../utils/version';
import { jsonInfo } from '../../utils/messages';
import type { Application, Release } from 'balena-sdk';
interface ExtendedDevice extends DeviceWithDeviceType {
dashboard_url?: string;
application_name?: string;
fleet: string; // 'org/name' slug
device_type?: string;
commit?: string;
last_seen?: string;
@ -44,85 +41,101 @@ interface ExtendedDevice extends DeviceWithDeviceType {
undervoltage_detected?: boolean;
}
interface FlagsDef {
help: void;
v13: boolean;
}
interface ArgsDef {
uuid: string;
}
export default class DeviceCmd extends Command {
public static description = stripIndent`
Show info about a single device.
Show information about a single device.
`;
public static examples = ['$ balena device 7cf02a6'];
public static args: Array<IArg<any>> = [
{
name: 'uuid',
description: 'the device uuid',
parse: (dev) => tryAsInteger(dev),
required: true,
},
${jsonInfo.split('\n').join('\n\t\t')}
`;
public static examples = [
'$ balena device 7cf02a6',
'$ balena device 7cf02a6 --view',
'$ balena device 7cf02a6 --json',
];
public static args = {
uuid: Args.string({
description: 'the device uuid',
required: true,
}),
};
public static usage = 'device <uuid>';
public static flags: flags.Input<FlagsDef> = {
public static flags = {
json: cf.json,
help: cf.help,
v13: cf.v13,
view: Flags.boolean({
default: false,
description: 'open device dashboard page',
}),
};
public static authenticated = true;
public static primary = true;
public async run() {
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
DeviceCmd,
);
const useAppWord = !options.v13 && !isV13();
const { args: params, flags: options } = await this.parse(DeviceCmd);
const balena = getBalenaSdk();
const device = (await balena.models.device.get(params.uuid, {
$select: [
'device_name',
'id',
'overall_status',
'is_online',
'ip_address',
'mac_address',
'last_connectivity_event',
'uuid',
'supervisor_version',
'is_web_accessible',
'note',
'os_version',
'memory_usage',
'memory_total',
'public_address',
'storage_block_device',
'storage_usage',
'storage_total',
'cpu_usage',
'cpu_temp',
'cpu_id',
'is_undervolted',
],
...expandForAppName,
})) as ExtendedDevice;
const device = (await balena.models.device.get(
params.uuid,
options.json
? {
$expand: {
device_tag: {
$select: ['tag_key', 'value'],
},
...expandForAppName.$expand,
},
}
: {
$select: [
'device_name',
'id',
'overall_status',
'is_online',
'ip_address',
'mac_address',
'last_connectivity_event',
'uuid',
'supervisor_version',
'is_web_accessible',
'note',
'os_version',
'memory_usage',
'memory_total',
'public_address',
'storage_block_device',
'storage_usage',
'storage_total',
'cpu_usage',
'cpu_temp',
'cpu_id',
'is_undervolted',
],
...expandForAppName,
},
)) as ExtendedDevice;
if (options.view) {
const open = await import('open');
const dashboardUrl = balena.models.device.getDashboardUrl(device.uuid);
await open(dashboardUrl, { wait: false });
return;
}
device.status = device.overall_status;
device.dashboard_url = balena.models.device.getDashboardUrl(device.uuid);
const belongsToApplication =
device.belongs_to__application as Application[];
device.application_name = belongsToApplication?.[0]
? belongsToApplication[0].app_name
device.fleet = belongsToApplication?.[0]
? belongsToApplication[0].slug
: 'N/a';
device.device_type = device.is_of__device_type[0].slug;
@ -170,8 +183,9 @@ export default class DeviceCmd extends Command {
);
}
if (useAppWord && process.stderr.isTTY) {
console.error(warnify(appToFleetOutputMsg));
if (options.json) {
console.log(JSON.stringify(device, null, 4));
return;
}
console.log(
@ -184,7 +198,7 @@ export default class DeviceCmd extends Command {
'ip_address',
'public_address',
'mac_address',
useAppWord ? 'application_name' : 'application_name => FLEET',
'fleet',
'last_seen',
'uuid',
'commit',

View File

@ -15,21 +15,14 @@
* limitations under the License.
*/
import { flags } from '@oclif/command';
import { Flags } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import {
applicationIdInfo,
appToFleetFlagMsg,
warnify,
} from '../../utils/messages';
import { applicationIdInfo } from '../../utils/messages';
import { runCommand } from '../../utils/helpers';
import { isV13 } from '../../utils/version';
interface FlagsDef {
application?: string;
app?: string;
fleet?: string;
yes: boolean;
advanced: boolean;
@ -37,42 +30,60 @@ interface FlagsDef {
drive?: string;
config?: string;
help: void;
'provisioning-key-name'?: string;
'provisioning-key-expiry-date'?: string;
}
export default class DeviceInitCmd extends Command {
public static description = stripIndent`
Initialize a device with balenaOS.
Initialize a device by downloading the OS image of the specified fleet
and writing it to an SD Card.
Register a new device in the selected fleet, download the OS image for the
fleet's default device type, configure the image and write it to an SD card.
This command effectively combines several other balena CLI commands in one,
namely:
If the --fleet option is omitted, it will be prompted for interactively.
'balena device register'
'balena os download'
'balena os build-config' or 'balena config generate'
'balena os configure'
'balena os local flash'
Possible arguments for the '--fleet', '--os-version' and '--drive' options can
be listed respectively with the commands:
'balena fleets'
'balena os versions'
'balena util available-drives'
If the '--fleet' or '--drive' options are omitted, interactive menus will be
presented with values to choose from. If the '--os-version' option is omitted,
the latest released OS version for the fleet's default device type will be used.
${applicationIdInfo.split('\n').join('\n\t\t')}
Image configuration questions will be asked interactively unless a pre-configured
'config.json' file is provided with the '--config' option. The file can be
generated with the 'balena config generate' or 'balena os build-config' commands.
`;
public static examples = [
'$ balena device init',
'$ balena device init --fleet MyFleet',
'$ balena device init -f myorg/myfleet',
'$ balena device init --fleet myFleet --os-version 2.101.7 --drive /dev/disk5 --config config.json --yes',
'$ balena device init --fleet myFleet --os-version 2.83.21+rev1.prod --drive /dev/disk5 --config config.json --yes',
];
public static usage = 'device init';
public static flags: flags.Input<FlagsDef> = {
...(isV13()
? {}
: {
application: cf.application,
app: cf.app,
}),
public static flags = {
fleet: cf.fleet,
yes: cf.yes,
advanced: flags.boolean({
advanced: Flags.boolean({
char: 'v',
description: 'show advanced configuration options',
}),
'os-version': flags.string({
'os-version': Flags.string({
description: stripIndent`
exact version number, or a valid semver range,
or 'latest' (includes pre-releases),
@ -82,16 +93,23 @@ export default class DeviceInitCmd extends Command {
`,
}),
drive: cf.drive,
config: flags.string({
config: Flags.string({
description: 'path to the config JSON file, see `balena os build-config`',
}),
'provisioning-key-name': Flags.string({
description: 'custom key name assigned to generated provisioning api key',
}),
'provisioning-key-expiry-date': Flags.string({
description:
'expiry date assigned to generated provisioning api key (format: YYYY-MM-DD)',
}),
help: cf.help,
};
public static authenticated = true;
public async run() {
const { flags: options } = this.parse<FlagsDef, {}>(DeviceInitCmd);
const { flags: options } = await this.parse(DeviceInitCmd);
// Imports
const { promisify } = await import('util');
@ -105,32 +123,21 @@ export default class DeviceInitCmd extends Command {
const logger = await Command.getLogger();
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
const application = (await getApplication(
balena,
options['application'] ||
(
await (await import('../../utils/patterns')).selectApplication()
).id,
{
$expand: {
is_for__device_type: {
$select: 'slug',
const application = options.fleet
? await getApplication(balena, options.fleet, {
$select: ['id', 'slug'],
$expand: {
is_for__device_type: {
$select: 'slug',
},
},
},
},
)) as ApplicationWithDeviceType;
})
: await (await import('../../utils/patterns')).selectApplication();
// Register new device
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);
const device = await balena.models.device.get(deviceUuid);
@ -173,6 +180,21 @@ export default class DeviceInitCmd extends Command {
} else if (options.advanced) {
configureCommand.push('--advanced');
}
if (options['provisioning-key-name']) {
configureCommand.push(
'--provisioning-key-name',
options['provisioning-key-name'],
);
}
if (options['provisioning-key-expiry-date']) {
configureCommand.push(
'--provisioning-key-expiry-date',
options['provisioning-key-expiry-date'],
);
}
await runCommand(configureCommand);
}

View File

@ -15,23 +15,10 @@
* limitations under the License.
*/
import { flags } from '@oclif/command';
import type { IArg } from '@oclif/parser/lib/args';
import { Flags, Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import { tryAsInteger } from '../../utils/validation';
interface FlagsDef {
enable: boolean;
disable: boolean;
status: boolean;
help?: void;
}
interface ArgsDef {
uuid: string | number;
}
export default class DeviceLocalModeCmd extends Command {
public static description = stripIndent`
@ -48,27 +35,25 @@ export default class DeviceLocalModeCmd extends Command {
'$ balena device local-mode 23c73a1 --status',
];
public static args: Array<IArg<any>> = [
{
name: 'uuid',
public static args = {
uuid: Args.string({
description: 'the uuid of the device to manage',
parse: (dev) => tryAsInteger(dev),
required: true,
},
];
}),
};
public static usage = 'device local-mode <uuid>';
public static flags: flags.Input<FlagsDef> = {
enable: flags.boolean({
public static flags = {
enable: Flags.boolean({
description: 'enable local mode',
exclusive: ['disable', 'status'],
}),
disable: flags.boolean({
disable: Flags.boolean({
description: 'disable local mode',
exclusive: ['enable', 'status'],
}),
status: flags.boolean({
status: Flags.boolean({
description: 'output boolean indicating local mode status',
exclusive: ['enable', 'disable'],
}),
@ -78,9 +63,8 @@ export default class DeviceLocalModeCmd extends Command {
public static authenticated = true;
public async run() {
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
DeviceLocalModeCmd,
);
const { args: params, flags: options } =
await this.parse(DeviceLocalModeCmd);
const balena = getBalenaSdk();

View File

@ -15,42 +15,18 @@
* limitations under the License.
*/
import type { flags } from '@oclif/command';
import type { IArg } from '@oclif/parser/lib/args';
import { Args } from '@oclif/core';
import type {
BalenaSDK,
Device,
DeviceType,
PineOptions,
PineTypedResult,
} from 'balena-sdk';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { ExpectedError } from '../../errors';
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import {
applicationIdInfo,
appToFleetFlagMsg,
warnify,
} from '../../utils/messages';
import { isV13 } from '../../utils/version';
type ExtendedDevice = PineTypedResult<
Device,
typeof import('../../utils/helpers').expandForAppNameAndCpuArch
> & {
application_name?: string;
};
interface FlagsDef {
application?: string;
app?: string;
fleet?: string;
help: void;
}
interface ArgsDef {
uuid: string;
}
import { applicationIdInfo } from '../../utils/messages';
export default class DeviceMoveCmd extends Command {
public static description = stripIndent`
@ -70,74 +46,70 @@ export default class DeviceMoveCmd extends Command {
'$ balena device move 7cf02a6 -f myorg/mynewfleet',
];
public static args: Array<IArg<any>> = [
{
name: 'uuid',
public static args = {
uuid: Args.string({
description:
'comma-separated list (no blank spaces) of device UUIDs to be moved',
required: true,
},
];
}),
};
public static usage = 'device move <uuid(s)>';
public static flags: flags.Input<FlagsDef> = {
...(isV13() ? {} : { app: cf.app, application: cf.application }),
public static flags = {
fleet: cf.fleet,
help: cf.help,
};
public static authenticated = true;
public async run() {
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
DeviceMoveCmd,
);
private async getDevices(balena: BalenaSDK, deviceUuids: string[]) {
const deviceOptions = {
$select: 'belongs_to__application',
$expand: {
is_of__device_type: {
$select: 'is_of__cpu_architecture',
$expand: {
is_of__cpu_architecture: {
$select: 'slug',
},
},
},
},
} satisfies PineOptions<Device>;
if ((options.application || options.app) && process.stderr.isTTY) {
console.error(warnify(appToFleetFlagMsg));
}
options.application ||= options.app || options.fleet;
// TODO: Refacor once `device.get()` accepts an array of uuids`
const devices = await Promise.all(
deviceUuids.map(
(uuid) =>
balena.models.device.get(uuid, deviceOptions) as Promise<
PineTypedResult<Device, typeof deviceOptions>
>,
),
);
return devices;
}
public async run() {
const { args: params, flags: options } = await this.parse(DeviceMoveCmd);
const balena = getBalenaSdk();
const { tryAsInteger } = await import('../../utils/validation');
const { expandForAppNameAndCpuArch } = await import('../../utils/helpers');
// Split uuids string into array of uuids
const deviceUuids = params.uuid.split(',');
// Parse ids string into array of correct types
const deviceIds: Array<string | number> = params.uuid
.split(',')
.map((id) => tryAsInteger(id));
const devices = await this.getDevices(balena, deviceUuids);
// Get devices
const devices = await Promise.all(
deviceIds.map(
(uuid) =>
balena.models.device.get(
uuid,
expandForAppNameAndCpuArch,
) as Promise<ExtendedDevice>,
),
);
// Map application name for each device
for (const device of devices) {
const belongsToApplication = device.belongs_to__application;
device.application_name = belongsToApplication?.[0]
? belongsToApplication[0].app_name
: 'N/a';
}
// Disambiguate application (if is a number, it could either be an ID or a numerical name)
// Disambiguate application
const { getApplication } = await import('../../utils/sdk');
// Get destination application
const application = options.application
? await getApplication(balena, options.application)
const application = options.fleet
? await getApplication(balena, options.fleet, { $select: ['id', 'slug'] })
: await this.interactivelySelectApplication(balena, devices);
// Move each device
for (const uuid of deviceIds) {
for (const uuid of deviceUuids) {
try {
await balena.models.device.move(uuid, application.id);
console.info(`Device ${uuid} was moved to fleet ${application.slug}`);
@ -150,9 +122,8 @@ export default class DeviceMoveCmd extends Command {
async interactivelySelectApplication(
balena: BalenaSDK,
devices: ExtendedDevice[],
devices: Awaited<ReturnType<typeof this.getDevices>>,
) {
const { getExpandedProp } = await import('../../utils/pine');
// deduplicate the slugs
const deviceCpuArchs = Array.from(
new Set(
@ -162,46 +133,44 @@ export default class DeviceMoveCmd extends Command {
),
);
const deviceTypeOptions = {
$select: 'slug',
$expand: {
is_of__cpu_architecture: {
$select: 'slug',
},
const allCpuArches = await balena.pine.get({
resource: 'cpu_architecture',
options: {
$select: ['id', 'slug'],
},
} as const;
const deviceTypes = (await balena.models.deviceType.getAllSupported(
deviceTypeOptions,
)) as Array<PineTypedResult<DeviceType, typeof deviceTypeOptions>>;
});
const compatibleDeviceTypeSlugs = new Set(
deviceTypes
.filter((deviceType) => {
const deviceTypeArch = getExpandedProp(
deviceType.is_of__cpu_architecture,
'slug',
)!;
return deviceCpuArchs.every((deviceCpuArch) =>
balena.models.os.isArchitectureCompatibleWith(
deviceCpuArch,
deviceTypeArch,
),
);
})
.map((deviceType) => deviceType.slug),
);
const compatibleCpuArchIds = allCpuArches
.filter((cpuArch) => {
return deviceCpuArchs.every((deviceCpuArch) =>
balena.models.os.isArchitectureCompatibleWith(
deviceCpuArch,
cpuArch.slug,
),
);
})
.map((deviceType) => deviceType.id);
const patterns = await import('../../utils/patterns');
try {
const application = await patterns.selectApplication(
(app) =>
compatibleDeviceTypeSlugs.has(app.is_for__device_type[0].slug) &&
devices.some((device) => device.application_name !== app.app_name),
{
is_for__device_type: {
$any: {
$alias: 'dt',
$expr: {
dt: {
is_of__cpu_architecture: { $in: compatibleCpuArchIds },
},
},
},
},
},
true,
);
return application;
} catch (err) {
if (!compatibleDeviceTypeSlugs.size) {
if (!compatibleCpuArchIds.length) {
throw new ExpectedError(
`${err.message}\nDo all devices have a compatible architecture?`,
);

View File

@ -15,25 +15,13 @@
* limitations under the License.
*/
import { flags } from '@oclif/command';
import type { IArg } from '@oclif/parser/lib/args';
import { Flags, Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent, getCliForm } from '../../utils/lazy';
import { tryAsInteger } from '../../utils/validation';
import type { Device } from 'balena-sdk';
import { ExpectedError } from '../../errors';
interface FlagsDef {
version?: string;
yes: boolean;
help: void;
}
interface ArgsDef {
uuid: string;
}
export default class DeviceOsUpdateCmd extends Command {
public static description = stripIndent`
Start a Host OS update for a device.
@ -47,23 +35,29 @@ export default class DeviceOsUpdateCmd extends Command {
`;
public static examples = [
'$ balena device os-update 23c73a1',
'$ balena device os-update 23c73a1 --version 2.101.7',
'$ balena device os-update 23c73a1 --version 2.31.0+rev1.prod',
'$ balena device os-update 23c73a1 --include-draft',
];
public static args: Array<IArg<any>> = [
{
name: 'uuid',
public static args = {
uuid: Args.string({
description: 'the uuid of the device to update',
parse: (dev) => tryAsInteger(dev),
required: true,
},
];
}),
};
public static usage = 'device os-update <uuid>';
public static flags: flags.Input<FlagsDef> = {
version: flags.string({
public static flags = {
version: Flags.string({
description: 'a balenaOS version',
exclusive: ['include-draft'],
}),
'include-draft': Flags.boolean({
description: 'include pre-release balenaOS versions',
default: false,
exclusive: ['version'],
}),
yes: cf.yes,
help: cf.help,
@ -72,9 +66,8 @@ export default class DeviceOsUpdateCmd extends Command {
public static authenticated = true;
public async run() {
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
DeviceOsUpdateCmd,
);
const { args: params, flags: options } =
await this.parse(DeviceOsUpdateCmd);
const sdk = getBalenaSdk();
@ -100,10 +93,25 @@ export default class DeviceOsUpdateCmd extends Command {
);
}
let includeDraft = options['include-draft'];
if (!includeDraft && options.version != null) {
const bSemver = await import('balena-semver');
const parsedVersion = bSemver.parse(options.version);
// When the user provides a draft version, we need to pass `includeDraft`
// to the os.getSupportedOsUpdateVersions() since w/o it the results
// will for sure not include the user provided version and the command
// would return a "not in the Host OS update targets" error.
includeDraft =
parsedVersion != null && parsedVersion.prerelease.length > 0;
}
// Get supported OS update versions
const hupVersionInfo = await sdk.models.os.getSupportedOsUpdateVersions(
is_of__device_type[0].slug,
currentOsVersion,
{
includeDraft,
},
);
if (hupVersionInfo.versions.length === 0) {
throw new ExpectedError(
@ -114,6 +122,8 @@ export default class DeviceOsUpdateCmd extends Command {
// Get target OS version
let targetOsVersion = options.version;
if (targetOsVersion != null) {
const { normalizeOsVersion } = await import('../../utils/normalization');
targetOsVersion = normalizeOsVersion(targetOsVersion);
if (!hupVersionInfo.versions.includes(targetOsVersion)) {
throw new ExpectedError(
`The provided version ${targetOsVersion} is not in the Host OS update targets for this device`,

View File

@ -0,0 +1,91 @@
/**
* @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 { Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import { getExpandedProp } from '../../utils/pine';
export default class DevicePinCmd extends Command {
public static description = stripIndent`
Pin a device to a release.
Pin a device to a release.
Note, if the commit is omitted, the currently pinned release will be printed, with instructions for how to see a list of releases
`;
public static examples = [
'$ balena device pin 7cf02a6',
'$ balena device pin 7cf02a6 91165e5',
];
public static args = {
uuid: Args.string({
description: 'the uuid of the device to pin to a release',
required: true,
}),
releaseToPinTo: Args.string({
description: 'the commit of the release for the device to get pinned to',
}),
};
public static usage = 'device pin <uuid> [releaseToPinTo]';
public static flags = {
help: cf.help,
};
public static authenticated = true;
public async run() {
const { args: params } = await this.parse(DevicePinCmd);
const balena = getBalenaSdk();
const device = await balena.models.device.get(params.uuid, {
$expand: {
should_be_running__release: {
$select: 'commit',
},
belongs_to__application: {
$select: 'slug',
},
},
});
const pinnedRelease = getExpandedProp(
device.should_be_running__release,
'commit',
);
const appSlug = getExpandedProp(device.belongs_to__application, 'slug');
const releaseToPinTo = params.releaseToPinTo;
if (!releaseToPinTo) {
console.log(
`${
pinnedRelease
? `This device is currently pinned to ${pinnedRelease}.`
: 'This device is not currently pinned to any release.'
} \n\nTo see a list of all releases this device can be pinned to, run \`balena releases ${appSlug}\`.`,
);
} else {
await balena.models.device.pinToRelease(params.uuid, releaseToPinTo);
}
}
}

View File

@ -15,26 +15,11 @@
* limitations under the License.
*/
import { flags } from '@oclif/command';
import type { IArg } from '@oclif/parser/lib/args';
import { Flags, Args } from '@oclif/core';
import Command from '../../command';
import { ExpectedError } from '../../errors';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import { tryAsInteger } from '../../utils/validation';
interface FlagsDef {
enable: boolean;
disable: boolean;
status: boolean;
help?: void;
}
interface ArgsDef {
uuid: string;
// Optional hidden arg to support old command format
legacyUuid?: string;
}
export default class DevicePublicUrlCmd extends Command {
public static description = stripIndent`
@ -43,9 +28,6 @@ export default class DevicePublicUrlCmd extends Command {
This command will output the current public URL for the
specified device. It can also enable or disable the URL,
or output the enabled status, using the respective options.
The old command style 'balena device public-url enable <uuid>'
is deprecated, but still supported.
`;
public static examples = [
@ -55,33 +37,25 @@ export default class DevicePublicUrlCmd extends Command {
'$ balena device public-url 23c73a1 --status',
];
public static args: Array<IArg<any>> = [
{
name: 'uuid',
public static args = {
uuid: Args.string({
description: 'the uuid of the device to manage',
parse: (dev) => tryAsInteger(dev),
required: true,
},
{
// Optional hidden arg to support old command format
name: 'legacyUuid',
parse: (dev) => tryAsInteger(dev),
hidden: true,
},
];
}),
};
public static usage = 'device public-url <uuid>';
public static flags: flags.Input<FlagsDef> = {
enable: flags.boolean({
public static flags = {
enable: Flags.boolean({
description: 'enable the public URL',
exclusive: ['disable', 'status'],
}),
disable: flags.boolean({
disable: Flags.boolean({
description: 'disable the public URL',
exclusive: ['enable', 'status'],
}),
status: flags.boolean({
status: Flags.boolean({
description: 'determine if public URL is enabled',
exclusive: ['enable', 'disable'],
}),
@ -91,28 +65,8 @@ export default class DevicePublicUrlCmd extends Command {
public static authenticated = true;
public async run() {
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
DevicePublicUrlCmd,
);
// Legacy command format support.
// Previously this command used the following format
// (changed due to oclif technicalities):
// `balena device public-url enable|disable|status <uuid>`
if (params.legacyUuid) {
const action = params.uuid;
if (!['enable', 'disable', 'status'].includes(action)) {
throw new ExpectedError(
`Unexpected arguments: ${params.uuid} ${params.legacyUuid}`,
);
}
options.enable = action === 'enable';
options.disable = action === 'disable';
options.status = action === 'status';
params.uuid = params.legacyUuid;
delete params.legacyUuid;
}
const { args: params, flags: options } =
await this.parse(DevicePublicUrlCmd);
const balena = getBalenaSdk();

View File

@ -15,20 +15,11 @@
* limitations under the License.
*/
import { flags } from '@oclif/command';
import type { IArg } from '@oclif/parser/lib/args';
import { Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, getCliUx, stripIndent } from '../../utils/lazy';
interface FlagsDef {
help: void;
}
interface ArgsDef {
uuid: string;
}
export default class DevicePurgeCmd extends Command {
public static description = stripIndent`
Purge data from a device.
@ -46,34 +37,30 @@ export default class DevicePurgeCmd extends Command {
public static usage = 'device purge <uuid>';
public static args: Array<IArg<any>> = [
{
name: 'uuid',
public static args = {
uuid: Args.string({
description: 'comma-separated list (no blank spaces) of device UUIDs',
required: true,
},
];
}),
};
public static flags: flags.Input<FlagsDef> = {
public static flags = {
help: cf.help,
};
public static authenticated = true;
public async run() {
const { args: params } = this.parse<FlagsDef, ArgsDef>(DevicePurgeCmd);
const { args: params } = await this.parse(DevicePurgeCmd);
const { tryAsInteger } = await import('../../utils/validation');
const balena = getBalenaSdk();
const ux = getCliUx();
const deviceIds = params.uuid.split(',').map((id) => {
return tryAsInteger(id);
});
const deviceUuids = params.uuid.split(',');
for (const deviceId of deviceIds) {
ux.action.start(`Purging data from device ${deviceId}`);
await balena.models.device.purge(deviceId);
for (const uuid of deviceUuids) {
ux.action.start(`Purging data from device ${uuid}`);
await balena.models.device.purge(uuid);
ux.action.stop();
}
}

View File

@ -15,21 +15,10 @@
* limitations under the License.
*/
import { flags } from '@oclif/command';
import type { IArg } from '@oclif/parser/lib/args';
import { Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import { tryAsInteger } from '../../utils/validation';
interface FlagsDef {
force: boolean;
help: void;
}
interface ArgsDef {
uuid: string;
}
export default class DeviceRebootCmd extends Command {
public static description = stripIndent`
@ -39,18 +28,16 @@ export default class DeviceRebootCmd extends Command {
`;
public static examples = ['$ balena device reboot 23c73a1'];
public static args: Array<IArg<any>> = [
{
name: 'uuid',
public static args = {
uuid: Args.string({
description: 'the uuid of the device to reboot',
parse: (dev) => tryAsInteger(dev),
required: true,
},
];
}),
};
public static usage = 'device reboot <uuid>';
public static flags: flags.Input<FlagsDef> = {
public static flags = {
force: cf.force,
help: cf.help,
};
@ -58,9 +45,7 @@ export default class DeviceRebootCmd extends Command {
public static authenticated = true;
public async run() {
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
DeviceRebootCmd,
);
const { args: params, flags: options } = await this.parse(DeviceRebootCmd);
const balena = getBalenaSdk();

View File

@ -15,23 +15,13 @@
* limitations under the License.
*/
import { flags } from '@oclif/command';
import type { IArg } from '@oclif/parser/lib/args';
import { Flags } from '@oclif/core';
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 {
uuid?: string;
help: void;
}
interface ArgsDef {
fleet: string;
}
export default class DeviceRegisterCmd extends Command {
public static description = stripIndent`
Register a new device.
@ -47,37 +37,49 @@ export default class DeviceRegisterCmd extends Command {
'$ balena device register MyFleet',
'$ balena device register MyFleet --uuid <uuid>',
'$ balena device register myorg/myfleet --uuid <uuid>',
'$ balena device register myorg/myfleet --uuid <uuid> --deviceType <deviceTypeSlug>',
];
public static args: Array<IArg<any>> = [ca.fleetRequired];
public static args = {
fleet: ca.fleetRequired,
};
public static usage = 'device register <fleet>';
public static flags: flags.Input<FlagsDef> = {
uuid: flags.string({
public static flags = {
uuid: Flags.string({
description: 'custom uuid',
char: 'u',
}),
deviceType: Flags.string({
description:
"device type slug (run 'balena devices supported' for possible values)",
}),
help: cf.help,
};
public static authenticated = true;
public async run() {
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
DeviceRegisterCmd,
);
const { args: params, flags: options } =
await this.parse(DeviceRegisterCmd);
const { getApplication } = await import('../../utils/sdk');
const balena = getBalenaSdk();
const application = await getApplication(balena, params.fleet);
const application = await getApplication(balena, params.fleet, {
$select: ['id', 'slug'],
});
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,
options.deviceType,
);
return result && result.uuid;
}

View File

@ -15,21 +15,10 @@
* limitations under the License.
*/
import { flags } from '@oclif/command';
import type { IArg } from '@oclif/parser/lib/args';
import { Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent, getCliForm } from '../../utils/lazy';
import { tryAsInteger } from '../../utils/validation';
interface FlagsDef {
help: void;
}
interface ArgsDef {
uuid: string;
newName?: string;
}
export default class DeviceRenameCmd extends Command {
public static description = stripIndent`
@ -44,29 +33,26 @@ export default class DeviceRenameCmd extends Command {
'$ balena device rename 7cf02a6 MyPi',
];
public static args: Array<IArg<any>> = [
{
name: 'uuid',
public static args = {
uuid: Args.string({
description: 'the uuid of the device to rename',
parse: (dev) => tryAsInteger(dev),
required: true,
},
{
name: 'newName',
}),
newName: Args.string({
description: 'the new name for the device',
},
];
}),
};
public static usage = 'device rename <uuid> [newName]';
public static flags: flags.Input<FlagsDef> = {
public static flags = {
help: cf.help,
};
public static authenticated = true;
public async run() {
const { args: params } = this.parse<FlagsDef, ArgsDef>(DeviceRenameCmd);
const { args: params } = await this.parse(DeviceRenameCmd);
const balena = getBalenaSdk();

View File

@ -15,8 +15,7 @@
* limitations under the License.
*/
import { flags } from '@oclif/command';
import type { IArg } from '@oclif/parser/lib/args';
import { Flags, Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, getCliUx, stripIndent } from '../../utils/lazy';
@ -26,15 +25,6 @@ import type {
CurrentServiceWithCommit,
} from 'balena-sdk';
interface FlagsDef {
help: void;
service?: string;
}
interface ArgsDef {
uuid: string;
}
export default class DeviceRestartCmd extends Command {
public static description = stripIndent`
Restart containers on a device.
@ -55,19 +45,18 @@ export default class DeviceRestartCmd extends Command {
'$ balena device restart 23c73a1 -s myService1,myService2',
];
public static args: Array<IArg<any>> = [
{
name: 'uuid',
public static args = {
uuid: Args.string({
description:
'comma-separated list (no blank spaces) of device UUIDs to restart',
required: true,
},
];
}),
};
public static usage = 'device restart <uuid>';
public static flags: flags.Input<FlagsDef> = {
service: flags.string({
public static flags = {
service: Flags.string({
description:
'comma-separated list (no blank spaces) of service names to restart',
char: 's',
@ -78,28 +67,23 @@ export default class DeviceRestartCmd extends Command {
public static authenticated = true;
public async run() {
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
DeviceRestartCmd,
);
const { args: params, flags: options } = await this.parse(DeviceRestartCmd);
const { tryAsInteger } = await import('../../utils/validation');
const balena = getBalenaSdk();
const ux = getCliUx();
const deviceIds = params.uuid.split(',').map((id) => {
return tryAsInteger(id);
});
const deviceUuids = params.uuid.split(',');
const serviceNames = options.service?.split(',');
// Iterate sequentially through deviceIds.
// Iterate sequentially through deviceUuids.
// We may later want to add a batching feature,
// so that n devices are processed in parallel
for (const deviceId of deviceIds) {
ux.action.start(`Restarting services on device ${deviceId}`);
for (const uuid of deviceUuids) {
ux.action.start(`Restarting services on device ${uuid}`);
if (serviceNames) {
await this.restartServices(balena, deviceId, serviceNames);
await this.restartServices(balena, uuid, serviceNames);
} else {
await this.restartAllServices(balena, deviceId);
await this.restartAllServices(balena, uuid);
}
ux.action.stop();
}
@ -107,7 +91,7 @@ export default class DeviceRestartCmd extends Command {
async restartServices(
balena: BalenaSDK,
deviceId: number | string,
deviceUuid: string,
serviceNames: string[],
) {
const { ExpectedError, instanceOf } = await import('../../errors');
@ -116,7 +100,7 @@ export default class DeviceRestartCmd extends Command {
// Get device
let device: DeviceWithServiceDetails<CurrentServiceWithCommit>;
try {
device = await balena.models.device.getWithServiceDetails(deviceId, {
device = await balena.models.device.getWithServiceDetails(deviceUuid, {
$expand: {
is_running__release: { $select: 'commit' },
},
@ -124,7 +108,7 @@ export default class DeviceRestartCmd extends Command {
} catch (e) {
const { BalenaDeviceNotFound } = await import('balena-errors');
if (instanceOf(e, BalenaDeviceNotFound)) {
throw new ExpectedError(`Device ${deviceId} not found.`);
throw new ExpectedError(`Device ${deviceUuid} not found.`);
} else {
throw e;
}
@ -136,7 +120,7 @@ export default class DeviceRestartCmd extends Command {
serviceNames.forEach((service) => {
if (!device.current_services[service]) {
throw new ExpectedError(
`Service ${service} not found on device ${deviceId}.`,
`Service ${service} not found on device ${deviceUuid}.`,
);
}
});
@ -155,7 +139,7 @@ export default class DeviceRestartCmd extends Command {
if (serviceContainer) {
restartPromises.push(
balena.models.device.restartService(
deviceId,
deviceUuid,
serviceContainer.image_id,
),
);
@ -166,32 +150,32 @@ export default class DeviceRestartCmd extends Command {
await Promise.all(restartPromises);
} catch (e) {
if (e.message.toLowerCase().includes('no online device')) {
throw new ExpectedError(`Device ${deviceId} is not online.`);
throw new ExpectedError(`Device ${deviceUuid} is not online.`);
} else {
throw e;
}
}
}
async restartAllServices(balena: BalenaSDK, deviceId: number | string) {
async restartAllServices(balena: BalenaSDK, deviceUuid: string) {
// Note: device.restartApplication throws `BalenaDeviceNotFound: Device not found` if device not online.
// Need to use device.get first to distinguish between non-existant and offline devices.
// Remove this workaround when SDK issue resolved: https://github.com/balena-io/balena-sdk/issues/649
const { instanceOf, ExpectedError } = await import('../../errors');
try {
const device = await balena.models.device.get(deviceId);
const device = await balena.models.device.get(deviceUuid);
if (!device.is_online) {
throw new ExpectedError(`Device ${deviceId} is not online.`);
throw new ExpectedError(`Device ${deviceUuid} is not online.`);
}
} catch (e) {
const { BalenaDeviceNotFound } = await import('balena-errors');
if (instanceOf(e, BalenaDeviceNotFound)) {
throw new ExpectedError(`Device ${deviceId} not found.`);
throw new ExpectedError(`Device ${deviceUuid} not found.`);
} else {
throw e;
}
}
await balena.models.device.restartApplication(deviceId);
await balena.models.device.restartApplication(deviceUuid);
}
}

View File

@ -15,21 +15,10 @@
* limitations under the License.
*/
import { flags } from '@oclif/command';
import type { IArg } from '@oclif/parser/lib/args';
import { Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import { tryAsInteger } from '../../utils/validation';
interface FlagsDef {
yes: boolean;
help: void;
}
interface ArgsDef {
uuid: string;
}
export default class DeviceRmCmd extends Command {
public static description = stripIndent`
@ -46,18 +35,17 @@ export default class DeviceRmCmd extends Command {
'$ balena device rm 7cf02a6 --yes',
];
public static args: Array<IArg<any>> = [
{
name: 'uuid',
public static args = {
uuid: Args.string({
description:
'comma-separated list (no blank spaces) of device UUIDs to be removed',
required: true,
},
];
}),
};
public static usage = 'device rm <uuid(s)>';
public static flags: flags.Input<FlagsDef> = {
public static flags = {
yes: cf.yes,
help: cf.help,
};
@ -65,9 +53,7 @@ export default class DeviceRmCmd extends Command {
public static authenticated = true;
public async run() {
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
DeviceRmCmd,
);
const { args: params, flags: options } = await this.parse(DeviceRmCmd);
const balena = getBalenaSdk();
const patterns = await import('../../utils/patterns');
@ -84,7 +70,7 @@ export default class DeviceRmCmd extends Command {
// Remove
for (const uuid of uuids) {
try {
await balena.models.device.remove(tryAsInteger(uuid));
await balena.models.device.remove(uuid);
} catch (err) {
console.info(`${err.message}, uuid: ${uuid}`);
process.exitCode = 1;

View File

@ -15,23 +15,12 @@
* limitations under the License.
*/
import { flags } from '@oclif/command';
import type { IArg } from '@oclif/parser/lib/args';
import { Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import { tryAsInteger } from '../../utils/validation';
import { ExpectedError } from '../../errors';
interface FlagsDef {
force: boolean;
help: void;
}
interface ArgsDef {
uuid: string;
}
export default class DeviceShutdownCmd extends Command {
public static description = stripIndent`
Shutdown a device.
@ -40,18 +29,16 @@ export default class DeviceShutdownCmd extends Command {
`;
public static examples = ['$ balena device shutdown 23c73a1'];
public static args: Array<IArg<any>> = [
{
name: 'uuid',
public static args = {
uuid: Args.string({
description: 'the uuid of the device to shutdown',
parse: (dev) => tryAsInteger(dev),
required: true,
},
];
}),
};
public static usage = 'device shutdown <uuid>';
public static flags: flags.Input<FlagsDef> = {
public static flags = {
force: cf.force,
help: cf.help,
};
@ -59,9 +46,8 @@ export default class DeviceShutdownCmd extends Command {
public static authenticated = true;
public async run() {
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
DeviceShutdownCmd,
);
const { args: params, flags: options } =
await this.parse(DeviceShutdownCmd);
const balena = getBalenaSdk();

View File

@ -0,0 +1,139 @@
/**
* @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 { Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, getCliUx, stripIndent } from '../../utils/lazy';
import type { BalenaSDK } from 'balena-sdk';
export default class DeviceStartServiceCmd extends Command {
public static description = stripIndent`
Start containers on a device.
Start containers on a device.
Multiple devices and services may be specified with a comma-separated list
of values (no spaces).
`;
public static examples = [
'$ balena device start-service 23c73a1 myService',
'$ balena device start-service 23c73a1 myService1,myService2',
];
public static args = {
uuid: Args.string({
description: 'comma-separated list (no blank spaces) of device UUIDs',
required: true,
}),
service: Args.string({
description: 'comma-separated list (no blank spaces) of service names',
required: true,
}),
};
public static usage = 'device start-service <uuid>';
public static flags = {
help: cf.help,
};
public static authenticated = true;
public async run() {
const { args: params } = await this.parse(DeviceStartServiceCmd);
const balena = getBalenaSdk();
const ux = getCliUx();
const deviceUuids = params.uuid.split(',');
const serviceNames = params.service.split(',');
// Iterate sequentially through deviceUuids.
// We may later want to add a batching feature,
// so that n devices are processed in parallel
for (const uuid of deviceUuids) {
ux.action.start(`Starting services on device ${uuid}`);
await this.startServices(balena, uuid, serviceNames);
ux.action.stop();
}
}
async startServices(
balena: BalenaSDK,
deviceUuid: string,
serviceNames: string[],
) {
const { ExpectedError } = await import('../../errors');
const { getExpandedProp } = await import('../../utils/pine');
// Get device
const device = await balena.models.device.getWithServiceDetails(
deviceUuid,
{
$expand: {
is_running__release: { $select: 'commit' },
},
},
);
const activeReleaseCommit = getExpandedProp(
device.is_running__release,
'commit',
);
// Check specified services exist on this device before startinganything
serviceNames.forEach((service) => {
if (!device.current_services[service]) {
throw new ExpectedError(
`Service ${service} not found on device ${deviceUuid}.`,
);
}
});
// Start services
const startPromises: Array<Promise<void>> = [];
for (const serviceName of serviceNames) {
const service = device.current_services[serviceName];
// Each service is an array of `CurrentServiceWithCommit`
// because when service is updating, it will actually hold 2 services
// Target commit matching `device.is_running__release`
const serviceContainer = service.find((s) => {
return s.commit === activeReleaseCommit;
});
if (serviceContainer) {
startPromises.push(
balena.models.device.startService(
deviceUuid,
serviceContainer.image_id,
),
);
}
}
try {
await Promise.all(startPromises);
} catch (e) {
if (e.message.toLowerCase().includes('no online device')) {
throw new ExpectedError(`Device ${deviceUuid} is not online.`);
} else {
throw e;
}
}
}
}

View File

@ -0,0 +1,139 @@
/**
* @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 { Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, getCliUx, stripIndent } from '../../utils/lazy';
import type { BalenaSDK } from 'balena-sdk';
export default class DeviceStopServiceCmd extends Command {
public static description = stripIndent`
Stop containers on a device.
Stop containers on a device.
Multiple devices and services may be specified with a comma-separated list
of values (no spaces).
`;
public static examples = [
'$ balena device stop-service 23c73a1 myService',
'$ balena device stop-service 23c73a1 myService1,myService2',
];
public static args = {
uuid: Args.string({
description: 'comma-separated list (no blank spaces) of device UUIDs',
required: true,
}),
service: Args.string({
description: 'comma-separated list (no blank spaces) of service names',
required: true,
}),
};
public static usage = 'device stop-service <uuid>';
public static flags = {
help: cf.help,
};
public static authenticated = true;
public async run() {
const { args: params } = await this.parse(DeviceStopServiceCmd);
const balena = getBalenaSdk();
const ux = getCliUx();
const deviceUuids = params.uuid.split(',');
const serviceNames = params.service.split(',');
// Iterate sequentially through deviceUuids.
// We may later want to add a batching feature,
// so that n devices are processed in parallel
for (const uuid of deviceUuids) {
ux.action.start(`Stopping services on device ${uuid}`);
await this.stopServices(balena, uuid, serviceNames);
ux.action.stop();
}
}
async stopServices(
balena: BalenaSDK,
deviceUuid: string,
serviceNames: string[],
) {
const { ExpectedError } = await import('../../errors');
const { getExpandedProp } = await import('../../utils/pine');
// Get device
const device = await balena.models.device.getWithServiceDetails(
deviceUuid,
{
$expand: {
is_running__release: { $select: 'commit' },
},
},
);
const activeReleaseCommit = getExpandedProp(
device.is_running__release,
'commit',
);
// Check specified services exist on this device before stoppinganything
serviceNames.forEach((service) => {
if (!device.current_services[service]) {
throw new ExpectedError(
`Service ${service} not found on device ${deviceUuid}.`,
);
}
});
// Stop services
const stopPromises: Array<Promise<void>> = [];
for (const serviceName of serviceNames) {
const service = device.current_services[serviceName];
// Each service is an array of `CurrentServiceWithCommit`
// because when service is updating, it will actually hold 2 services
// Target commit matching `device.is_running__release`
const serviceContainer = service.find((s) => {
return s.commit === activeReleaseCommit;
});
if (serviceContainer) {
stopPromises.push(
balena.models.device.stopService(
deviceUuid,
serviceContainer.image_id,
),
);
}
}
try {
await Promise.all(stopPromises);
} catch (e) {
if (e.message.toLowerCase().includes('no online device')) {
throw new ExpectedError(`Device ${deviceUuid} is not online.`);
} else {
throw e;
}
}
}
}

View File

@ -0,0 +1,53 @@
/**
* @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 { Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
export default class DeviceTrackFleetCmd extends Command {
public static description = stripIndent`
Make a device track the fleet's pinned release.
Make a device track the fleet's pinned release.
`;
public static examples = ['$ balena device track-fleet 7cf02a6'];
public static args = {
uuid: Args.string({
description: "the uuid of the device to make track the fleet's release",
required: true,
}),
};
public static usage = 'device track-fleet <uuid>';
public static flags = {
help: cf.help,
};
public static authenticated = true;
public async run() {
const { args: params } = await this.parse(DeviceTrackFleetCmd);
const balena = getBalenaSdk();
await balena.models.device.trackApplicationRelease(params.uuid);
}
}

View File

@ -15,36 +15,25 @@
* limitations under the License.
*/
import { flags } from '@oclif/command';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { expandForAppName } from '../../utils/helpers';
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
import {
applicationIdInfo,
appToFleetFlagMsg,
appToFleetOutputMsg,
jsonInfo,
warnify,
} from '../../utils/messages';
import { isV13 } from '../../utils/version';
import { applicationIdInfo, jsonInfo } from '../../utils/messages';
import type { Application } from 'balena-sdk';
import type { Device, PineOptions } from 'balena-sdk';
interface ExtendedDevice extends DeviceWithDeviceType {
dashboard_url?: string;
application_name?: string | null;
device_type?: string | null;
}
interface FlagsDef {
application?: string;
app?: string;
fleet?: string;
help: void;
json: boolean;
v13: boolean;
}
const devicesSelectFields = {
$select: [
'id',
'uuid',
'device_name',
'status',
'is_online',
'supervisor_version',
'os_version',
],
} satisfies PineOptions<Device>;
export default class DevicesCmd extends Command {
public static description = stripIndent`
@ -66,84 +55,58 @@ export default class DevicesCmd extends Command {
public static usage = 'devices';
public static flags: flags.Input<FlagsDef> = {
...(isV13()
? {}
: {
application: {
...cf.application,
exclusive: ['app', 'fleet', 'v13'],
},
app: { ...cf.app, exclusive: ['application', 'fleet', 'v13'] },
}),
fleet: { ...cf.fleet, exclusive: ['app', 'application'] },
public static flags = {
fleet: cf.fleet,
json: cf.json,
help: cf.help,
v13: cf.v13,
};
public static primary = true;
public static authenticated = true;
protected useAppWord = false;
protected hasWarned = false;
public async run() {
const { flags: options } = this.parse<FlagsDef, {}>(DevicesCmd);
this.useAppWord = !options.fleet && !options.v13 && !isV13();
const { flags: options } = await this.parse(DevicesCmd);
const balena = getBalenaSdk();
const devicesOptions = {
...devicesSelectFields,
...expandForAppName,
$orderby: { device_name: 'asc' },
} satisfies PineOptions<Device>;
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;
const devices = (
await (async () => {
if (options.fleet != null) {
const { getApplication } = await import('../../utils/sdk');
const application = await getApplication(balena, options.fleet, {
$select: 'slug',
$expand: {
owns__device: devicesOptions,
},
});
return application.owns__device;
}
let devices;
return await balena.pine.get({
resource: 'device',
options: devicesOptions,
});
})()
).map((device) => ({
...device,
dashboard_url: balena.models.device.getDashboardUrl(device.uuid),
fleet: device.belongs_to__application?.[0]?.slug || null,
uuid: options.json ? device.uuid : device.uuid.slice(0, 7),
device_type: device.is_of__device_type?.[0]?.slug || null,
}));
if (options.application != null) {
const { getApplication } = await import('../../utils/sdk');
const application = await getApplication(balena, options.application);
devices = (await balena.models.device.getAllByApplication(
application.id,
expandForAppName,
)) as ExtendedDevice[];
} else {
devices = (await balena.models.device.getAll(
expandForAppName,
)) as ExtendedDevice[];
}
devices = devices.map(function (device) {
device.dashboard_url = balena.models.device.getDashboardUrl(device.uuid);
const belongsToApplication =
device.belongs_to__application as Application[];
device.application_name = belongsToApplication?.[0]?.app_name || null;
device.uuid = options.json ? device.uuid : device.uuid.slice(0, 7);
device.device_type = device.is_of__device_type?.[0]?.slug || null;
return device;
});
const jName = this.useAppWord ? 'application_name' : 'fleet_name';
const tName = this.useAppWord ? 'APPLICATION NAME' : 'FLEET';
const fields = [
const fields: Array<keyof (typeof devices)[number]> = [
'id',
'uuid',
'device_name',
'device_type',
options.json
? `application_name => ${jName}`
: `application_name => ${tName}`,
'fleet',
'status',
'is_online',
'supervisor_version',
@ -156,9 +119,6 @@ export default class DevicesCmd extends Command {
const mapped = devices.map((device) => pickAndRename(device, fields));
console.log(JSON.stringify(mapped, null, 4));
} else {
if (!this.hasWarned && this.useAppWord && process.stderr.isTTY) {
console.error(warnify(appToFleetOutputMsg));
}
const _ = await import('lodash');
console.log(
getVisuals().table.horizontal(

View File

@ -14,40 +14,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { flags } from '@oclif/command';
import { Flags } from '@oclif/core';
import type * as BalenaSdk from 'balena-sdk';
import * as _ from 'lodash';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
import { CommandHelp } from '../../utils/oclif-utils';
import { isV13 } from '../../utils/version';
interface FlagsDef {
discontinued: boolean;
help: void;
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 {
public static description = stripIndent`
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,
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
@ -56,8 +37,7 @@ export default class DevicesSupportedCmd extends Command {
`;
public static examples = [
'$ balena devices supported',
'$ balena devices supported --verbose',
'$ balena devices supported -vj',
'$ balena devices supported --json',
];
public static usage = (
@ -65,83 +45,49 @@ export default class DevicesSupportedCmd extends Command {
new CommandHelp({ args: DevicesSupportedCmd.args }).defaultUsage()
).trim();
public static flags: flags.Input<FlagsDef> = {
discontinued: flags.boolean({
description: isV13()
? 'No effect (DEPRECATED)'
: 'include "discontinued" device types (DEPRECATED)',
}),
public static flags = {
help: cf.help,
json: flags.boolean({
json: Flags.boolean({
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() {
const { flags: options } = this.parse<FlagsDef, {}>(DevicesSupportedCmd);
const [dts, configDTs] = await Promise.all([
getBalenaSdk().models.deviceType.getAllSupported({
$expand: { is_of__cpu_architecture: { $select: 'slug' } },
$select: ['slug', 'name'],
}),
getBalenaSdk().models.config.getDeviceTypes(),
]);
const dtsBySlug = _.keyBy(dts, (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),
]);
const { flags: options } = await this.parse(DevicesSupportedCmd);
const pineOptions = {
$select: ['slug', 'name'],
$expand: {
is_of__cpu_architecture: { $select: 'slug' },
device_type_alias: {
$select: 'is_referenced_by__alias',
$orderby: { is_referenced_by__alias: 'asc' },
},
},
} satisfies BalenaSdk.PineOptions<BalenaSdk.DeviceType>;
const dts = (await getBalenaSdk().models.deviceType.getAllSupported(
pineOptions,
)) as Array<
BalenaSdk.PineTypedResult<BalenaSdk.DeviceType, typeof pineOptions>
>;
interface DT {
slug: string;
aliases: string[];
arch: string;
state?: string; // to be removed in CLI v13
name: string;
}
let deviceTypes: DT[] = [];
for (const slug of slugsOfInterest) {
const configDT: Partial<typeof configDTs[0]> =
configDTsBySlug[slug] || {};
if (configDT.state === 'DISCONTINUED' && !options.discontinued) {
continue;
}
const dt: Partial<typeof dts[0]> = dtsBySlug[slug] || {};
const aliases = (configDT.aliases || []).filter(
(alias) => alias !== slug,
);
deviceTypes.push({
slug,
let deviceTypes = dts.map((dt): DT => {
const aliases = dt.device_type_alias
.map((dta) => dta.is_referenced_by__alias)
.filter((alias) => alias !== dt.slug);
return {
slug: dt.slug,
aliases: options.json ? aliases : [aliases.join(', ')],
arch:
(dt.is_of__cpu_architecture as any)?.[0]?.slug ||
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 =
options.verbose && !isV13()
? ['slug', 'aliases', 'arch', 'state', 'name']
: ['slug', 'aliases', 'arch', 'name'];
arch: dt.is_of__cpu_architecture[0]?.slug || 'n/a',
name: dt.name || 'N/A',
};
});
const fields = ['slug', 'aliases', 'arch', 'name'];
deviceTypes = _.sortBy(deviceTypes, fields);
if (options.json) {
console.log(JSON.stringify(deviceTypes, null, 4));

View File

@ -15,21 +15,15 @@
* limitations under the License.
*/
import { flags } from '@oclif/command';
import { Args } from '@oclif/core';
import type * as BalenaSdk 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 {
applicationIdInfo,
appToFleetFlagMsg,
warnify,
} from '../../utils/messages';
import { isV13 } from '../../utils/version';
import { applicationIdInfo } from '../../utils/messages';
interface FlagsDef {
application?: string;
fleet?: string;
device?: string; // device UUID
help: void;
@ -84,44 +78,35 @@ export default class EnvAddCmd extends Command {
'$ balena env add EDITOR vim --device 7cf02a6,d6f1433 --service MyService,MyService2',
];
public static args = [
{
name: 'name',
public static args = {
name: Args.string({
required: true,
description: 'environment or config variable name',
},
{
name: 'value',
}),
value: Args.string({
required: false,
description:
"variable value; if omitted, use value from this process' environment",
},
];
}),
};
// Required for supporting empty string ('') `value` args.
public static strict = false;
public static usage = 'env add <name> [value]';
public static flags: flags.Input<FlagsDef> = {
...(isV13()
? {}
: { application: { ...cf.application, exclusive: ['fleet', 'device'] } }),
fleet: { ...cf.fleet, exclusive: ['application', 'device'] },
device: { ...cf.device, exclusive: ['application', 'fleet'] },
public static flags = {
fleet: { ...cf.fleet, exclusive: ['device'] },
device: { ...cf.device, exclusive: ['fleet'] },
help: cf.help,
quiet: cf.quiet,
service: cf.service,
};
public async run() {
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
EnvAddCmd,
);
const { args: params, flags: options } = await this.parse(EnvAddCmd);
const cmd = this;
if (options.application && process.stderr.isTTY) {
console.error(warnify(appToFleetFlagMsg));
}
options.application ||= options.fleet;
if (!options.application && !options.device) {
if (!options.fleet && !options.device) {
throw new ExpectedError(
'Either the --fleet or the --device option must be specified',
);
@ -163,16 +148,16 @@ export default class EnvAddCmd extends Command {
}
const varType = isConfigVar ? 'configVar' : 'envVar';
if (options.application) {
for (const app of options.application.split(',')) {
if (options.fleet) {
for (const appSlug of await resolveFleetSlugs(balena, options.fleet)) {
try {
await balena.models.application[varType].set(
app,
appSlug,
params.name,
params.value,
);
} catch (err) {
console.error(`${err.message}, fleet: ${app}`);
console.error(`${err.message}, fleet: ${appSlug}`);
process.exitCode = 1;
}
}
@ -193,6 +178,25 @@ export default class EnvAddCmd extends Command {
}
}
// TODO: Stop accepting application names in the next major
// and just drop this in favor of doing the .split(',') directly.
async function resolveFleetSlugs(
balena: BalenaSdk.BalenaSDK,
fleetOption: string,
) {
const fleetSlugs: string[] = [];
const { getFleetSlug } = await import('../../utils/sdk');
for (const appNameOrSlug of fleetOption.split(',')) {
try {
fleetSlugs.push(await getFleetSlug(balena, appNameOrSlug));
} catch (err) {
console.error(`${err.message}, fleet: ${appNameOrSlug}`);
process.exitCode = 1;
}
}
return fleetSlugs;
}
/**
* Add service variables for a device or fleet.
*/
@ -201,18 +205,18 @@ async function setServiceVars(
params: ArgsDef,
options: FlagsDef,
) {
if (options.application) {
for (const app of options.application.split(',')) {
if (options.fleet) {
for (const appSlug of await resolveFleetSlugs(sdk, options.fleet)) {
for (const service of options.service!.split(',')) {
try {
const serviceId = await getServiceIdForApp(sdk, app, service);
const serviceId = await getServiceIdForApp(sdk, appSlug, service);
await sdk.models.service.var.set(
serviceId,
params.name,
params.value!,
);
} catch (err) {
console.error(`${err.message}, fleet: ${app}`);
console.error(`${err.message}, fleet: ${appSlug}`);
process.exitCode = 1;
}
}
@ -257,11 +261,12 @@ async function setServiceVars(
*/
async function getServiceIdForApp(
sdk: BalenaSdk.BalenaSDK,
appName: string,
appSlug: string,
serviceName: string,
): Promise<number> {
let serviceId: number | undefined;
const services = await sdk.models.service.getAllByApplication(appName, {
const services = await sdk.models.service.getAllByApplication(appSlug, {
$select: 'id',
$filter: { service_name: serviceName },
});
if (services.length > 0) {
@ -269,7 +274,7 @@ async function getServiceIdForApp(
}
if (serviceId === undefined) {
throw new ExpectedError(
`Cannot find service ${serviceName} for fleet ${appName}`,
`Cannot find service ${serviceName} for fleet ${appSlug}`,
);
}
return serviceId;

View File

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { flags } from '@oclif/command';
import { Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
@ -22,20 +22,6 @@ import * as ec from '../../utils/env-common';
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import { parseAsInteger } from '../../utils/validation';
type IArg<T> = import('@oclif/parser').args.IArg<T>;
interface FlagsDef {
config: boolean;
device: boolean;
service: boolean;
help: void;
}
interface ArgsDef {
id: number;
value: string;
}
export default class EnvRenameCmd extends Command {
public static description = stripIndent`
Change the value of a config or env var for a fleet, device or service.
@ -54,24 +40,22 @@ export default class EnvRenameCmd extends Command {
'$ balena env rename 678678 1 --device --config',
];
public static args: Array<IArg<any>> = [
{
name: 'id',
public static args = {
id: Args.integer({
required: true,
description: "variable's numeric database ID",
parse: (input) => parseAsInteger(input, 'id'),
},
{
name: 'value',
parse: async (input) => parseAsInteger(input, 'id'),
}),
value: Args.string({
required: true,
description:
"variable value; if omitted, use value from this process' environment",
},
];
}),
};
public static usage = 'env rename <id> <value>';
public static flags: flags.Input<FlagsDef> = {
public static flags = {
config: ec.booleanConfig,
device: ec.booleanDevice,
service: ec.booleanService,
@ -79,9 +63,7 @@ export default class EnvRenameCmd extends Command {
};
public async run() {
const { args: params, flags: opt } = this.parse<FlagsDef, ArgsDef>(
EnvRenameCmd,
);
const { args: params, flags: opt } = await this.parse(EnvRenameCmd);
await Command.checkLoggedIn();

View File

@ -15,26 +15,13 @@
* limitations under the License.
*/
import { flags } from '@oclif/command';
import { Flags, Args } from '@oclif/core';
import Command from '../../command';
import * as ec from '../../utils/env-common';
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import { parseAsInteger } from '../../utils/validation';
type IArg<T> = import('@oclif/parser').args.IArg<T>;
interface FlagsDef {
config: boolean;
device: boolean;
service: boolean;
yes: boolean;
}
interface ArgsDef {
id: number;
}
export default class EnvRmCmd extends Command {
public static description = stripIndent`
Remove a config or env var from a fleet, device or service.
@ -57,22 +44,21 @@ export default class EnvRmCmd extends Command {
'$ balena env rm 789789 --device --service --yes',
];
public static args: Array<IArg<any>> = [
{
name: 'id',
public static args = {
id: Args.integer({
required: true,
description: "variable's numeric database ID",
parse: (input) => parseAsInteger(input, 'id'),
},
];
parse: async (input) => parseAsInteger(input, 'id'),
}),
};
public static usage = 'env rm <id>';
public static flags: flags.Input<FlagsDef> = {
public static flags = {
config: ec.booleanConfig,
device: ec.booleanDevice,
service: ec.booleanService,
yes: flags.boolean({
yes: Flags.boolean({
char: 'y',
description:
'do not prompt for confirmation before deleting the variable',
@ -81,9 +67,7 @@ export default class EnvRmCmd extends Command {
};
public async run() {
const { args: params, flags: opt } = this.parse<FlagsDef, ArgsDef>(
EnvRmCmd,
);
const { args: params, flags: opt } = await this.parse(EnvRmCmd);
await Command.checkLoggedIn();

View File

@ -14,49 +14,34 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { flags } from '@oclif/command';
import { Flags } from '@oclif/core';
import type { Interfaces } from '@oclif/core';
import type * as SDK from 'balena-sdk';
import * as _ from 'lodash';
import Command from '../command';
import { ExpectedError } from '../errors';
import * as cf from '../utils/common-flags';
import { getBalenaSdk, getVisuals, stripIndent } from '../utils/lazy';
import {
applicationIdInfo,
appToFleetFlagMsg,
appToFleetOutputMsg,
warnify,
} from '../utils/messages';
import { isV13 } from '../utils/version';
import Command from '../../command';
import { ExpectedError } from '../../errors';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
import { applicationIdInfo } from '../../utils/messages';
interface FlagsDef {
application?: string;
fleet?: string;
config: boolean;
device?: string; // device UUID
json: boolean;
help: void;
service?: string; // service name
verbose: boolean;
v13: boolean;
}
type FlagsDef = Interfaces.InferredFlags<typeof EnvsCmd.flags>;
interface EnvironmentVariableInfo extends SDK.EnvironmentVariableBase {
appName?: string | null; // application name
fleet?: string | null; // fleet slug
deviceUUID?: string; // device UUID
serviceName?: string; // service name
}
interface DeviceServiceEnvironmentVariableInfo
extends SDK.DeviceServiceEnvironmentVariable {
appName?: string; // application name
fleet?: string; // fleet slug
deviceUUID?: string; // device UUID
serviceName?: string; // service name
}
interface ServiceEnvironmentVariableInfo
extends SDK.ServiceEnvironmentVariable {
appName?: string; // application name
fleet?: string; // fleet slug
deviceUUID?: string; // device UUID
serviceName?: string; // service name
}
@ -96,15 +81,12 @@ export default class EnvsCmd extends Command {
in case the current user was removed from the fleet by the fleet's owner).
${applicationIdInfo.split('\n').join('\n\t\t')}
${appToFleetOutputMsg.split('\n').join('\n\t\t')}
`;
public static examples = [
'$ balena envs --fleet myorg/myfleet',
'$ balena envs --fleet MyFleet --json',
'$ balena envs --fleet MyFleet --service MyService',
'$ balena envs --fleet MyFleet --service MyService',
'$ balena envs --fleet MyFleet --config',
'$ balena envs --device 7cf02a6',
'$ balena envs --device 7cf02a6 --json',
@ -114,62 +96,44 @@ export default class EnvsCmd extends Command {
public static usage = 'envs';
public static flags: flags.Input<FlagsDef> = {
...(isV13()
? {}
: {
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({
public static flags = {
fleet: { ...cf.fleet, exclusive: ['device'] },
config: Flags.boolean({
default: false,
char: 'c',
description: 'show configuration variables only',
exclusive: ['service'],
}),
device: { exclusive: ['fleet', 'application'], ...cf.device },
device: { ...cf.device, exclusive: ['fleet'] },
help: cf.help,
json: cf.json,
verbose: cf.verbose,
service: { exclusive: ['config'], ...cf.service },
v13: cf.v13,
service: { ...cf.service, exclusive: ['config'] },
};
protected useAppWord = false;
protected hasWarned = false;
public async run() {
const { flags: options } = this.parse<FlagsDef, {}>(EnvsCmd);
this.useAppWord = !options.fleet && !options.v13 && !isV13();
const { flags: options } = await this.parse(EnvsCmd);
const variables: EnvironmentVariableInfo[] = [];
await Command.checkLoggedIn();
if (options.application && !options.json && process.stderr.isTTY) {
this.hasWarned = true;
console.error(warnify(appToFleetFlagMsg));
}
options.application ||= options.fleet;
if (!options.application && !options.device) {
if (!options.fleet && !options.device) {
throw new ExpectedError('Missing --fleet or --device option');
}
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
if (options.device) {
const { getDeviceAndMaybeAppFromUUID } = await import('../utils/cloud');
const { getDeviceAndMaybeAppFromUUID } = await import(
'../../utils/cloud'
);
const [device, app] = await getDeviceAndMaybeAppFromUUID(
balena,
options.device,
@ -178,23 +142,23 @@ export default class EnvsCmd extends Command {
);
fullUUID = device.uuid;
if (app) {
appNameOrSlug = app.slug;
fleetSlug = app.slug;
}
}
if (appNameOrSlug && options.service) {
await validateServiceName(balena, options.service, appNameOrSlug);
if (fleetSlug && options.service) {
await validateServiceName(balena, options.service, fleetSlug);
}
variables.push(...(await getAppVars(balena, appNameOrSlug, options)));
variables.push(...(await getAppVars(balena, fleetSlug, options)));
if (fullUUID) {
variables.push(
...(await getDeviceVars(balena, fullUUID, appNameOrSlug, options)),
...(await getDeviceVars(balena, fullUUID, fleetSlug, options)),
);
}
if (!options.json && variables.length === 0) {
const target =
(options.service ? `service "${options.service}" of ` : '') +
(options.application
? `fleet "${options.application}"`
(options.fleet
? `fleet "${options.fleet}"`
: `device "${options.device}"`);
throw new ExpectedError(`No environment variables found for ${target}`);
}
@ -206,24 +170,14 @@ export default class EnvsCmd extends Command {
varArray: EnvironmentVariableInfo[],
options: FlagsDef,
) {
const fields = ['id', 'name', 'value'];
const fields = ['id', 'name', 'value', 'fleet'];
// Replace undefined app names with 'N/A' or null
varArray = varArray.map((i: EnvironmentVariableInfo) => {
if (i.appName) {
// 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';
}
i.fleet ||= options.json ? null : 'N/A';
return i;
});
const jName = this.useAppWord ? 'appName' : 'fleetName';
const tName = this.useAppWord ? 'APPLICATION' : 'FLEET';
fields.push(options.json ? `appName => ${jName}` : `appName => ${tName}`);
if (options.device) {
fields.push(options.json ? 'deviceUUID' : 'deviceUUID => DEVICE');
}
@ -232,13 +186,10 @@ export default class EnvsCmd extends Command {
}
if (options.json) {
const { pickAndRename } = await import('../utils/helpers');
const { pickAndRename } = await import('../../utils/helpers');
const mapped = varArray.map((o) => pickAndRename(o, fields));
this.log(JSON.stringify(mapped, null, 4));
} else {
if (!this.hasWarned && this.useAppWord && process.stderr.isTTY) {
console.error(warnify(appToFleetOutputMsg));
}
this.log(
getVisuals().table.horizontal(
_.sortBy(varArray, (v: SDK.EnvironmentVariableBase) => v.name),
@ -252,14 +203,15 @@ export default class EnvsCmd extends Command {
async function validateServiceName(
sdk: SDK.BalenaSDK,
serviceName: string,
appName: string,
fleetSlug: string,
) {
const services = await sdk.models.service.getAllByApplication(appName, {
const services = await sdk.models.service.getAllByApplication(fleetSlug, {
$select: 'id',
$filter: { service_name: serviceName },
});
if (services.length === 0) {
throw new ExpectedError(
`Service "${serviceName}" not found for fleet "${appName}"`,
`Service "${serviceName}" not found for fleet "${fleetSlug}"`,
);
}
}
@ -273,17 +225,18 @@ async function validateServiceName(
*/
async function getAppVars(
sdk: SDK.BalenaSDK,
appNameOrSlug: string | undefined,
fleetSlug: string | undefined,
options: FlagsDef,
): Promise<EnvironmentVariableInfo[]> {
const appVars: EnvironmentVariableInfo[] = [];
if (!appNameOrSlug) {
if (!fleetSlug) {
return appVars;
}
const vars = await sdk.models.application[
options.config ? 'configVar' : 'envVar'
].getAllByApplication(appNameOrSlug);
fillInInfoFields(vars, appNameOrSlug);
const vars =
await sdk.models.application[
options.config ? 'configVar' : 'envVar'
].getAllByApplication(fleetSlug);
fillInInfoFields(vars, fleetSlug);
appVars.push(...vars);
if (!options.config) {
const pineOpts: SDK.PineOptions<SDK.ServiceEnvironmentVariable> = {
@ -299,10 +252,10 @@ async function getAppVars(
};
}
const serviceVars = await sdk.models.service.var.getAllByApplication(
appNameOrSlug,
fleetSlug,
pineOpts,
);
fillInInfoFields(serviceVars, appNameOrSlug);
fillInInfoFields(serviceVars, fleetSlug);
appVars.push(...serviceVars);
}
return appVars;
@ -315,16 +268,15 @@ async function getAppVars(
async function getDeviceVars(
sdk: SDK.BalenaSDK,
fullUUID: string,
appNameOrSlug: string | undefined,
fleetSlug: string | undefined,
options: FlagsDef,
): Promise<EnvironmentVariableInfo[]> {
const printedUUID = options.json ? fullUUID : options.device!;
const deviceVars: EnvironmentVariableInfo[] = [];
if (options.config) {
const deviceConfigVars = await sdk.models.device.configVar.getAllByDevice(
fullUUID,
);
fillInInfoFields(deviceConfigVars, appNameOrSlug, printedUUID);
const deviceConfigVars =
await sdk.models.device.configVar.getAllByDevice(fullUUID);
fillInInfoFields(deviceConfigVars, fleetSlug, printedUUID);
deviceVars.push(...deviceConfigVars);
} else {
const pineOpts: SDK.PineOptions<SDK.DeviceServiceEnvironmentVariable> = {
@ -345,13 +297,12 @@ async function getDeviceVars(
fullUUID,
pineOpts,
);
fillInInfoFields(deviceServiceVars, appNameOrSlug, printedUUID);
fillInInfoFields(deviceServiceVars, fleetSlug, printedUUID);
deviceVars.push(...deviceServiceVars);
const deviceEnvVars = await sdk.models.device.envVar.getAllByDevice(
fullUUID,
);
fillInInfoFields(deviceEnvVars, appNameOrSlug, printedUUID);
const deviceEnvVars =
await sdk.models.device.envVar.getAllByDevice(fullUUID);
fillInInfoFields(deviceEnvVars, fleetSlug, printedUUID);
deviceVars.push(...deviceEnvVars);
}
return deviceVars;
@ -367,7 +318,7 @@ function fillInInfoFields(
| EnvironmentVariableInfo[]
| DeviceServiceEnvironmentVariableInfo[]
| ServiceEnvironmentVariableInfo[],
appNameOrSlug?: string,
fleetSlug?: string,
deviceUUID?: string,
) {
for (const envVar of varArray) {
@ -381,7 +332,7 @@ function fillInInfoFields(
?.installs__service as SDK.Service[]
)[0]?.service_name;
}
envVar.appName = appNameOrSlug;
envVar.fleet = fleetSlug;
envVar.serviceName = envVar.serviceName || '*';
envVar.deviceUUID = deviceUUID || '*';
}

View File

@ -1,6 +1,6 @@
/**
* @license
* Copyright 2021 Balena Ltd.
* 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.
@ -15,6 +15,69 @@
* limitations under the License.
*/
import { FleetCreateCmd } from '../app/create';
import { Flags, Args } from '@oclif/core';
export default FleetCreateCmd;
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { stripIndent } from '../../utils/lazy';
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: Args.string({
description: 'fleet name',
required: true,
}),
};
public static usage = 'fleet create <name>';
public static flags = {
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 } = await this.parse(FleetCreateCmd);
await (
await import('../../utils/application-create')
).applicationCreateBase('fleet', options, params);
}
}

View File

@ -1,6 +1,6 @@
/**
* @license
* Copyright 2021 Balena Ltd.
* 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.
@ -15,6 +15,79 @@
* limitations under the License.
*/
import { FleetCmd } from '../app';
import { Flags } from '@oclif/core';
export default FleetCmd;
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';
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',
'$ balena fleet myorg/myfleet --view',
];
public static args = {
fleet: ca.fleetRequired,
};
public static usage = 'fleet <fleet>';
public static flags = {
help: cf.help,
view: Flags.boolean({
default: false,
description: 'open fleet dashboard page',
}),
...cf.dataOutputFlags,
};
public static authenticated = true;
public static primary = true;
public async run() {
const { args: params, flags: options } = await this.parse(FleetCmd);
const { getApplication } = await import('../../utils/sdk');
const balena = getBalenaSdk();
const application = await getApplication(balena, params.fleet, {
$expand: {
is_for__device_type: { $select: 'slug' },
should_be_running__release: { $select: 'commit' },
},
});
if (options.view) {
const open = await import('open');
const dashboardUrl = balena.models.application.getDashboardUrl(
application.id,
);
await open(dashboardUrl, { wait: false });
return;
}
const outputApplication = {
...application,
device_type: application.is_for__device_type[0].slug,
commit: application.should_be_running__release[0]?.commit,
};
await this.outputData(
outputApplication,
['app_name', 'id', 'device_type', 'slug', 'commit'],
options,
);
}
}

88
lib/commands/fleet/pin.ts Normal file
View File

@ -0,0 +1,88 @@
/**
* @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 { Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import { getExpandedProp } from '../../utils/pine';
export default class FleetPinCmd extends Command {
public static description = stripIndent`
Pin a fleet to a release.
Pin a fleet to a release.
Note, if the commit is omitted, the currently pinned release will be printed, with instructions for how to see a list of releases
`;
public static examples = [
'$ balena fleet pin myfleet',
'$ balena fleet pin myorg/myfleet 91165e5',
];
public static args = {
slug: Args.string({
description: 'the slug of the fleet to pin to a release',
required: true,
}),
releaseToPinTo: Args.string({
description: 'the commit of the release for the fleet to get pinned to',
}),
};
public static usage = 'fleet pin <slug> [releaseToPinTo]';
public static flags = {
help: cf.help,
};
public static authenticated = true;
public async run() {
const { args: params } = await this.parse(FleetPinCmd);
const balena = getBalenaSdk();
const fleet = await balena.models.application.get(params.slug, {
$expand: {
should_be_running__release: {
$select: 'commit',
},
},
});
const pinnedRelease = getExpandedProp(
fleet.should_be_running__release,
'commit',
);
const releaseToPinTo = params.releaseToPinTo;
const slug = params.slug;
if (!releaseToPinTo) {
console.log(
`${
pinnedRelease
? `This fleet is currently pinned to ${pinnedRelease}.`
: 'This fleet is not currently pinned to any release.'
} \n\nTo see a list of all releases this fleet can be pinned to, run \`balena releases ${slug}\`.`,
);
} else {
await balena.models.application.pinToRelease(slug, releaseToPinTo);
}
}
}

View File

@ -1,6 +1,6 @@
/**
* @license
* Copyright 2021 Balena Ltd.
* 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.
@ -15,6 +15,61 @@
* limitations under the License.
*/
import { FleetPurgeCmd } from '../app/purge';
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';
export default FleetPurgeCmd;
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 = {
fleet: ca.fleetRequired,
};
public static usage = 'fleet purge <fleet>';
public static flags = {
help: cf.help,
};
public static authenticated = true;
public async run() {
const { args: params } = await this.parse(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, {
$select: 'id',
});
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;
}
}
}
}

View File

@ -1,6 +1,6 @@
/**
* @license
* Copyright 2021 Balena Ltd.
* 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.
@ -15,6 +15,125 @@
* limitations under the License.
*/
import { FleetRenameCmd } from '../app/rename';
import { Args } from '@oclif/core';
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';
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 = {
fleet: ca.fleetRequired,
newName: Args.string({
description: 'the new name for the fleet',
}),
};
public static usage = 'fleet rename <fleet> [newName]';
public static flags = {
help: cf.help,
};
public static authenticated = true;
public async run() {
const { args: params } = await this.parse(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, {
$select: ['id', 'app_name', 'slug'],
$expand: {
application_type: {
$select: 'slug',
},
},
});
// Check app exists
if (!application) {
throw new ExpectedError(`Error: fleet ${params.fleet} not found.`);
}
// Check app supports renaming
const appType = application.application_type[0];
if (appType.slug === 'legacy-v1' || appType.slug === 'legacy-v2') {
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 getApplication(balena, application.id, {
$select: ['app_name', 'slug'],
});
// 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}`);
}
}

View File

@ -1,6 +1,6 @@
/**
* @license
* Copyright 2021 Balena Ltd.
* 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.
@ -15,6 +15,50 @@
* limitations under the License.
*/
import { FleetRestartCmd } from '../app/restart';
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';
export default FleetRestartCmd;
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 = {
fleet: ca.fleetRequired,
};
public static usage = 'fleet restart <fleet>';
public static flags = {
help: cf.help,
};
public static authenticated = true;
public async run() {
const { args: params } = await this.parse(FleetRestartCmd);
const { getApplication } = await import('../../utils/sdk');
const balena = getBalenaSdk();
// Disambiguate application
const application = await getApplication(balena, params.fleet, {
$select: 'slug',
});
await balena.models.application.restart(application.slug);
}
}

View File

@ -1,6 +1,6 @@
/**
* @license
* Copyright 2021 Balena Ltd.
* 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.
@ -15,6 +15,61 @@
* limitations under the License.
*/
import { FleetRmCmd } from '../app/rm';
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';
export default FleetRmCmd;
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 = {
fleet: ca.fleetRequired,
};
public static usage = 'fleet rm <fleet>';
public static flags = {
yes: cf.yes,
help: cf.help,
};
public static authenticated = true;
public async run() {
const { args: params, flags: options } = await this.parse(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, {
$select: 'slug',
});
// Remove
await balena.models.application.remove(application.slug);
}
}

View File

@ -0,0 +1,56 @@
/**
* @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 { Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
export default class FleetTrackLatestCmd extends Command {
public static description = stripIndent`
Make this fleet track the latest release.
Make this fleet track the latest release.
`;
public static examples = [
'$ balena fleet track-latest myorg/myfleet',
'$ balena fleet track-latest myfleet',
];
public static args = {
slug: Args.string({
description: 'the slug of the fleet to make track the latest release',
required: true,
}),
};
public static usage = 'fleet track-latest <slug>';
public static flags = {
help: cf.help,
};
public static authenticated = true;
public async run() {
const { args: params } = await this.parse(FleetTrackLatestCmd);
const balena = getBalenaSdk();
await balena.models.application.trackLatestRelease(params.slug);
}
}

View File

@ -0,0 +1,93 @@
/**
* @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 * as BalenaSdk from 'balena-sdk';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
interface ExtendedApplication extends ApplicationWithDeviceTypeSlug {
device_count: number;
online_devices: number;
device_type?: string;
}
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 = {
...cf.dataSetOutputFlags,
help: cf.help,
};
public static authenticated = true;
public static primary = true;
public async run() {
const { flags: options } = await this.parse(FleetsCmd);
const balena = getBalenaSdk();
const pineOptions = {
$select: ['id', 'app_name', 'slug'],
$expand: {
is_for__device_type: { $select: 'slug' },
owns__device: { $select: 'is_online' },
},
} satisfies BalenaSdk.PineOptions<BalenaSdk.Application>;
// Get applications
const applications =
(await balena.models.application.getAllDirectlyAccessible(
pineOptions,
)) as Array<
BalenaSdk.PineTypedResult<BalenaSdk.Application, typeof pineOptions>
> 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;
});
await this.outputData(
applications,
[
'id',
'app_name',
'slug',
'device_type',
'device_count',
'online_devices',
],
options,
);
}
}

View File

@ -15,6 +15,7 @@
* limitations under the License.
*/
import { Args } from '@oclif/core';
import Command from '../../command';
import { stripIndent } from '../../utils/lazy';
import { CommandHelp } from '../../utils/oclif-utils';
@ -27,12 +28,6 @@ import { CommandHelp } from '../../utils/oclif-utils';
// - https://github.com/balena-io/balena-cli/pull/1455#discussion_r334308357
// - https://github.com/balena-io/balena-cli/pull/1455#discussion_r334308526
interface ArgsDef {
image: string;
type: string;
config: string;
}
export default class OsinitCmd extends Command {
public static description = stripIndent`
Do actual init of the device with the preconfigured os image.
@ -41,20 +36,17 @@ export default class OsinitCmd extends Command {
Use \`balena os initialize <image>\` instead.
`;
public static args = [
{
name: 'image',
public static args = {
image: Args.string({
required: true,
},
{
name: 'type',
}),
type: Args.string({
required: true,
},
{
name: 'config',
}),
config: Args.string({
required: true,
},
];
}),
};
public static usage = (
'internal osinit ' +
@ -63,9 +55,10 @@ export default class OsinitCmd extends Command {
public static hidden = true;
public static root = true;
public static offlineCompatible = true;
public async run() {
const { args: params } = this.parse<{}, ArgsDef>(OsinitCmd);
const { args: params } = await this.parse(OsinitCmd);
const config = JSON.parse(params.config);

View File

@ -15,24 +15,12 @@
* limitations under the License.
*/
import { flags } from '@oclif/command';
import Command from '../command';
import * as cf from '../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../utils/lazy';
import { applicationIdInfo } from '../utils/messages';
import { parseAsLocalHostnameOrIp } from '../utils/validation';
import { isV13 } from '../utils/version';
interface FlagsDef {
application?: string;
fleet?: string;
pollInterval?: number;
help?: void;
}
interface ArgsDef {
deviceIpOrHostname?: string;
}
import { Args, Flags } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import { applicationIdInfo } from '../../utils/messages';
import { parseAsLocalHostnameOrIp } from '../../utils/validation';
export default class JoinCmd extends Command {
public static description = stripIndent`
@ -65,21 +53,19 @@ export default class JoinCmd extends Command {
'$ balena join 192.168.1.25 --fleet MyFleet',
];
public static args = [
{
name: 'deviceIpOrHostname',
public static args = {
deviceIpOrHostname: Args.string({
description: 'the IP or hostname of device',
parse: parseAsLocalHostnameOrIp,
},
];
}),
};
// Hardcoded to preserve camelcase
public static usage = 'join [deviceIpOrHostname]';
public static flags: flags.Input<FlagsDef> = {
...(isV13() ? {} : { application: cf.application }),
public static flags = {
fleet: cf.fleet,
pollInterval: flags.integer({
pollInterval: Flags.integer({
description: 'the interval in minutes to check for updates',
char: 'i',
}),
@ -90,18 +76,16 @@ export default class JoinCmd extends Command {
public static primary = true;
public async run() {
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
JoinCmd,
);
const { args: params, flags: options } = await this.parse(JoinCmd);
const promote = await import('../utils/promote');
const promote = await import('../../utils/promote');
const sdk = getBalenaSdk();
const logger = await Command.getLogger();
return promote.join(
logger,
sdk,
params.deviceIpOrHostname,
options.application || options.fleet,
options.fleet,
options.pollInterval,
);
}

View File

@ -15,21 +15,12 @@
* limitations under the License.
*/
import { flags } from '@oclif/command';
import { Args } from '@oclif/core';
import Command from '../../command';
import { ExpectedError } from '../../errors';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
interface FlagsDef {
help: void;
}
interface ArgsDef {
name: string;
path: string;
}
export default class KeyAddCmd extends Command {
public static description = stripIndent`
Add an SSH key to balenaCloud.
@ -60,21 +51,19 @@ export default class KeyAddCmd extends Command {
'$ balena key add Main %userprofile%.sshid_rsa.pub',
];
public static args = [
{
name: 'name',
public static args = {
name: Args.string({
description: 'the SSH key name',
required: true,
},
{
name: `path`,
}),
path: Args.string({
description: `the path to the public key file`,
},
];
}),
};
public static usage = 'key add <name> [path]';
public static flags: flags.Input<FlagsDef> = {
public static flags = {
help: cf.help,
};
@ -83,7 +72,7 @@ export default class KeyAddCmd extends Command {
public static readStdin = true;
public async run() {
const { args: params } = this.parse<FlagsDef, ArgsDef>(KeyAddCmd);
const { args: params } = await this.parse(KeyAddCmd);
let key: string;
if (params.path != null) {

View File

@ -15,22 +15,12 @@
* limitations under the License.
*/
import { flags } from '@oclif/command';
import { Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
import { parseAsInteger } from '../../utils/validation';
type IArg<T> = import('@oclif/parser').args.IArg<T>;
interface FlagsDef {
help: void;
}
interface ArgsDef {
id: number;
}
export default class KeyCmd extends Command {
public static description = stripIndent`
Display an SSH key.
@ -40,25 +30,24 @@ export default class KeyCmd extends Command {
public static examples = ['$ balena key 17'];
public static args: Array<IArg<any>> = [
{
name: 'id',
public static args = {
id: Args.integer({
description: 'balenaCloud ID for the SSH key',
parse: (x) => parseAsInteger(x, 'id'),
parse: async (x) => parseAsInteger(x, 'id'),
required: true,
},
];
}),
};
public static usage = 'key <id>';
public static flags: flags.Input<FlagsDef> = {
public static flags = {
help: cf.help,
};
public static authenticated = true;
public async run() {
const { args: params } = this.parse<{}, ArgsDef>(KeyCmd);
const { args: params } = await this.parse(KeyCmd);
const key = await getBalenaSdk().models.key.get(params.id);

View File

@ -15,23 +15,12 @@
* limitations under the License.
*/
import { flags } from '@oclif/command';
import { Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import { parseAsInteger } from '../../utils/validation';
type IArg<T> = import('@oclif/parser').args.IArg<T>;
interface FlagsDef {
yes: boolean;
help: void;
}
interface ArgsDef {
id: number;
}
export default class KeyRmCmd extends Command {
public static description = stripIndent`
Remove an SSH key from balenaCloud.
@ -43,18 +32,17 @@ export default class KeyRmCmd extends Command {
public static examples = ['$ balena key rm 17', '$ balena key rm 17 --yes'];
public static args: Array<IArg<any>> = [
{
name: 'id',
public static args = {
id: Args.integer({
description: 'balenaCloud ID for the SSH key',
parse: (x) => parseAsInteger(x, 'id'),
parse: async (x) => parseAsInteger(x, 'id'),
required: true,
},
];
}),
};
public static usage = 'key rm <id>';
public static flags: flags.Input<FlagsDef> = {
public static flags = {
yes: cf.yes,
help: cf.help,
};
@ -62,9 +50,7 @@ export default class KeyRmCmd extends Command {
public static authenticated = true;
public async run() {
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
KeyRmCmd,
);
const { args: params, flags: options } = await this.parse(KeyRmCmd);
const patterns = await import('../../utils/patterns');

Some files were not shown because too many files have changed in this diff Show More