Compare commits

...

76 Commits

Author SHA1 Message Date
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
66 changed files with 2216 additions and 1015 deletions

2
.gitattributes vendored
View File

@ -6,7 +6,7 @@
*.sh text 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

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

@ -5,13 +5,11 @@ npm:
os: ubuntu
architecture: x86_64
node_versions:
- "10"
- "12"
- "14"
- name: linux
os: alpine
architecture: x86_64
node_versions:
- "10"
- "12"
- "14"

View File

@ -1,3 +1,454 @@
- commits:
- subject: Drop unnecessary directory list created during balena deploy & push
hash: f2858801350077305a92a6ba32659a46dd3e1e39
body: ''
footer:
Change-type: patch
change-type: patch
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
signed-off-by: Thodoris Greasidis <thodoris@balena.io>
author: Thodoris Greasidis
nested: []
version: 12.55.5
date: 2021-12-13T23:29:59.103Z
- commits:
- subject: 'os download, os versions: Accept device type aliases'
hash: bfc995e9486024628f3599f52b9c0fb13d815311
body: ''
footer:
Change-type: patch
change-type: patch
author: Paulo Castro
nested: []
version: 12.55.4
date: 2021-12-10T00:56:34.562Z
- commits:
- subject: >-
os download: Improve error message when not logged in (balenaOS ESR
versions)
hash: 6e07db08131a59622d06459ecb62b47adeb52826
body: ''
footer:
Change-type: patch
change-type: patch
author: Paulo Castro
nested: []
- subject: 'os download: Assume ''.prod'' suffix by default for all balenaOS versions'
hash: 5c40c8d51f16db9f9e28e4286f4c2c277a993f37
body: ''
footer:
Resolves: '#2387'
resolves: '#2387'
Change-type: patch
change-type: patch
author: Paulo Castro
nested: []
version: 12.55.3
date: 2021-12-09T18:51:23.706Z
- commits:
- subject: 'v13 preparations: Standardize command data output'
hash: f3fb9b6bdffa1bf65c90f8061bf83663a5ad7b85
body: ''
footer:
Change-type: patch
change-type: patch
Signed-off-by: Scott Lowe <scott@balena.io>
signed-off-by: Scott Lowe <scott@balena.io>
author: Scott Lowe
nested: []
version: 12.55.2
date: 2021-12-08T11:22:59.156Z
- commits:
- subject: 'chore: Bump multicast-dns to rebased commit'
hash: fdc0d08e963daf9bccdf8150962c365ff4ab32e3
body: >
Otherwise npm install fails due to the missing commit in
npm-shrinkwrap.json
footer:
Change-type: patch
change-type: patch
Signed-off-by: Kyle Harding <kyle@balena.io>
signed-off-by: Kyle Harding <kyle@balena.io>
See: 'https://github.com/balena-io-modules/multicast-dns/pull/1'
see: 'https://github.com/balena-io-modules/multicast-dns/pull/1'
author: Kyle Harding
nested: []
version: 12.55.1
date: 2021-11-30T16:10:20.410Z
- commits:
- subject: Add provisioning key name option to config generate options
hash: 6ba67eefdbb47e1aa901be022b497e028260ac0b
body: ''
footer:
Change-Type: minor
change-type: minor
Signed-off-by: Nitish Agarwal 1592163+nitishagar@users.noreply.github.com
signed-off-by: Nitish Agarwal 1592163+nitishagar@users.noreply.github.com
author: Nitish Agarwal
nested: []
version: 12.55.0
date: 2021-11-29T10:48:16.077Z
- commits:
- subject: 'os configure, local configure: Reuse disk partition scanning logic'
hash: fcea91bfb6cbcc32117d7242460f1e58476339ac
body: ''
footer:
Change-type: patch
change-type: patch
author: Paulo Castro
nested: []
version: 12.54.5
date: 2021-11-27T01:19:18.487Z
- commits:
- subject: Bump 'balena-lint' and fix 'no-floating-promises' warnings
hash: 09d004423cae2bb699aa344e17164ce392e1108b
body: ''
footer:
Change-type: patch
change-type: patch
author: Paulo Castro
nested: []
version: 12.54.4
date: 2021-11-26T16:03:03.934Z
- commits:
- subject: Improve directory scan speed prior to tarballing
hash: 257dd514ed7c0f6988b8a47219991cc4f61b4529
body: >
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.
footer:
Change-type: patch
change-type: patch
Signed-off-by: Lucian Buzzo <lucian.buzzo@gmail.com>
signed-off-by: Lucian Buzzo <lucian.buzzo@gmail.com>
author: Lucian Buzzo
nested: []
version: 12.54.3
date: 2021-11-26T13:59:56.032Z
- commits:
- subject: Set the correct target state when using Compose "image" field
hash: d2a5a9ba8638e712a4b286ce6775158c89615b08
body: >
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.
footer:
Change-type: patch
change-type: patch
Signed-off-by: Lucian Buzzo <lucian.buzzo@gmail.com>
signed-off-by: Lucian Buzzo <lucian.buzzo@gmail.com>
author: Lucian Buzzo
nested: []
version: 12.54.2
date: 2021-11-26T10:14:56.289Z
- commits:
- subject: Fix mistake in `config generate` examples
hash: b183d884007365d1240fd825e65ed8d95b7ce34e
body: ''
footer:
Change-type: patch
change-type: patch
Signed-off-by: Scott Lowe <scott@balena.io>
signed-off-by: Scott Lowe <scott@balena.io>
author: Scott Lowe
nested: []
version: 12.54.1
date: 2021-11-25T22:35:08.652Z
- commits:
- subject: Improve UX for offline usage
hash: 2b6a2142eb1929694e0eb14cf26bac4ae9b6dca1
body: ''
footer:
Change-type: minor
change-type: minor
Resolves: '#2372'
resolves: '#2372'
Signed-off-by: Scott Lowe <scott@balena.io>
signed-off-by: Scott Lowe <scott@balena.io>
author: Scott Lowe
nested: []
version: 12.54.0
date: 2021-11-25T20:45:24.329Z
- commits:
- subject: >-
Avoid creating an extra provisioning API key in os configure & config
generate
hash: 58b29bf4bbd96a57804b7be86aaac1d3cd8cc473
body: ''
footer:
Change-type: patch
change-type: patch
Changelog-entry: >-
Avoid creating an extra provisioning API key in os configure & config
generate
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
see: >-
https://github.com/balena-io/balena-cli/pull/2294#discussion_r756499196
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
signed-off-by: Thodoris Greasidis <thodoris@balena.io>
author: Thodoris Greasidis
nested: []
version: 12.53.2
date: 2021-11-25T14:45:34.458Z
- commits:
- subject: Transitional changes to doc files for landr implementation
hash: 5a9b5e3b080eccded0aa7d5dfe9276df9637ef83
body: ''
footer:
Change-type: patch
change-type: patch
Signed-off-by: Scott Lowe <scott@balena.io>
signed-off-by: Scott Lowe <scott@balena.io>
author: Scott Lowe
nested: []
version: 12.53.1
date: 2021-11-25T10:07:50.877Z
- commits:
- subject: 'config read/write/inject: Avoid need for internet access'
hash: b546e4dd97caf4a3da4c3eea140bc201fb6e59ef
body: ''
footer:
Change-type: minor
change-type: minor
author: Paulo Castro
nested: []
- subject: 'config read: Add ''--json'' option for JSON output'
hash: e4870916e233e1540bc4b25d5bde13b8e4e3bed7
body: ''
footer:
Change-type: minor
change-type: minor
author: Paulo Castro
nested: []
version: 12.53.0
date: 2021-11-25T02:11:13.469Z
- commits:
- subject: Delete 'doc/automated-init.md' and improve 'balena help device init'
hash: 952d782e905c733f7d98fc4524850d399adbefdc
body: ''
footer:
Change-type: patch
change-type: patch
author: Paulo Castro
nested: []
version: 12.52.2
date: 2021-11-24T18:31:20.275Z
- commits:
- subject: 'push/build: Add test cases for .dockerignore filtering corner cases'
hash: d64b6deb81d32fe16c79d97ab5a4699512b74387
body: ''
footer:
Change-type: patch
change-type: patch
author: Paulo Castro
nested: []
version: 12.52.1
date: 2021-11-22T01:53:27.126Z
- commits:
- subject: >-
os download: Display OS version actually downloaded (range or
'recommended')
hash: f46452f6de7bbfa00e073986d47a85608e5a9fea
body: ''
footer:
Change-type: patch
change-type: patch
author: Paulo Castro
nested: []
- subject: 'os versions, os download: Add support for balenaOS ESR versions'
hash: c166ec75979fcaa8bd123a1bf84f379279050fd4
body: ''
footer:
Change-type: minor
change-type: minor
author: Paulo Castro
nested: []
version: 12.52.0
date: 2021-11-20T00:51:09.369Z
- commits:
- subject: 'deploy: Ensure the release fails if an image''s digest (hash) is missing'
hash: 23b07f8a41c82c0b23a38bac55cbd3874300dd58
body: ''
footer:
Change-type: patch
change-type: patch
author: Paulo Castro
nested: []
version: 12.51.3
date: 2021-11-16T11:58:31.895Z
- commits:
- subject: Update balena CI configuration (remove Node v10 from npm pipeline list)
hash: ae5ea0f4e85008624ff46fe10998fadbd8a48b15
body: ''
footer:
Change-type: patch
change-type: patch
author: Paulo Castro
nested: []
version: 12.51.2
date: 2021-11-16T00:07:56.053Z
- commits:
- subject: Fix forums support link in README.md
hash: 112a7b8194d098e9c66754eb256590e13f54fe29
body: ''
footer:
Change-type: patch
change-type: patch
Signed-off-by: Scott Lowe <scott@balena.io>
signed-off-by: Scott Lowe <scott@balena.io>
author: Scott Lowe
nested: []
version: 12.51.1
date: 2021-10-25T17:29:36.975Z
- commits:
- subject: Add support for YAML anchors and aliases in 'docker-compose.yml'
hash: 8e712ac91055c4efde885854488000a27c6b483d
body: >
This allows project files to define services from generic fragments by
leveraging YAML's anchors and aliases. See here for an example:
https://github.com/compose-spec/compose-spec/blob/43f6537b2c8f01b6d3f0e184d13a0f3cb93d38d7/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-
footer:
Change-type: minor
change-type: minor
author: dfunckt
nested: []
version: 12.51.0
date: 2021-10-22T16:50:50.004Z
- commits:
- subject: 'preload: Avoid possible ValueError when parsing storage driver'
hash: 48053ecefc00e451f3ee5c9b82b2b398978ec229
body: |
Update balena-preload from 10.5.0 to 11.0.0
footer:
Change-type: patch
change-type: patch
Signed-off-by: Kyle Harding <kyle@balena.io>
signed-off-by: Kyle Harding <kyle@balena.io>
author: Kyle Harding
nested:
- commits:
- subject: Avoid creating multiple preload containers
hash: 6b5b6428833ce2cd5c53c2051d6f515f1b3e4c37
body: |
This was only caught when we started correctly naming
the preload container by switching from `Name` to `name` in
our createContainer options.
Previously we were creating two containers but they had unique
random names so we never saw the conflict.
footer:
Change-type: patch
change-type: patch
Signed-off-by: Kyle Harding <kyle@balena.io>
signed-off-by: Kyle Harding <kyle@balena.io>
author: Kyle Harding
nested: []
- subject: 'major: Remove balena-preload script in favor of use with CLI'
hash: 7de06434155913b199024683e62c85e94f1d1cb1
body: ''
footer:
Signed-off-by: >-
Lorenzo Alberto Maria Ambrosi
<lorenzothunder.ambrosi@gmail.com>
signed-off-by: >-
Lorenzo Alberto Maria Ambrosi
<lorenzothunder.ambrosi@gmail.com>
author: Lorenzo Alberto Maria Ambrosi
nested: []
- subject: Fix missing 'await' for getEdisonPartitions()
hash: 9a2ebfdb2a5375304ee5cf2ba6321b93f93886ed
body: ''
footer:
Change-type: patch
change-type: patch
author: Paulo Castro
nested: []
- subject: Add extra type information (refactor bind mount array)
hash: 5b8d21e68d9da902f7304a210cb1abe22420d1d0
body: ''
footer:
Change-type: patch
change-type: patch
author: Paulo Castro
nested: []
- subject: Run linter
hash: 456c727e6518317588b31e28ffb734377fb85e76
body: ''
footer:
Change-type: patch
change-type: patch
author: Paulo Castro
nested: []
- subject: 'major: Convert to typescript'
hash: ce241be0a780bdff87f4513e4d5a0ec63d72ac7e
body: ''
footer:
Signed-off-by: >-
Lorenzo Alberto Maria Ambrosi
<lorenzothunder.ambrosi@gmail.com>
signed-off-by: >-
Lorenzo Alberto Maria Ambrosi
<lorenzothunder.ambrosi@gmail.com>
author: Lorenzo Alberto Maria Ambrosi
nested: []
- subject: 'patch: Fix incorrect python List index check'
hash: 85d404000ac6e9b4ac83ce1feed33789deca182b
body: ''
footer:
Signed-off-by: >-
Lorenzo Alberto Maria Ambrosi
<lorenzothunder.ambrosi@gmail.com>
signed-off-by: >-
Lorenzo Alberto Maria Ambrosi
<lorenzothunder.ambrosi@gmail.com>
author: Lorenzo Alberto Maria Ambrosi
nested: []
version: balena-preload-11.0.0
date: 2021-10-13T18:20:43.867Z
version: 12.50.3
date: 2021-10-20T15:30:14.694Z
- commits:
- subject: Error message when renaming a fleet now mentions the target name.
hash: c493c33e3896784ee60c9d4ac79721ca6b96a778
body: ''
footer:
Change-type: patch
change-type: patch
Signed-off-by: Carlo Miguel F. Cruz <carloc@balena.io>
signed-off-by: Carlo Miguel F. Cruz <carloc@balena.io>
author: Carlo Miguel F. Cruz
nested: []
version: 12.50.2
date: 2021-10-05T09:04:40.995Z
- commits:
- subject: Update dependencies (@sentry/node error reporting)
hash: 08dfc945f3ed1c518a7d1830a5a37d72fd5739fd

View File

@ -4,6 +4,119 @@ All notable changes to this project will be documented in this file
automatically by Versionist. DO NOT EDIT THIS FILE MANUALLY!
This project adheres to [Semantic Versioning](http://semver.org/).
## 12.55.5 - 2021-12-13
* Drop unnecessary directory list created during balena deploy & push [Thodoris Greasidis]
## 12.55.4 - 2021-12-10
* os download, os versions: Accept device type aliases [Paulo Castro]
## 12.55.3 - 2021-12-09
* os download: Improve error message when not logged in (balenaOS ESR versions) [Paulo Castro]
* os download: Assume '.prod' suffix by default for all balenaOS versions [Paulo Castro]
## 12.55.2 - 2021-12-08
* v13 preparations: Standardize command data output [Scott Lowe]
## 12.55.1 - 2021-11-30
* chore: Bump multicast-dns to rebased commit [Kyle Harding]
## 12.55.0 - 2021-11-29
* Add provisioning key name option to config generate options [Nitish Agarwal]
## 12.54.5 - 2021-11-27
* os configure, local configure: Reuse disk partition scanning logic [Paulo Castro]
## 12.54.4 - 2021-11-26
* Bump 'balena-lint' and fix 'no-floating-promises' warnings [Paulo Castro]
## 12.54.3 - 2021-11-26
* Improve directory scan speed prior to tarballing [Lucian Buzzo]
## 12.54.2 - 2021-11-26
* Set the correct target state when using Compose "image" field [Lucian Buzzo]
## 12.54.1 - 2021-11-25
* Fix mistake in `config generate` examples [Scott Lowe]
## 12.54.0 - 2021-11-25
* Improve UX for offline usage [Scott Lowe]
## 12.53.2 - 2021-11-25
* Avoid creating an extra provisioning API key in os configure & config generate [Thodoris Greasidis]
## 12.53.1 - 2021-11-25
* Transitional changes to doc files for landr implementation [Scott Lowe]
## 12.53.0 - 2021-11-25
* config read/write/inject: Avoid need for internet access [Paulo Castro]
* config read: Add '--json' option for JSON output [Paulo Castro]
## 12.52.2 - 2021-11-24
* Delete 'doc/automated-init.md' and improve 'balena help device init' [Paulo Castro]
## 12.52.1 - 2021-11-22
* push/build: Add test cases for .dockerignore filtering corner cases [Paulo Castro]
## 12.52.0 - 2021-11-20
* os download: Display OS version actually downloaded (range or 'recommended') [Paulo Castro]
* os versions, os download: Add support for balenaOS ESR versions [Paulo Castro]
## 12.51.3 - 2021-11-16
* deploy: Ensure the release fails if an image's digest (hash) is missing [Paulo Castro]
## 12.51.2 - 2021-11-16
* Update balena CI configuration (remove Node v10 from npm pipeline list) [Paulo Castro]
## 12.51.1 - 2021-10-25
* Fix forums support link in README.md [Scott Lowe]
## 12.51.0 - 2021-10-22
* Add support for YAML anchors and aliases in 'docker-compose.yml' [dfunckt]
## 12.50.3 - 2021-10-20
<details>
<summary> preload: Avoid possible ValueError when parsing storage driver [Kyle Harding] </summary>
> ### balena-preload-11.0.0 - 2021-10-13
>
> * Avoid creating multiple preload containers [Kyle Harding]
> * major: Remove balena-preload script in favor of use with CLI [Lorenzo Alberto Maria Ambrosi]
> * Fix missing 'await' for getEdisonPartitions() [Paulo Castro]
> * Add extra type information (refactor bind mount array) [Paulo Castro]
> * Run linter [Paulo Castro]
> * major: Convert to typescript [Lorenzo Alberto Maria Ambrosi]
> * patch: Fix incorrect python List index check [Lorenzo Alberto Maria Ambrosi]
>
</details>
## 12.50.2 - 2021-10-05
* Error message when renaming a fleet now mentions the target name. [Carlo Miguel F. Cruz]
## 12.50.1 - 2021-09-30
* Update dependencies (@sentry/node error reporting) [Paulo Castro]

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,7 +120,7 @@ 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).
The `INSTALL*.md` and `TROUBLESHOOTING.md` files are also manually edited.

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

@ -310,7 +310,7 @@ async function zipPkg() {
archive.on('warning', console.warn);
archive.pipe(outputStream);
archive.finalize();
archive.finalize().catch(reject);
});
}

View File

@ -105,4 +105,5 @@ async function printMarkdown() {
}
}
// tslint:disable-next-line:no-floating-promises
printMarkdown();

View File

@ -24,15 +24,15 @@ const simplegit = require('simple-git/promise');
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 docFile = path.join(ROOT, 'docs', 'balena-cli.md');
const [docStat, gitStatus] = await Promise.all([
fs.stat(docFile),
git.status(),

View File

@ -103,4 +103,5 @@ export async function run(args?: string[]) {
}
}
// tslint:disable-next-line:no-floating-promises
run();

View File

@ -136,4 +136,5 @@ async function main() {
}
}
// tslint:disable-next-line:no-floating-promises
main();

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.

View File

@ -132,7 +132,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/).
@ -915,10 +915,27 @@ the uuid of the device to identify
## device init
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.
Fleets may be specified by fleet name, slug, or numeric ID. Fleet slugs are
the recommended option, as they are unique and unambiguous. Slugs can be
@ -932,11 +949,15 @@ environments). Numeric fleet IDs are deprecated because they consist of an
implementation detail of the balena backend. We intend to remove support for
numeric IDs at some point in the future.
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.
Examples:
$ balena device init
$ balena device init --fleet MyFleet
$ balena device init -f myorg/myfleet
$ balena device init --fleet myFleet --os-version 2.83.21+rev1.prod --drive /dev/disk5 --config config.json --yes
### Options
@ -978,6 +999,10 @@ Check `balena util available-drives` for available options.
path to the config JSON file, see `balena os build-config`
#### --provisioning-key-name PROVISIONING-KEY-NAME
custom key name assigned to generated provisioning api key
## device local-mode &#60;uuid&#62;
Output current local mode status, or enable/disable local mode
@ -2240,6 +2265,9 @@ device UUID
Show the available balenaOS versions for the given device type.
Check available types with `balena devices supported`.
balenaOS ESR versions can be listed with the '--esr' option. See also:
https://www.balena.io/docs/reference/OS/extended-support-release/
Examples:
$ balena os versions raspberrypi3
@ -2252,23 +2280,28 @@ device type
### Options
#### --esr
select balenaOS ESR versions
## os download &#60;type&#62;
Download an unconfigured OS image for a certain device type.
Check available types with `balena devices supported`
Download an unconfigured OS image for the specified device type.
Check available device types with 'balena devices supported'.
Note: Currently this command only works with balenaCloud, not openBalena.
If using openBalena, please download the OS from: https://www.balena.io/os/
If version is not specified the newest stable (non-pre-release) version of OS
is downloaded (if available), otherwise the newest version (if all existing
versions for the given device type are pre-release).
The '--version' option is used to select the balenaOS version. If omitted,
the latest released version is downloaded (and if only pre-release versions
exist, the latest pre-release version is downloaded).
You can pass `--version menu` to pick the OS version from the interactive menu
of all available versions.
Use '--version menu' or '--version menu-esr' to interactively select the
OS version. The latter lists ESR versions which are only available for
download on Production and Enterprise plans. See also:
https://www.balena.io/docs/reference/OS/extended-support-release/
To download a development image append `.dev` to the version or select from
the interactive menu.
Development images can be selected by appending `.dev` to the version.
Examples:
@ -2276,9 +2309,11 @@ Examples:
$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version 2.60.1+rev1
$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version 2.60.1+rev1.dev
$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version ^2.60.0
$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version 2021.10.1
$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version latest
$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version default
$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version menu
$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version menu-esr
### Arguments
@ -2294,11 +2329,13 @@ output path
#### --version VERSION
exact version number, or a valid semver range,
version number (ESR or non-ESR versions),
or semver range (non-ESR versions only),
or 'latest' (includes pre-releases),
or 'default' (excludes pre-releases if at least one stable version is available),
or 'default' (excludes pre-releases if at least one released version is available),
or 'recommended' (excludes pre-releases, will fail if only pre-release versions are available),
or 'menu' (will show the interactive menu)
or 'menu' (interactive menu, non-ESR versions),
or 'menu-esr' (interactive menu, ESR versions)
## os build-config &#60;image&#62; &#60;device-type&#62;
@ -2448,6 +2485,10 @@ balenaOS version, for example "2.32.0" or "2.44.0+rev1"
paths to local files to place into the 'system-connections' directory
#### --provisioning-key-name PROVISIONING-KEY-NAME
custom key name assigned to generated provisioning api key
## os initialize &#60;image&#62;
Initialize an os image for a device with a previously
@ -2513,7 +2554,7 @@ 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
@ -2575,18 +2616,22 @@ the wifi ssid to use (used only if --network is set to wifi)
the wifi key to use (used only if --network is set to wifi)
#### --provisioning-key-name PROVISIONING-KEY-NAME
custom key name assigned to generated provisioning api key
## config inject &#60;file&#62;
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/
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
### Arguments
@ -2598,7 +2643,7 @@ the path to the config.json file to inject
#### -t, --type TYPE
device type (Check available types with `balena devices supported`)
ignored - no longer required
#### -d, --drive DRIVE
@ -2606,39 +2651,54 @@ path to OS image file (e.g. balena.img) or block device (e.g. /dev/disk2)
## config read
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/
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
### Options
#### -t, --type TYPE
device type (Check available types with `balena devices supported`)
ignored - no longer required
#### -d, --drive DRIVE
path to OS image file (e.g. balena.img) or block device (e.g. /dev/disk2)
#### -j, --json
produce JSON output instead of tabular output
## config reconfigure
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.
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
### Options
#### -t, --type TYPE
device type (Check available types with `balena devices supported`)
ignored - no longer required
#### -d, --drive DRIVE
@ -2648,16 +2708,23 @@ path to OS image file (e.g. balena.img) or block device (e.g. /dev/disk2)
show advanced commands
#### --version VERSION
balenaOS version, for example "2.32.0" or "2.44.0+rev1"
## config write &#60;key&#62; &#60;value&#62;
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/
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
### Arguments
@ -2673,7 +2740,7 @@ the value of the config parameter to write
#### -t, --type TYPE
device type (Check available types with `balena devices supported`)
ignored - no longer required
#### -d, --drive DRIVE

View File

@ -77,11 +77,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 +93,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. */
@ -149,7 +153,10 @@ async function oclifRun(command: string[], options: AppOptions) {
/** CLI entrypoint. Called by the `bin/balena` and `bin/balena-dev` 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

@ -16,7 +16,12 @@
*/
import Command from '@oclif/command';
import { InsufficientPrivilegesError } from './errors';
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

@ -28,8 +28,10 @@ import {
appToFleetCmdMsg,
warnify,
} from '../../utils/messages';
import { isV13 } from '../../utils/version';
import type { DataOutputOptions } from '../../framework';
interface FlagsDef {
interface FlagsDef extends DataOutputOptions {
help: void;
}
@ -56,13 +58,14 @@ export class FleetCmd extends Command {
public static flags: flags.Input<FlagsDef> = {
help: cf.help,
...(isV13() ? cf.dataOutputFlags : {}),
};
public static authenticated = true;
public static primary = true;
public async run(parserOutput?: ParserOutput<FlagsDef, ArgsDef>) {
const { args: params } =
const { args: params, flags: options } =
parserOutput || this.parse<FlagsDef, ArgsDef>(FleetCmd);
const { getApplication } = await import('../../utils/sdk');
@ -82,16 +85,24 @@ export class FleetCmd extends Command {
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',
]),
);
if (isV13()) {
await this.outputData(
application,
['app_name', 'id', 'device_type', 'slug', 'commit'],
options,
);
} else {
// Emulate table.vertical title output, but avoid uppercasing and inserting spaces
console.log(`== ${application.app_name}`);
console.log(
getVisuals().table.vertical(application, [
'id',
'device_type',
'slug',
'commit',
]),
);
}
}
}

View File

@ -120,7 +120,7 @@ export class FleetRenameCmd extends Command {
} 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 new ExpectedError(`Error: fleet ${newName} already exists.`);
}
throw e;
}

View File

@ -17,18 +17,20 @@
import { flags } from '@oclif/command';
import type { Output as ParserOutput } from '@oclif/parser';
import Command from '../command';
import * as cf from '../utils/common-flags';
import { getBalenaSdk, getVisuals, stripIndent } from '../utils/lazy';
import { appToFleetCmdMsg, warnify } from '../utils/messages';
import { isV13 } from '../utils/version';
import type { DataSetOutputOptions } from '../framework';
interface ExtendedApplication extends ApplicationWithDeviceType {
device_count?: number;
online_devices?: number;
device_count: number;
online_devices: number;
device_type?: string;
}
interface FlagsDef {
interface FlagsDef extends DataSetOutputOptions {
help: void;
verbose?: boolean;
}
@ -48,12 +50,17 @@ export class FleetsCmd extends Command {
public static usage = 'fleets';
public static flags: flags.Input<FlagsDef> = {
...(isV13()
? {}
: {
verbose: flags.boolean({
default: false,
char: 'v',
description: 'No-op since release v12.0.0',
}),
}),
...(isV13() ? cf.dataSetOutputFlags : {}),
help: cf.help,
verbose: flags.boolean({
default: false,
char: 'v',
description: 'No-op since release v12.0.0',
}),
};
public static authenticated = true;
@ -75,28 +82,39 @@ export class FleetsCmd extends Command {
},
})) 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.online_devices =
application.owns__device?.filter((d) => d.is_online).length || 0;
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',
]),
);
if (isV13()) {
await this.outputData(
applications,
[
'id',
'app_name',
'slug',
'device_type',
'device_count',
'online_devices',
],
_parserOutput.flags,
);
} else {
console.log(
getVisuals().table.horizontal(applications, [
'id',
this.useAppWord ? 'app_name' : 'app_name => NAME',
'slug',
'device_type',
'online_devices',
'device_count',
]),
);
}
}
}

View File

@ -42,6 +42,7 @@ interface FlagsDef {
wifiSsid?: string;
wifiKey?: string;
appUpdatePollInterval?: string;
'provisioning-key-name'?: string;
help: void;
}
@ -65,7 +66,7 @@ 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',
@ -94,7 +95,10 @@ export default class ConfigGenerateCmd extends Command {
}),
}),
fleet: { ...cf.fleet, exclusive: ['application', 'app', 'device'] },
device: { ...cf.device, exclusive: ['application', 'app', 'fleet'] },
device: {
...cf.device,
exclusive: ['application', 'app', 'fleet', 'provisioning-key-name'],
},
deviceApiKey: flags.string({
description:
'custom device key - note that this is only supported on balenaOS 2.0.3+',
@ -128,6 +132,10 @@ export default class ConfigGenerateCmd extends Command {
description:
'supervisor cloud polling interval in minutes (e.g. for variable updates)',
}),
'provisioning-key-name': flags.string({
description: 'custom key name assigned to generated provisioning api key',
exclusive: ['device'],
}),
help: cf.help,
};
@ -202,6 +210,7 @@ export default class ConfigGenerateCmd extends Command {
override: options,
});
answers.version = options.version;
answers.provisioningKeyName = options['provisioning-key-name'];
// Generate config
const { generateDeviceConfig, generateApplicationConfig } = await import(

View File

@ -21,7 +21,7 @@ import * as cf from '../../utils/common-flags';
import { getVisuals, stripIndent } from '../../utils/lazy';
interface FlagsDef {
type: string;
type?: string;
drive?: string;
help: void;
}
@ -32,18 +32,18 @@ interface ArgsDef {
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 = [
@ -57,12 +57,13 @@ export default class ConfigInjectCmd extends Command {
public static usage = 'config inject <file>';
public static flags: flags.Input<FlagsDef> = {
type: cf.deviceType,
type: cf.deviceTypeIgnored,
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>(
@ -81,7 +82,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

@ -21,35 +21,40 @@ import * as cf from '../../utils/common-flags';
import { getVisuals, stripIndent } from '../../utils/lazy';
interface FlagsDef {
type: string;
type?: string;
drive?: string;
help: void;
json: boolean;
}
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,
type: cf.deviceTypeIgnored,
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);
@ -61,9 +66,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

@ -21,38 +21,48 @@ import * as cf from '../../utils/common-flags';
import { getVisuals, stripIndent } from '../../utils/lazy';
interface FlagsDef {
type: string;
type?: string;
drive?: string;
advanced: boolean;
help: void;
version?: string;
}
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,
type: cf.deviceTypeIgnored,
drive: cf.driveOrImg,
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() {
@ -65,10 +75,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

@ -21,7 +21,7 @@ import * as cf from '../../utils/common-flags';
import { getVisuals, stripIndent } from '../../utils/lazy';
interface FlagsDef {
type: string;
type?: string;
drive?: string;
help: void;
}
@ -33,16 +33,19 @@ interface ArgsDef {
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 = [
@ -61,14 +64,13 @@ export default class ConfigWriteCmd extends Command {
public static usage = 'config write <key> <value>';
public static flags: flags.Input<FlagsDef> = {
type: cf.deviceType,
type: cf.deviceTypeIgnored,
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>(
@ -82,14 +84,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

@ -37,24 +37,46 @@ interface FlagsDef {
drive?: string;
config?: string;
help: void;
'provisioning-key-name'?: 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.83.21+rev1.prod --drive /dev/disk5 --config config.json --yes',
];
public static usage = 'device init';
@ -85,6 +107,9 @@ export default class DeviceInitCmd extends Command {
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',
}),
help: cf.help,
};
@ -173,6 +198,13 @@ 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'],
);
}
await runCommand(configureCommand);
}

View File

@ -63,6 +63,7 @@ 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);

View File

@ -56,6 +56,7 @@ export default class LocalConfigureCmd extends Command {
};
public static root = true;
public static offlineCompatible = true;
public async run() {
const { args: params } = this.parse<FlagsDef, ArgsDef>(LocalConfigureCmd);
@ -96,7 +97,7 @@ export default class LocalConfigureCmd extends Command {
readonly CONNECTIONS_FOLDER = '/system-connections';
getConfigurationSchema(bootPartition: number, connectionFileName?: string) {
getConfigurationSchema(bootPartition?: number, connectionFileName?: string) {
connectionFileName ??= 'resin-wifi';
return {
mapper: [
@ -235,9 +236,9 @@ export default class LocalConfigureCmd extends Command {
async prepareConnectionFile(target: string) {
const _ = await import('lodash');
const imagefs = await import('balena-image-fs');
const helpers = await import('../../utils/helpers');
const { getBootPartition } = await import('balena-config-json');
const bootPartition = await helpers.getBootPartition(target);
const bootPartition = await getBootPartition(target);
const files = await imagefs.interact(target, bootPartition, async (_fs) => {
return await promisify(_fs.readdir)(this.CONNECTIONS_FOLDER);

View File

@ -65,6 +65,8 @@ export default class LocalFlashCmd extends Command {
help: cf.help,
};
public static offlineCompatible = true;
public async run() {
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
LocalFlashCmd,

View File

@ -187,7 +187,7 @@ ${messages.reachingOut}`);
if (loginType === 'register') {
const open = await import('open');
const signupUrl = `https://dashboard.${balenaUrl}/signup`;
open(signupUrl, { wait: false });
await open(signupUrl, { wait: false });
throw new ExpectedError(`Please sign up at ${signupUrl}`);
}

View File

@ -49,6 +49,7 @@ interface FlagsDef {
version?: string;
'system-connection': string[];
'initial-device-name'?: string;
'provisioning-key-name'?: string;
}
interface ArgsDef {
@ -62,6 +63,7 @@ interface Answers {
version: string; // e.g. "2.32.0+rev1"
wifiSsid?: string;
wifiKey?: string;
provisioningKeyName?: string;
}
const deviceApiKeyDeprecationMsg = stripIndent`
@ -145,6 +147,7 @@ export default class OsConfigureCmd extends Command {
config: flags.string({
description:
'path to a pre-generated config.json file to be injected in the OS image',
exclusive: ['provisioning-key-name'],
}),
'config-app-update-poll-interval': flags.integer({
description:
@ -160,7 +163,10 @@ export default class OsConfigureCmd extends Command {
'config-wifi-ssid': flags.string({
description: 'WiFi SSID (network name) (non-interactive configuration)',
}),
device: { exclusive: ['app', 'application', 'fleet'], ...cf.device },
device: {
exclusive: ['app', 'application', 'fleet', 'provisioning-key-name'],
...cf.device,
},
'device-api-key': flags.string({
char: 'k',
description:
@ -184,9 +190,15 @@ export default class OsConfigureCmd extends Command {
description:
"paths to local files to place into the 'system-connections' directory",
}),
'provisioning-key-name': flags.string({
description: 'custom key name assigned to generated provisioning api key',
exclusive: ['config', 'device'],
}),
help: cf.help,
};
public static authenticated = true;
public async run() {
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
OsConfigureCmd,
@ -254,6 +266,8 @@ export default class OsConfigureCmd extends Command {
options.version ||
(await getOsVersionFromImage(params.image, deviceTypeManifest, devInit));
answers.provisioningKeyName = options['provisioning-key-name'];
if (_.isEmpty(configJson)) {
if (device) {
configJson = await generateDeviceConfig(
@ -300,7 +314,8 @@ export default class OsConfigureCmd extends Command {
}),
);
const bootPartition = await helpers.getBootPartition(params.image);
const { getBootPartition } = await import('balena-config-json');
const bootPartition = await getBootPartition(params.image);
const imagefs = await import('balena-image-fs');

View File

@ -34,30 +34,33 @@ export default class OsDownloadCmd extends Command {
public static description = stripIndent`
Download an unconfigured OS image.
Download an unconfigured OS image for a certain device type.
Check available types with \`balena devices supported\`
Download an unconfigured OS image for the specified device type.
Check available device types with 'balena devices supported'.
Note: Currently this command only works with balenaCloud, not openBalena.
If using openBalena, please download the OS from: https://www.balena.io/os/
If version is not specified the newest stable (non-pre-release) version of OS
is downloaded (if available), otherwise the newest version (if all existing
versions for the given device type are pre-release).
The '--version' option is used to select the balenaOS version. If omitted,
the latest released version is downloaded (and if only pre-release versions
exist, the latest pre-release version is downloaded).
You can pass \`--version menu\` to pick the OS version from the interactive menu
of all available versions.
Use '--version menu' or '--version menu-esr' to interactively select the
OS version. The latter lists ESR versions which are only available for
download on Production and Enterprise plans. See also:
https://www.balena.io/docs/reference/OS/extended-support-release/
To download a development image append \`.dev\` to the version or select from
the interactive menu.
Development images can be selected by appending \`.dev\` to the version.
`;
public static examples = [
'$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img',
'$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version 2.60.1+rev1',
'$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version 2.60.1+rev1.dev',
'$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version ^2.60.0',
'$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version 2021.10.1',
'$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version latest',
'$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version default',
'$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version menu',
'$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version menu-esr',
];
public static args = [
@ -78,11 +81,13 @@ export default class OsDownloadCmd extends Command {
}),
version: flags.string({
description: stripIndent`
exact version number, or a valid semver range,
version number (ESR or non-ESR versions),
or semver range (non-ESR versions only),
or 'latest' (includes pre-releases),
or 'default' (excludes pre-releases if at least one stable version is available),
or 'default' (excludes pre-releases if at least one released version is available),
or 'recommended' (excludes pre-releases, will fail if only pre-release versions are available),
or 'menu' (will show the interactive menu)
or 'menu' (interactive menu, non-ESR versions),
or 'menu-esr' (interactive menu, ESR versions)
`,
}),
help: cf.help,
@ -93,6 +98,26 @@ export default class OsDownloadCmd extends Command {
OsDownloadCmd,
);
// balenaOS ESR versions require user authentication
if (options.version) {
const { isESR } = await import('balena-image-manager');
if (options.version === 'menu-esr' || isESR(options.version)) {
try {
await OsDownloadCmd.checkLoggedIn();
} catch (e) {
const { ExpectedError, NotLoggedInError } = await import(
'../../errors'
);
if (e instanceof NotLoggedInError) {
throw new ExpectedError(stripIndent`
${e.message}
User authentication is required to download balenaOS ESR versions.`);
}
throw e;
}
}
}
const { downloadOSImage } = await import('../../utils/cloud');
try {

View File

@ -18,9 +18,10 @@
import { flags } from '@oclif/command';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import { stripIndent } from '../../utils/lazy';
interface FlagsDef {
esr?: boolean;
help: void;
}
@ -34,6 +35,9 @@ export default class OsVersionsCmd extends Command {
Show the available balenaOS versions for the given device type.
Check available types with \`balena devices supported\`.
balenaOS ESR versions can be listed with the '--esr' option. See also:
https://www.balena.io/docs/reference/OS/extended-support-release/
`;
public static examples = ['$ balena os versions raspberrypi3'];
@ -50,16 +54,20 @@ export default class OsVersionsCmd extends Command {
public static flags: flags.Input<FlagsDef> = {
help: cf.help,
esr: flags.boolean({
description: 'select balenaOS ESR versions',
default: false,
}),
};
public async run() {
const { args: params } = this.parse<FlagsDef, ArgsDef>(OsVersionsCmd);
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
OsVersionsCmd,
);
const { versions: vs, recommended } =
await getBalenaSdk().models.os.getSupportedVersions(params.type);
const { getFormattedOsVersions } = await import('../../utils/cloud');
const vs = await getFormattedOsVersions(params.type, !!options.esr);
vs.forEach((v) => {
console.log(`v${v}` + (v === recommended ? ' (recommended)' : ''));
});
console.log(vs.map((v) => v.formattedVersion).join('\n'));
}
}

View File

@ -282,10 +282,17 @@ Can be repeated to add multiple certificates.\
if (signal) {
gotSignal = true;
nodeCleanup.uninstall(); // don't call cleanup handler again
preloader.cleanup().then(() => {
// calling process.exit() won't inform parent process of signal
process.kill(process.pid, signal);
});
preloader
.cleanup()
.then(() => {
// calling process.exit() won't inform parent process of signal
process.kill(process.pid, signal);
})
.catch((e) => {
if (process.env.DEBUG) {
console.error(e);
}
});
return false;
}
});

View File

@ -68,6 +68,7 @@ export default class ScanCmd extends Command {
public static primary = true;
public static root = true;
public static offlineCompatible = true;
public async run() {
const _ = await import('lodash');

View File

@ -117,6 +117,7 @@ export default class SshCmd extends Command {
};
public static primary = true;
public static offlineCompatible = true;
public async run() {
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
@ -144,6 +145,7 @@ export default class SshCmd extends Command {
const useProxy = !!proxyConfig && !options.noproxy;
// this will be a tunnelled SSH connection...
await Command.checkNotUsingOfflineMode();
await Command.checkLoggedIn();
const deviceUuid = await getOnlineTargetDeviceUuid(
sdk,

View File

@ -38,6 +38,8 @@ export default class UtilAvailableDrivesCmd extends Command {
help: cf.help,
};
public static offlineCompatible = true;
public async run() {
this.parse<FlagsDef, {}>(UtilAvailableDrivesCmd);

View File

@ -57,6 +57,8 @@ export default class VersionCmd extends Command {
public static usage = 'version';
public static offlineCompatible = true;
public static flags: flags.Input<FlagsDef> = {
all: flags.boolean({
default: false,

View File

@ -104,7 +104,11 @@ export class DeprecationChecker {
const url = this.getNpmUrl(version);
let response: import('got').Response<Dictionary<any>> | undefined;
try {
response = await got(url, { responseType: 'json', retry: 0 });
response = await got(url, {
responseType: 'json',
retry: 0,
timeout: 4000,
});
} catch (e) {
// 404 is expected if `version` hasn't been published yet
if (e.response?.statusCode !== 404) {

View File

@ -31,6 +31,8 @@ export class NotLoggedInError extends ExpectedError {}
export class InsufficientPrivilegesError extends ExpectedError {}
export class NotAvailableInOfflineModeError extends ExpectedError {}
export class InvalidPortMappingError extends ExpectedError {
constructor(mapping: string) {
super(`'${mapping}' is not a valid port mapping.`);

View File

@ -16,7 +16,7 @@
*/
import * as packageJSON from '../package.json';
import { getBalenaSdk } from './utils/lazy';
import { getBalenaSdk, stripIndent } from './utils/lazy';
interface CachedUsername {
token: string;
@ -129,10 +129,20 @@ async function sendEvent(balenaUrl: string, event: string, username?: string) {
data: Buffer.from(JSON.stringify(trackData)).toString('base64'),
};
try {
await got(url, { searchParams, retry: 0 });
await got(url, { searchParams, retry: 0, timeout: 4000 });
} catch (e) {
if (process.env.DEBUG) {
console.error(`[debug] Event tracking error: ${e.message || e}`);
}
if (e instanceof got.TimeoutError) {
console.error(stripIndent`
Timeout submitting analytics event to balenaCloud/openBalena.
If you are using the balena CLI in an air-gapped environment with a filtered
internet connection, set the BALENARC_OFFLINE_MODE=1 environment variable
when using CLI commands that do not strictly require access to balenaCloud.
`);
}
// Note: You can simulate a timeout using non-routable address 10.0.0.0
}
}

19
lib/framework/index.ts Normal file
View File

@ -0,0 +1,19 @@
/*
Copyright 2020 Balena
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 { DataOutputOptions, DataSetOutputOptions } from './output';
export { DataOutputOptions, DataSetOutputOptions };

158
lib/framework/output.ts Normal file
View File

@ -0,0 +1,158 @@
/*
Copyright 2020 Balena
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 { getCliUx, getChalk } from '../utils/lazy';
export interface DataOutputOptions {
fields?: string;
json?: boolean;
}
export interface DataSetOutputOptions extends DataOutputOptions {
filter?: string;
'no-header'?: boolean;
'no-truncate'?: boolean;
sort?: string;
}
/**
* Output message to STDERR
*/
export function outputMessage(msg: string) {
// Messages go to STDERR
console.error(msg);
}
/**
* Output result data to STDOUT
* Supports:
* - arrays of items (displayed in a tabular way),
* - single items (displayed in a field per row format).
*
* @param data Array of data objects to output
* @param fields Array of fieldnames, specifying the fields and display order
* @param options Output options
*/
export async function outputData(
data: any[] | {},
fields: string[],
options: DataOutputOptions | DataSetOutputOptions,
) {
if (Array.isArray(data)) {
await outputDataSet(data, fields, options as DataSetOutputOptions);
} else {
await outputDataItem(data, fields, options as DataOutputOptions);
}
}
/**
* Wraps the cli.ux table implementation, to output tabular data
*
* @param data Array of data objects to output
* @param fields Array of fieldnames, specifying the fields and display order
* @param options Output options
*/
async function outputDataSet(
data: any[],
fields: string[],
options: DataSetOutputOptions,
) {
// Oclif expects fields to be specified in the format used in table headers (though lowercase)
// By replacing underscores with spaces here, we can support both header format and actual field name
// (e.g. as seen in json output).
options.fields = options.fields?.replace(/_/g, ' ');
options.filter = options.filter?.replace(/_/g, ' ');
options.sort = options.sort?.replace(/_/g, ' ');
getCliUx().table(
data,
// Convert fields array to column object keys
// that cli.ux expects. We can later add support
// for both formats if beneficial
fields.reduce((ac, a) => ({ ...ac, [a]: {} }), {}),
{
...options,
...(options.json
? {
output: 'json',
}
: {}),
columns: options.fields,
printLine,
},
);
}
/**
* Outputs a single data object (like `resin-cli-visuals table.vertical`),
* but supporting a subset of options from `cli-ux table` (--json and --fields)
*
* @param data Array of data objects to output
* @param fields Array of fieldnames, specifying the fields and display order
* @param options Output options
*/
async function outputDataItem(
data: any,
fields: string[],
options: DataOutputOptions,
) {
const outData: typeof data = {};
// Convert comma separated list of fields in `options.fields` to array of correct format.
// Note, user may have specified the true field name (e.g. `some_field`),
// or the format displayed in headers (e.g. `Some field`, case insensitive).
const userSelectedFields = options.fields?.split(',').map((f) => {
return f.toLowerCase().trim().replace(/ /g, '_');
});
// Order and filter the fields based on `fields` parameter and `options.fields`
(userSelectedFields || fields).forEach((fieldName) => {
if (fields.includes(fieldName)) {
outData[fieldName] = data[fieldName];
}
});
if (options.json) {
printLine(JSON.stringify(outData, undefined, 2));
} else {
const chalk = getChalk();
const { capitalize } = await import('lodash');
// Find longest key, so we can align results
const longestKeyLength = getLongestObjectKeyLength(outData);
// Output one field per line
for (const [k, v] of Object.entries(outData)) {
const shim = ' '.repeat(longestKeyLength - k.length);
const kDisplay = capitalize(k.replace(/_/g, ' '));
printLine(`${chalk.bold(kDisplay) + shim} : ${v}`);
}
}
}
function getLongestObjectKeyLength(o: any): number {
return Object.keys(o).length >= 1
? Object.keys(o).reduce((a, b) => {
return a.length > b.length ? a : b;
}).length
: 0;
}
function printLine(s: any) {
// Duplicating oclif cli-ux's default implementation here,
// but using this one explicitly for ease of testing
process.stdout.write(s + '\n');
}

View File

@ -58,7 +58,13 @@ export function normalizeEnvVar(varName: string) {
process.env[varName] = parseBoolEnvVar(varName) ? '1' : '';
}
const bootstrapVars = ['DEBUG', 'BALENARC_NO_SENTRY'];
const bootstrapVars = [
'BALENARC_NO_SENTRY',
'BALENARC_NO_ANALYTICS',
'BALENARC_OFFLINE_MODE',
'BALENARC_UNSUPPORTED',
'DEBUG',
];
export function normalizeEnvVars(varNames: string[] = bootstrapVars) {
for (const varName of varNames) {
@ -66,6 +72,17 @@ export function normalizeEnvVars(varNames: string[] = bootstrapVars) {
}
}
/**
* Set the individual env vars implied by BALENARC_OFFLINE_MODE.
*/
export function setOfflineModeEnvVars() {
if (process.env.BALENARC_OFFLINE_MODE) {
process.env.BALENARC_UNSUPPORTED = '1';
process.env.BALENARC_NO_SENTRY = '1';
process.env.BALENARC_NO_ANALYTICS = '1';
}
}
/**
* Implements the 'pkgExec' command, used as a way to provide a Node.js
* interpreter for child_process.spawn()-like operations when the CLI is

View File

@ -107,6 +107,16 @@ export const getDeviceAndMaybeAppFromUUID = _.memoize(
(_sdk, deviceUUID) => deviceUUID,
);
/** Given a device type alias like 'nuc', return the actual slug like 'intel-nuc'. */
export const unaliasDeviceType = _.memoize(async function (
sdk: SDK.BalenaSDK,
deviceType: string,
): Promise<string> {
return (
(await sdk.models.device.getManifestBySlug(deviceType)).slug || deviceType
);
});
/**
* Download balenaOS image for the specified `deviceType`.
* `OSVersion` may be one of:
@ -130,15 +140,13 @@ export async function downloadOSImage(
console.info(`Getting device operating system for ${deviceType}`);
if (!OSVersion) {
console.warn('OS version not specified: using latest stable version');
console.warn('OS version not specified: using latest released version');
}
OSVersion = OSVersion
? await resolveOSVersion(deviceType, OSVersion)
: 'default';
const displayVersion = OSVersion === 'default' ? '' : ` ${OSVersion}`;
// Override the default zlib flush value as we've seen cases of
// incomplete files being identified as successful downloads when using Z_SYNC_FLUSH.
// Using Z_NO_FLUSH results in a Z_BUF_ERROR instead of a corrupt image file.
@ -150,10 +158,17 @@ export async function downloadOSImage(
const manager = await import('balena-image-manager');
const stream = await manager.get(deviceType, OSVersion);
const displayVersion = await new Promise((resolve, reject) => {
stream.on('error', reject);
stream.on('balena-image-manager:resolved-version', resolve);
});
const visuals = getVisuals();
const bar = new visuals.Progress(`Downloading Device OS${displayVersion}`);
const bar = new visuals.Progress(
`Downloading balenaOS version ${displayVersion}`,
);
const spinner = new visuals.Spinner(
`Downloading Device OS${displayVersion} (size unknown)`,
`Downloading balenaOS version ${displayVersion} (size unknown)`,
);
stream.on('progress', (state: any) => {
@ -183,28 +198,42 @@ export async function downloadOSImage(
const streamToPromise = await import('stream-to-promise');
await streamToPromise(stream.pipe(output));
console.info('The image was downloaded successfully');
console.info(
`balenaOS image version ${displayVersion} downloaded successfully`,
);
return outputPath;
}
async function resolveOSVersion(deviceType: string, version: string) {
if (version !== 'menu') {
async function resolveOSVersion(
deviceType: string,
version: string,
): Promise<string> {
if (['menu', 'menu-esr'].includes(version)) {
return await selectOSVersionFromMenu(deviceType, version === 'menu-esr');
}
if (/^v?\d+\.\d+\.\d+/.test(version)) {
if (version[0] === 'v') {
version = version.slice(1);
}
return version;
// The version must end with either '.dev' or '.prod', as expected
// by `balena-image-manager` and the balena SDK.
if (!version.endsWith('.dev') && !version.endsWith('.prod')) {
version += '.prod';
}
}
return version;
}
const vs = (
(await getBalenaSdk().models.hostapp.getAllOsVersions([deviceType]))[
deviceType
] ?? []
).filter((v) => v.osType === 'default');
async function selectOSVersionFromMenu(
deviceType: string,
esr: boolean,
): Promise<string> {
const vs = await getFormattedOsVersions(deviceType, esr);
const choices = vs.map((v) => ({
value: v.rawVersion,
name: `v${v.rawVersion}` + (v.isRecommended ? ' (recommended)' : ''),
name: v.formattedVersion,
}));
return getCliForm().ask({
@ -214,3 +243,44 @@ async function resolveOSVersion(deviceType: string, version: string) {
default: (vs.find((v) => v.isRecommended) ?? vs[0])?.rawVersion,
});
}
/**
* Return the output of sdk.models.hostapp.getAvailableOsVersions(), filtered
* regarding ESR or non-ESR versions, and having the `formattedVersion` field
* reformatted for compatibility with the pre-existing output format of the
* `os versions` and `os download` commands.
*/
export async function getFormattedOsVersions(
deviceType: string,
esr: boolean,
): Promise<SDK.OsVersion[]> {
const sdk = getBalenaSdk();
let slug = deviceType;
let versionsByDT: SDK.OsVersionsByDeviceType =
await sdk.models.hostapp.getAvailableOsVersions([slug]);
// if slug is an alias, fetch the real slug
if (!versionsByDT[slug]?.length) {
// unaliasDeviceType() produces a nice error msg if slug is invalid
slug = await unaliasDeviceType(sdk, slug);
if (slug !== deviceType) {
versionsByDT = await sdk.models.hostapp.getAvailableOsVersions([slug]);
}
}
const versions: SDK.OsVersion[] = (versionsByDT[slug] || [])
.filter((v: SDK.OsVersion) => v.osType === (esr ? 'esr' : 'default'))
.map((v: SDK.OsVersion) => {
const i = v.formattedVersion.indexOf(' ');
v.formattedVersion =
i < 0
? `v${v.rawVersion}`
: `v${v.rawVersion}${v.formattedVersion.substring(i)}`;
return v;
});
if (!versions.length) {
const vType = esr ? 'ESR versions' : 'versions';
throw new ExpectedError(
`Error: No balenaOS ${vType} found for device type '${deviceType}'.`,
);
}
return versions;
}

View File

@ -16,11 +16,12 @@
*/
import { flags } from '@oclif/command';
import type { IBooleanFlag } from '@oclif/parser/lib/flags';
import { stripIndent } from './lazy';
import { lowercaseIfSlug } from './normalization';
import { isV13 } from './version';
import type { IBooleanFlag } from '@oclif/parser/lib/flags';
import type { DataOutputOptions, DataSetOutputOptions } from '../framework';
export const v13: IBooleanFlag<boolean> = flags.boolean({
description: stripIndent`\
@ -113,8 +114,49 @@ export const deviceType = flags.string({
required: true,
});
export const deviceTypeIgnored = flags.string({
description: 'ignored - no longer required',
char: 't',
required: false,
hidden: isV13(),
});
export const json: IBooleanFlag<boolean> = flags.boolean({
char: 'j',
description: 'produce JSON output instead of tabular output',
default: false,
});
export const dataOutputFlags: flags.Input<DataOutputOptions> = {
fields: flags.string({
description: 'only show provided fields (comma-separated)',
}),
json: flags.boolean({
char: 'j',
exclusive: ['no-truncate'],
description: 'output in json format',
default: false,
}),
};
export const dataSetOutputFlags: flags.Input<DataOutputOptions> &
flags.Input<DataSetOutputOptions> = {
...dataOutputFlags,
filter: flags.string({
description:
'filter results by substring matching of a given field, eg: --filter field=foo',
}),
'no-header': flags.boolean({
exclusive: ['json'],
description: 'hide table header from output',
default: false,
}),
'no-truncate': flags.boolean({
exclusive: ['json'],
description: 'do not truncate output to fit screen',
default: false,
}),
sort: flags.string({
description: `field to sort by (prepend '-' for descending order)`,
}),
};

View File

@ -63,9 +63,7 @@ export function createProject(
const compose = require('resin-compose-parse');
// both methods below may throw.
const rawComposition = yml.load(composeStr, {
schema: yml.FAILSAFE_SCHEMA,
});
const rawComposition = yml.load(composeStr);
const composition = compose.normalize(rawComposition);
projectName ||= path.basename(composePath);
@ -356,76 +354,6 @@ export const authorizePush = function (
.catch(() => '');
};
/**
* @param {import('dockerode')} docker
* @param {string} token
* @param {Array<import('./compose-types').TaggedImage>} images
* @param {(serviceImage: import('balena-release/build/models').ImageModel, props: object) => void} afterEach
*/
export const pushAndUpdateServiceImages = function (
docker,
token,
images,
afterEach,
) {
const { DockerProgress } = require('docker-progress');
const { retry } = require('./helpers');
const tty = require('./tty')(process.stdout);
const Bluebird = require('bluebird');
const opts = { authconfig: { registrytoken: token } };
const progress = new DockerProgress({ docker });
const renderer = pushProgressRenderer(
tty,
getChalk().blue('[Push]') + ' ',
);
const reporters = progress.aggregateProgress(images.length, renderer);
return Bluebird.using(tty.cursorHidden(), () =>
Promise.all(
images.map(({ serviceImage, localImage, props, logs }, index) =>
Promise.all([
localImage.inspect().then((img) => img.Size),
retry({
// @ts-ignore
func: () => progress.push(localImage.name, reporters[index], opts),
maxAttempts: 3, // try calling func 3 times (max)
// @ts-ignore
label: localImage.name, // label for retry log messages
initialDelayMs: 2000, // wait 2 seconds before the 1st retry
backoffScaler: 1.4, // wait multiplier for each retry
}).finally(renderer.end),
])
.then(
/** @type {([number, string]) => void} */
function ([size, digest]) {
serviceImage.image_size = size;
serviceImage.content_hash = digest;
serviceImage.build_log = logs;
serviceImage.dockerfile = props.dockerfile;
serviceImage.project_type = props.projectType;
if (props.startTime) {
serviceImage.start_timestamp = props.startTime;
}
if (props.endTime) {
serviceImage.end_timestamp = props.endTime;
}
serviceImage.push_timestamp = new Date();
serviceImage.status = 'success';
},
)
.catch(function (e) {
serviceImage.error_message = '' + e;
serviceImage.status = 'failed';
throw e;
})
.finally(() => afterEach?.(serviceImage, props)),
),
),
);
};
// utilities
const renderProgressBar = function (percentage, stepCount) {
@ -437,7 +365,7 @@ const renderProgressBar = function (percentage, stepCount) {
return `${bar} ${_.padStart(percentage, 3)}%`;
};
var pushProgressRenderer = function (tty, prefix) {
export const pushProgressRenderer = function (tty, prefix) {
const fn = function (e) {
const { error, percentage } = e;
if (error != null) {

View File

@ -1085,53 +1085,52 @@ async function performResolution(
releaseHash: string,
preprocessHook?: (dockerfile: string) => string,
): Promise<MultiBuild.BuildTask[]> {
const { cloneTarStream } = await import('tar-utils');
const multiBuild = await import('resin-multibuild');
return await new Promise<MultiBuild.BuildTask[]>((resolve, reject) => {
const buildTasks = multiBuild.performResolution(
tasks,
deviceInfo.arch,
deviceInfo.deviceType,
{ error: [reject] },
{
BALENA_RELEASE_HASH: releaseHash,
BALENA_APP_NAME: appName,
},
preprocessHook,
);
(async () => {
try {
// Do one task at a time in order to reduce peak memory usage. Resolves to buildTasks.
for (const buildTask of buildTasks) {
// buildStream is falsy for "external" tasks (image pull)
if (!buildTask.buildStream) {
continue;
}
let error: Error | undefined;
try {
// Consume each task.buildStream in order to trigger the
// resolution events that define fields like:
// task.dockerfile, task.dockerfilePath,
// task.projectType, task.resolved
// This mimics what is currently done in `resin-builder`.
buildTask.buildStream = await cloneTarStream(buildTask.buildStream);
} catch (e) {
error = e;
}
if (error || (!buildTask.external && !buildTask.resolved)) {
const cause = error ? `${error}\n` : '';
throw new ExpectedError(
`${cause}Project type for service "${buildTask.serviceName}" could not be determined. Missing a Dockerfile?`,
);
}
}
resolve(buildTasks);
} catch (e) {
reject(e);
}
})();
const resolveListeners: MultiBuild.ResolveListeners = {};
const resolvePromise = new Promise<never>((_resolve, reject) => {
resolveListeners.error = [reject];
});
const buildTasks = multiBuild.performResolution(
tasks,
deviceInfo.arch,
deviceInfo.deviceType,
resolveListeners,
{
BALENA_RELEASE_HASH: releaseHash,
BALENA_APP_NAME: appName,
},
preprocessHook,
);
await Promise.race([resolvePromise, resolveTasks(buildTasks)]);
return buildTasks;
}
async function resolveTasks(buildTasks: MultiBuild.BuildTask[]) {
const { cloneTarStream } = await import('tar-utils');
// Do one task at a time in order to reduce peak memory usage. Resolves to buildTasks.
for (const buildTask of buildTasks) {
// buildStream is falsy for "external" tasks (image pull)
if (!buildTask.buildStream) {
continue;
}
let error: Error | undefined;
try {
// Consume each task.buildStream in order to trigger the
// resolution events that define fields like:
// task.dockerfile, task.dockerfilePath,
// task.projectType, task.resolved
// This mimics what is currently done in `resin-builder`.
buildTask.buildStream = await cloneTarStream(buildTask.buildStream);
} catch (e) {
error = e;
}
if (error || (!buildTask.external && !buildTask.resolved)) {
const cause = error ? `${error}\n` : '';
throw new ExpectedError(
`${cause}Project type for service "${buildTask.serviceName}" could not be determined. Missing a Dockerfile?`,
);
}
}
}
/**
@ -1306,15 +1305,101 @@ async function getTokenForPreviousRepos(
return token;
}
async function pushAndUpdateServiceImages(
docker: Dockerode,
token: string,
images: TaggedImage[],
afterEach: (
serviceImage: import('balena-release/build/models').ImageModel,
props: object,
) => void,
) {
const { DockerProgress } = await import('docker-progress');
const { retry } = await import('./helpers');
const { pushProgressRenderer } = await import('./compose');
const tty = (await import('./tty'))(process.stdout);
const opts = { authconfig: { registrytoken: token } };
const progress = new DockerProgress({ docker });
const renderer = pushProgressRenderer(
tty,
getChalk().blue('[Push]') + ' ',
);
const reporters = progress.aggregateProgress(images.length, renderer);
const pushImage = async (
localImage: Dockerode.Image,
index: number,
): Promise<string> => {
try {
// TODO 'localImage as any': find out exactly why tsc warns about
// 'name' that exists as a matter of fact, with a value similar to:
// "name": "registry2.balena-cloud.com/v2/aa27790dff571ec7d2b4fbcf3d4648d5:latest"
const imgName: string = (localImage as any).name || '';
const imageDigest: string = await retry({
func: () => progress.push(imgName, reporters[index], opts),
maxAttempts: 3, // try calling func 3 times (max)
label: imgName, // label for retry log messages
initialDelayMs: 2000, // wait 2 seconds before the 1st retry
backoffScaler: 1.4, // wait multiplier for each retry
});
if (!imageDigest) {
throw new ExpectedError(stripIndent`\
Unable to extract image digest (content hash) from image upload progress stream for image:
${imgName}`);
}
return imageDigest;
} finally {
renderer.end();
}
};
const inspectAndPushImage = async (
{ serviceImage, localImage, props, logs }: TaggedImage,
index: number,
) => {
try {
const [imgInfo, imgDigest] = await Promise.all([
localImage.inspect(),
pushImage(localImage, index),
]);
serviceImage.image_size = imgInfo.Size;
serviceImage.content_hash = imgDigest;
serviceImage.build_log = logs;
serviceImage.dockerfile = props.dockerfile;
serviceImage.project_type = props.projectType;
if (props.startTime) {
serviceImage.start_timestamp = props.startTime;
}
if (props.endTime) {
serviceImage.end_timestamp = props.endTime;
}
serviceImage.push_timestamp = new Date();
serviceImage.status = 'success';
} catch (error) {
serviceImage.error_message = '' + error;
serviceImage.status = 'failed';
throw error;
} finally {
await afterEach(serviceImage, props);
}
};
tty.hideCursor();
try {
await Promise.all(images.map(inspectAndPushImage));
} finally {
tty.showCursor();
}
}
async function pushServiceImages(
docker: import('dockerode'),
docker: Dockerode,
logger: Logger,
pineClient: ReturnType<typeof import('balena-release').createClient>,
taggedImages: TaggedImage[],
token: string,
skipLogUpload: boolean,
): Promise<void> {
const { pushAndUpdateServiceImages } = await import('./compose');
const releaseMod = await import('balena-release');
logger.logInfo('Pushing images to registry...');
await pushAndUpdateServiceImages(
@ -1337,7 +1422,7 @@ async function pushServiceImages(
const PLAIN_SEMVER_REGEX = /^([0-9]+)\.([0-9]+)\.([0-9]+)$/;
export async function deployProject(
docker: import('dockerode'),
docker: Dockerode,
logger: Logger,
composition: Composition,
images: BuiltImage[],

View File

@ -35,6 +35,9 @@ export interface ImgConfig {
wifiKey?: string;
initialDeviceName?: string;
apiKey?: string;
deviceApiKey?: string;
// props for older OS versions
connectivity?: string;
files?: {
@ -51,7 +54,7 @@ export interface ImgConfig {
};
}
export async function generateBaseConfig(
export async function generateApplicationConfig(
application: BalenaSdk.Application,
options: {
version: string;
@ -70,9 +73,7 @@ export async function generateBaseConfig(
const config = (await getBalenaSdk().models.os.getConfig(
application.slug,
options,
)) as ImgConfig & { apiKey?: string };
// os.getConfig always returns a config for an app
delete config.apiKey;
)) as ImgConfig;
// merge sshKeys to config, when they have been specified
if (options.os && options.os.sshKeys) {
@ -86,25 +87,6 @@ export async function generateBaseConfig(
return config;
}
export async function generateApplicationConfig(
application: BalenaSdk.Application,
options: {
version: string;
deviceType?: string;
appUpdatePollInterval?: number;
},
) {
const config = await generateBaseConfig(application, options);
if (semver.satisfies(options.version, '<2.7.8')) {
await addApplicationKey(config, application.id);
} else {
await addProvisioningKey(config, application.id);
}
return config;
}
export function generateDeviceConfig(
device: DeviceWithDeviceType & {
belongs_to__application: BalenaSdk.PineDeferred;
@ -112,19 +94,32 @@ export function generateDeviceConfig(
deviceApiKey: string | true | undefined,
options: { version: string },
) {
return getBalenaSdk()
.models.application.get(device.belongs_to__application.__id)
const sdk = getBalenaSdk();
return sdk.models.application
.get(device.belongs_to__application.__id)
.then(async (application) => {
const baseConfigOpts = {
...options,
deviceType: device.is_of__device_type[0].slug,
};
const config = await generateBaseConfig(application, baseConfigOpts);
// TODO: Generate the correct key beforehand and pass it to os.getConfig() once
// the API supports injecting a provided key, to avoid generating an unused one.
const config = await generateApplicationConfig(
application,
baseConfigOpts,
);
// os.getConfig always returns a config for an app
delete config.apiKey;
if (deviceApiKey == null && semver.satisfies(options.version, '<2.0.3')) {
await addApplicationKey(config, application.id);
config.apiKey = await sdk.models.application.generateApiKey(
application.id,
);
} else {
await addDeviceKey(config, device.uuid, deviceApiKey || true);
config.deviceApiKey =
typeof deviceApiKey === 'string' && deviceApiKey
? deviceApiKey
: await sdk.models.device.generateDeviceKey(device.uuid);
}
return config;
@ -139,35 +134,3 @@ export function generateDeviceConfig(
return config;
});
}
function addApplicationKey(config: any, applicationNameOrId: string | number) {
return getBalenaSdk()
.models.application.generateApiKey(applicationNameOrId)
.then((apiKey) => {
config.apiKey = apiKey;
return apiKey;
});
}
function addProvisioningKey(config: any, applicationNameOrId: string | number) {
return getBalenaSdk()
.models.application.generateProvisioningKey(applicationNameOrId)
.then((apiKey) => {
config.apiKey = apiKey;
return apiKey;
});
}
async function addDeviceKey(
config: any,
uuid: string,
customDeviceApiKey: string | true,
) {
if (customDeviceApiKey === true) {
config.deviceApiKey = await getBalenaSdk().models.device.generateDeviceKey(
uuid,
);
} else {
config.deviceApiKey = customDeviceApiKey;
}
}

View File

@ -34,6 +34,7 @@ import {
loadProject,
makeBuildTasks,
tarDirectory,
makeImageName,
} from '../compose_ts';
import Logger = require('../logger');
import { DeviceAPI, DeviceInfo } from './api';
@ -44,6 +45,7 @@ import { stripIndent } from '../lazy';
const LOCAL_APPNAME = 'localapp';
const LOCAL_RELEASEHASH = 'localrelease';
const LOCAL_PROJECT_NAME = 'local_image';
// Define the logger here so the debug output
// can be used everywhere
@ -197,14 +199,17 @@ export async function deployToDevice(opts: DeviceDeployOptions): Promise<void> {
await checkBuildSecretsRequirements(docker, opts.source);
globalLogger.logDebug('Tarring all non-ignored files...');
const tarStartTime = Date.now();
const tarStream = await tarDirectory(opts.source, {
composition: project.composition,
convertEol: opts.convertEol,
multiDockerignore: opts.multiDockerignore,
nogitignore: opts.nogitignore, // v13: delete this line
});
globalLogger.logDebug(`Tarring complete in ${Date.now() - tarStartTime} ms`);
// Try to detect the device information
globalLogger.logDebug('Fetching device information...');
const deviceInfo = await api.getDeviceInformation();
let buildLogs: Dictionary<string> | undefined;
@ -375,7 +380,11 @@ async function performBuilds(
// We can be sure that localImage.name is set here, because of the failure code above
const image = docker.getImage(localImage.name!);
await image.tag({
repo: generateImageName(localImage.serviceName),
repo: makeImageName(
LOCAL_PROJECT_NAME,
localImage.serviceName,
'latest',
),
force: true,
});
imagesToRemove.push(localImage.name!);
@ -533,7 +542,7 @@ async function assignDockerBuildOpts(
'io.resin.local.image': '1',
'io.resin.local.service': task.serviceName,
},
t: generateImageName(task.serviceName),
t: getImageNameFromTask(task),
nocache: opts.nocache,
forcerm: true,
pull: opts.pull,
@ -550,8 +559,10 @@ async function assignDockerBuildOpts(
);
}
function generateImageName(serviceName: string): string {
return `local_image_${serviceName}:latest`;
function getImageNameFromTask(task: BuildTask): string {
return !task.external && task.tag
? task.tag
: makeImageName(LOCAL_PROJECT_NAME, task.serviceName, 'latest');
}
export function generateTargetState(
@ -587,6 +598,8 @@ export function generateTargetState(
contract = keyedBuildTasks[name].contract;
}
const task = keyedBuildTasks[name];
services[idx] = {
...defaults,
...opts,
@ -595,7 +608,7 @@ export function generateTargetState(
imageId: idx,
serviceName: name,
serviceId: idx,
image: generateImageName(name),
image: getImageNameFromTask(task),
running: true,
},
};

View File

@ -201,43 +201,6 @@ function getApplication(
) as Promise<ApplicationWithDeviceType>;
}
/**
* Returns the boot partition number of the given image.
* @param imagePath Local filesystem path to a balenaOS image file
*/
export async function getBootPartition(imagePath: string): Promise<number> {
const imagefs = await import('balena-image-fs');
const filedisk = await import('file-disk');
const partitioninfo = await import('partitioninfo');
const partitionNumber = await filedisk.withOpenFile(
imagePath,
'r',
async (handle) => {
const disk = new filedisk.FileDisk(handle, true, false, false);
const { partitions } = await partitioninfo.getPartitions(disk, {
includeExtended: false,
getLogical: true,
});
for (const { index } of partitions) {
try {
return await imagefs.interact(disk, index, async (fs) => {
const statAsync = promisify(fs.stat);
const stats = await statAsync('/device-type.json');
if (stats.isFile()) {
return index;
}
});
} catch (error) {
// noop
}
}
},
);
return partitionNumber ?? 1;
}
const second = 1000; // 1000 milliseconds
const minute = 60 * second;
export const delay = promisify(setTimeout);

View File

@ -200,6 +200,45 @@ export interface FileStats {
stats: Stats;
}
/**
* Create a list of files for the filesystem subtree rooted at
* projectDir, excluding entries for directories themselves.
* @param projectDir Source directory (root of subtree to be listed)
*/
async function listFiles(projectDir: string): Promise<string[]> {
const files: string[] = [];
async function walk(currentDirs: string[]): Promise<string[]> {
if (!currentDirs.length) {
return files;
}
const foundDirs: string[] = [];
// Because `currentDirs` can be of arbitrary length, process them in smaller batches
// to avoid out of memory errors.
// This approach is significantly faster than using Bluebird.map with a
// concurrency setting
const chunks = _.chunk(currentDirs, 100);
for (const chunk of chunks) {
await Promise.all(
chunk.map(async (dir) => {
const _files = await fs.readdir(dir, { withFileTypes: true });
for (const entry of _files) {
const fpath = path.join(dir, entry.name);
if (entry.isDirectory()) {
foundDirs.push(fpath);
} else {
files.push(fpath);
}
}
}),
);
}
return walk(foundDirs);
}
return walk([projectDir]);
}
/**
* Return the contents of a .dockerignore file at projectDir, as a string.
* Return an empty string if a .dockerignore file does not exist.
@ -211,7 +250,7 @@ async function readDockerIgnoreFile(projectDir: string): Promise<string> {
let dockerIgnoreStr = '';
try {
dockerIgnoreStr = await fs.readFile(dockerIgnorePath, 'utf8');
} catch (err) {
} catch (err: any) {
if (err.code !== 'ENOENT') {
throw new ExpectedError(
`Error reading file "${dockerIgnorePath}": ${err.message}`,
@ -269,7 +308,10 @@ export async function filterFilesWithDockerignore(
projectDir: string,
multiDockerignore: boolean,
serviceDirsByService: ServiceDirs,
): Promise<{ filteredFileList: FileStats[]; dockerignoreFiles: FileStats[] }> {
): Promise<{
filteredFileList: FileStats[];
dockerignoreFiles: FileStats[];
}> {
// path.resolve() also converts forward slashes to backslashes on Windows
projectDir = path.resolve(projectDir);
const root = '.' + path.sep;
@ -294,45 +336,65 @@ export async function filterFilesWithDockerignore(
const dockerignoreServiceDirs: string[] = multiDockerignore
? Object.keys(ignoreByDir).filter((dir) => dir && dir !== root)
: [];
const files = await listFiles(projectDir);
const dockerignoreFiles: FileStats[] = [];
const filteredFileList: FileStats[] = [];
const klaw = await import('klaw');
await new Promise((resolve, reject) => {
// Looking at klaw's source code, `preserveSymlinks` appears to only
// afect the `stats` argument to the `data` event handler
klaw(projectDir, { preserveSymlinks: false })
.on('error', reject)
.on('end', resolve)
.on('data', (item: { path: string; stats: Stats }) => {
const { path: filePath, stats } = item;
// With `preserveSymlinks: false`, filePath cannot be a symlink.
// filePath may be a directory or a regular or special file
if (!stats.isFile()) {
return;
}
// Because `files` can be of arbitrary length, process them in smaller batches
// to avoid out of memory errors.
// This approach is significantly faster than using Bluebird.map with a
// concurrency setting
const chunks = _.chunk(files, 750);
for (const chunk of chunks) {
await Promise.all(
chunk.map(async (filePath) => {
const relPath = path.relative(projectDir, filePath);
const fileInfo = {
filePath,
relPath,
stats,
};
// .dockerignore files are always added to a list of known dockerignore files
if (path.basename(relPath) === '.dockerignore') {
dockerignoreFiles.push(fileInfo);
const diStats = await fs.stat(filePath);
dockerignoreFiles.push({
filePath,
relPath,
stats: diStats,
});
}
for (const dir of dockerignoreServiceDirs) {
if (relPath.startsWith(dir)) {
if (!ignoreByDir[dir].ignores(relPath.substring(dir.length))) {
filteredFileList.push(fileInfo);
}
// First check if the file is ignored by a .dockerignore file in a service directory
const matchingDir = dockerignoreServiceDirs.find((dir) => {
return relPath.startsWith(dir);
});
// If the file is ignore in a service directory, exit early, otherwise check if it is ignored by the root .dockerignore file.
// Crucially, if the file is in a known service directory, and isn't ignored, the root .dockerignore file should not be checked.
if (matchingDir) {
if (
ignoreByDir[matchingDir].ignores(
relPath.substring(matchingDir.length),
)
) {
return;
}
} else if (ignoreByDir[root].ignores(relPath)) {
return;
}
if (!ignoreByDir[root].ignores(relPath)) {
filteredFileList.push(fileInfo);
// At this point we can do a final stat of the file, and check if it should be included
const stats = await fs.stat(filePath);
// filePath may be a special file that we should ignore, such as a socket
if (stats.isFile()) {
filteredFileList.push({
filePath,
relPath,
stats,
});
}
});
});
}),
);
}
return { filteredFileList, dockerignoreFiles };
}

View File

@ -317,12 +317,18 @@ async function getTarStream(build: RemoteBuild): Promise<Stream.Readable> {
Object.keys(build.opts.registrySecrets).length > 0
? preFinalizeCallback
: undefined;
return await tarDirectory(path.resolve(build.source), {
globalLogger.logDebug('Tarring all non-ignored files...');
const tarStartTime = Date.now();
const tarStream = await tarDirectory(path.resolve(build.source), {
preFinalizeCallback: preFinalizeCb,
convertEol: build.opts.convertEol,
multiDockerignore: build.opts.multiDockerignore,
nogitignore: build.nogitignore, // v13: delete this line
});
globalLogger.logDebug(
`Tarring complete in ${Date.now() - tarStartTime} ms`,
);
return tarStream;
} finally {
tarSpinner.stop();
}

View File

@ -1,3 +1,20 @@
/**
* @license
* Copyright 2018-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.
*/
const windowSize: { width?: number; height?: number } = {};
const updateWindowSize = () => {
@ -29,13 +46,6 @@ export = (stream: NodeJS.WriteStream = process.stdout) => {
const cursorDown = (rows: number = 0) => stream.write(`\u001B[${rows}B`);
const cursorHidden = () => {
const Bluebird = require('bluebird') as typeof import('bluebird');
return Bluebird.try(hideCursor).disposer(() => {
showCursor();
});
};
const write = (str: string) => stream.write(str);
const writeLine = (str: string) => stream.write(`${str}\n`);
@ -54,7 +64,6 @@ export = (stream: NodeJS.WriteStream = process.stdout) => {
currentWindowSize,
hideCursor,
showCursor,
cursorHidden,
cursorUp,
cursorDown,
write,

561
npm-shrinkwrap.json generated
View File

@ -1,6 +1,6 @@
{
"name": "balena-cli",
"version": "12.50.1",
"version": "12.55.5",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -915,12 +915,12 @@
}
},
"@babel/generator": {
"version": "7.14.5",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.5.tgz",
"integrity": "sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA==",
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.0.tgz",
"integrity": "sha512-RR8hUCfRQn9j9RPKEVXo9LiwoxLPYn6hNZlvUOR8tSnaxlD0p0+la00ZP9/SnRt6HchKr+X0fO2r8vrETiJGew==",
"dev": true,
"requires": {
"@babel/types": "^7.14.5",
"@babel/types": "^7.16.0",
"jsesc": "^2.5.1",
"source-map": "^0.5.0"
},
@ -934,41 +934,41 @@
}
},
"@babel/helper-function-name": {
"version": "7.14.5",
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz",
"integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==",
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.0.tgz",
"integrity": "sha512-BZh4mEk1xi2h4HFjWUXRQX5AEx4rvaZxHgax9gcjdLWdkjsY7MKt5p0otjsg5noXw+pB+clMCjw+aEVYADMjog==",
"dev": true,
"requires": {
"@babel/helper-get-function-arity": "^7.14.5",
"@babel/template": "^7.14.5",
"@babel/types": "^7.14.5"
"@babel/helper-get-function-arity": "^7.16.0",
"@babel/template": "^7.16.0",
"@babel/types": "^7.16.0"
}
},
"@babel/helper-get-function-arity": {
"version": "7.14.5",
"resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz",
"integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==",
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.0.tgz",
"integrity": "sha512-ASCquNcywC1NkYh/z7Cgp3w31YW8aojjYIlNg4VeJiHkqyP4AzIvr4qx7pYDb4/s8YcsZWqqOSxgkvjUz1kpDQ==",
"dev": true,
"requires": {
"@babel/types": "^7.14.5"
"@babel/types": "^7.16.0"
}
},
"@babel/helper-hoist-variables": {
"version": "7.14.5",
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.14.5.tgz",
"integrity": "sha512-R1PXiz31Uc0Vxy4OEOm07x0oSjKAdPPCh3tPivn/Eo8cvz6gveAeuyUUPB21Hoiif0uoPQSSdhIPS3352nvdyQ==",
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.0.tgz",
"integrity": "sha512-1AZlpazjUR0EQZQv3sgRNfM9mEVWPK3M6vlalczA+EECcPz3XPh6VplbErL5UoMpChhSck5wAJHthlj1bYpcmg==",
"dev": true,
"requires": {
"@babel/types": "^7.14.5"
"@babel/types": "^7.16.0"
}
},
"@babel/helper-split-export-declaration": {
"version": "7.14.5",
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz",
"integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==",
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.0.tgz",
"integrity": "sha512-0YMMRpuDFNGTHNRiiqJX19GjNXA4H0E8jZ2ibccfSxaCogbm3am5WN/2nQNj0YnQwGWM1J06GOcQ2qnh3+0paw==",
"dev": true,
"requires": {
"@babel/types": "^7.14.5"
"@babel/types": "^7.16.0"
}
},
"@babel/helper-validator-identifier": {
@ -1035,46 +1035,46 @@
}
},
"@babel/template": {
"version": "7.14.5",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz",
"integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==",
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.0.tgz",
"integrity": "sha512-MnZdpFD/ZdYhXwiunMqqgyZyucaYsbL0IrjoGjaVhGilz+x8YB++kRfygSOIj1yOtWKPlx7NBp+9I1RQSgsd5A==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.14.5",
"@babel/parser": "^7.14.5",
"@babel/types": "^7.14.5"
"@babel/code-frame": "^7.16.0",
"@babel/parser": "^7.16.0",
"@babel/types": "^7.16.0"
},
"dependencies": {
"@babel/code-frame": {
"version": "7.14.5",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz",
"integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==",
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz",
"integrity": "sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA==",
"dev": true,
"requires": {
"@babel/highlight": "^7.14.5"
"@babel/highlight": "^7.16.0"
}
},
"@babel/helper-validator-identifier": {
"version": "7.14.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz",
"integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==",
"version": "7.15.7",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz",
"integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==",
"dev": true
},
"@babel/highlight": {
"version": "7.14.5",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz",
"integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==",
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz",
"integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==",
"dev": true,
"requires": {
"@babel/helper-validator-identifier": "^7.14.5",
"@babel/helper-validator-identifier": "^7.15.7",
"chalk": "^2.0.0",
"js-tokens": "^4.0.0"
}
},
"@babel/parser": {
"version": "7.14.7",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.7.tgz",
"integrity": "sha512-X67Z5y+VBJuHB/RjwECp8kSl5uYi0BvRbNeWqkaJCVh+LiTPl19WBUfG627psSgp9rSf6ojuXghQM3ha6qHHdA==",
"version": "7.16.4",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.4.tgz",
"integrity": "sha512-6V0qdPUaiVHH3RtZeLIsc+6pDhbYzHR8ogA8w+f+Wc77DuXto19g2QUwveINoS34Uw+W8/hQDGJCx+i4n7xcng==",
"dev": true
},
"ansi-styles": {
@ -1115,52 +1115,52 @@
}
},
"@babel/traverse": {
"version": "7.14.7",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.7.tgz",
"integrity": "sha512-9vDr5NzHu27wgwejuKL7kIOm4bwEtaPQ4Z6cpCmjSuaRqpH/7xc4qcGEscwMqlkwgcXl6MvqoAjZkQ24uSdIZQ==",
"version": "7.16.3",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.3.tgz",
"integrity": "sha512-eolumr1vVMjqevCpwVO99yN/LoGL0EyHiLO5I043aYQvwOJ9eR5UsZSClHVCzfhBduMAsSzgA/6AyqPjNayJag==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.14.5",
"@babel/generator": "^7.14.5",
"@babel/helper-function-name": "^7.14.5",
"@babel/helper-hoist-variables": "^7.14.5",
"@babel/helper-split-export-declaration": "^7.14.5",
"@babel/parser": "^7.14.7",
"@babel/types": "^7.14.5",
"@babel/code-frame": "^7.16.0",
"@babel/generator": "^7.16.0",
"@babel/helper-function-name": "^7.16.0",
"@babel/helper-hoist-variables": "^7.16.0",
"@babel/helper-split-export-declaration": "^7.16.0",
"@babel/parser": "^7.16.3",
"@babel/types": "^7.16.0",
"debug": "^4.1.0",
"globals": "^11.1.0"
},
"dependencies": {
"@babel/code-frame": {
"version": "7.14.5",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz",
"integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==",
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz",
"integrity": "sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA==",
"dev": true,
"requires": {
"@babel/highlight": "^7.14.5"
"@babel/highlight": "^7.16.0"
}
},
"@babel/helper-validator-identifier": {
"version": "7.14.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz",
"integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==",
"version": "7.15.7",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz",
"integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==",
"dev": true
},
"@babel/highlight": {
"version": "7.14.5",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz",
"integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==",
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz",
"integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==",
"dev": true,
"requires": {
"@babel/helper-validator-identifier": "^7.14.5",
"@babel/helper-validator-identifier": "^7.15.7",
"chalk": "^2.0.0",
"js-tokens": "^4.0.0"
}
},
"@babel/parser": {
"version": "7.14.7",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.7.tgz",
"integrity": "sha512-X67Z5y+VBJuHB/RjwECp8kSl5uYi0BvRbNeWqkaJCVh+LiTPl19WBUfG627psSgp9rSf6ojuXghQM3ha6qHHdA==",
"version": "7.16.4",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.4.tgz",
"integrity": "sha512-6V0qdPUaiVHH3RtZeLIsc+6pDhbYzHR8ogA8w+f+Wc77DuXto19g2QUwveINoS34Uw+W8/hQDGJCx+i4n7xcng==",
"dev": true
},
"ansi-styles": {
@ -1201,19 +1201,19 @@
}
},
"@babel/types": {
"version": "7.14.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz",
"integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==",
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.0.tgz",
"integrity": "sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg==",
"dev": true,
"requires": {
"@babel/helper-validator-identifier": "^7.14.5",
"@babel/helper-validator-identifier": "^7.15.7",
"to-fast-properties": "^2.0.0"
},
"dependencies": {
"@babel/helper-validator-identifier": {
"version": "7.14.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz",
"integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==",
"version": "7.15.7",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz",
"integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==",
"dev": true
}
}
@ -1246,9 +1246,9 @@
"integrity": "sha512-o3/sRDyrXC75BUUziMAs+W5C02aVST0YqY5Ny31Ot3a+7CzK2XDRinMGywvK93tm2QVdL83HGkN483S62Xo9Dw=="
},
"@balena/lint": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/@balena/lint/-/lint-6.1.1.tgz",
"integrity": "sha512-i7FbC+3RNyOO0iNELmwXEfLfumJ4p/c9a/GP54U7ARMgzYzkFhVguwDqMjG8PE46DyjJyb5AAsekBkgbFWNMDA==",
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/@balena/lint/-/lint-6.2.0.tgz",
"integrity": "sha512-/17R2TAYIrjryiNUBVaDrFyeU5OQwTNybTo/gllJpR8DLa5+vnYGelCm+igHMU10u4wMgJVLz8SBNrEbaDW5iw==",
"dev": true,
"requires": {
"@types/glob": "^7.1.3",
@ -1265,9 +1265,9 @@
},
"dependencies": {
"@types/node": {
"version": "12.20.16",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.16.tgz",
"integrity": "sha512-6CLxw83vQf6DKqXxMPwl8qpF8I7THFZuIwLt4TnNsumxkp1VsRZWT8txQxncT/Rl2UojTsFzWgDG4FRMwafrlA==",
"version": "12.20.37",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.37.tgz",
"integrity": "sha512-i1KGxqcvJaLQali+WuypQnXwcplhtNtjs66eNsZpp2P2FL/trJJxx/VWsM0YCL2iMoIJrbXje48lvIQAQ4p2ZA==",
"dev": true
}
}
@ -2473,12 +2473,6 @@
"integrity": "sha512-AUxAGNIPr7wQmzdFMNhHy/RkR5kk8gSzAZIuCYY//6ZYJKHvnjezmoEYP34coPleUPnqrUWt03cCq7NzNaA/qg==",
"dev": true
},
"@types/estree": {
"version": "0.0.48",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.48.tgz",
"integrity": "sha512-LfZwXoGUDo0C3me81HXgkBg5CTQYb6xzEl+fNmbO4JdRiSKQ8A0GD1OBBvKAIsbCUgoyAty7m99GqqMQe784ew==",
"dev": true
},
"@types/event-stream": {
"version": "3.3.34",
"resolved": "https://registry.npmjs.org/@types/event-stream/-/event-stream-3.3.34.tgz",
@ -2754,9 +2748,9 @@
"dev": true
},
"@types/prettier": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.3.2.tgz",
"integrity": "sha512-eI5Yrz3Qv4KPUa/nSIAi0h+qX0XyewOliug5F2QAtuRg6Kjg6jfmxe1GIwoIRhZspD1A0RP8ANrPwvEXXtRFog==",
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.4.2.tgz",
"integrity": "sha512-ekoj4qOQYp7CvjX8ZDBgN86w3MqQhLE1hczEJbEIjgFEumDy+na/4AJAbLXfgEWFNB2pKadM5rPFtuSGMWK7xA==",
"dev": true
},
"@types/prettyjson": {
@ -2954,92 +2948,96 @@
"dev": true
},
"@vue/compiler-core": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.1.5.tgz",
"integrity": "sha512-TXBhFinoBaXKDykJzY26UEuQU1K07FOp/0Ie+OXySqqk0bS0ZO7Xvl7UmiTUPYcLrWbxWBR7Bs/y55AI0MNc2Q==",
"version": "3.2.23",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.23.tgz",
"integrity": "sha512-4ZhiI/orx+7EJ1B+0zjgvXMV2uRN+XBfG06UN2sJfND9rH5gtEQT3QmO4erum1o6Irl7y754W8/KSaDJh4EUQg==",
"dev": true,
"requires": {
"@babel/parser": "^7.12.0",
"@babel/types": "^7.12.0",
"@vue/shared": "3.1.5",
"estree-walker": "^2.0.1",
"@babel/parser": "^7.15.0",
"@vue/shared": "3.2.23",
"estree-walker": "^2.0.2",
"source-map": "^0.6.1"
},
"dependencies": {
"@babel/parser": {
"version": "7.14.7",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.7.tgz",
"integrity": "sha512-X67Z5y+VBJuHB/RjwECp8kSl5uYi0BvRbNeWqkaJCVh+LiTPl19WBUfG627psSgp9rSf6ojuXghQM3ha6qHHdA==",
"version": "7.16.4",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.4.tgz",
"integrity": "sha512-6V0qdPUaiVHH3RtZeLIsc+6pDhbYzHR8ogA8w+f+Wc77DuXto19g2QUwveINoS34Uw+W8/hQDGJCx+i4n7xcng==",
"dev": true
}
}
},
"@vue/compiler-dom": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.1.5.tgz",
"integrity": "sha512-ZsL3jqJ52OjGU/YiT/9XiuZAmWClKInZM2aFJh9gnsAPqOrj2JIELMbkIFpVKR/CrVO/f2VxfPiiQdQTr65jcQ==",
"version": "3.2.23",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.23.tgz",
"integrity": "sha512-X2Nw8QFc5lgoK3kio5ktM95nqmLUH+q+N/PbV4kCHzF1avqv/EGLnAhaaF0Iu4bewNvHJAAhhwPZFeoV/22nbw==",
"dev": true,
"requires": {
"@vue/compiler-core": "3.1.5",
"@vue/shared": "3.1.5"
"@vue/compiler-core": "3.2.23",
"@vue/shared": "3.2.23"
}
},
"@vue/compiler-sfc": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.1.5.tgz",
"integrity": "sha512-mtMY6xMvZeSRx9MTa1+NgJWndrkzVTdJ1pQAmAKQuxyb5LsHVvrgP7kcQFvxPHVpLVTORbTJWHaiqoKrJvi1iA==",
"version": "3.2.23",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.23.tgz",
"integrity": "sha512-Aw+pb50Q5zTjyvWod8mNKmYZDRGHJBptmNNWE+84ZxrzEztPgMz8cNYIzWGbwcFVkmJlhvioAMvKnB+LM/sjSA==",
"dev": true,
"requires": {
"@babel/parser": "^7.13.9",
"@babel/types": "^7.13.0",
"@types/estree": "^0.0.48",
"@vue/compiler-core": "3.1.5",
"@vue/compiler-dom": "3.1.5",
"@vue/compiler-ssr": "3.1.5",
"@vue/shared": "3.1.5",
"consolidate": "^0.16.0",
"estree-walker": "^2.0.1",
"hash-sum": "^2.0.0",
"lru-cache": "^5.1.1",
"@babel/parser": "^7.15.0",
"@vue/compiler-core": "3.2.23",
"@vue/compiler-dom": "3.2.23",
"@vue/compiler-ssr": "3.2.23",
"@vue/ref-transform": "3.2.23",
"@vue/shared": "3.2.23",
"estree-walker": "^2.0.2",
"magic-string": "^0.25.7",
"merge-source-map": "^1.1.0",
"postcss": "^8.1.10",
"postcss-modules": "^4.0.0",
"postcss-selector-parser": "^6.0.4",
"source-map": "^0.6.1"
},
"dependencies": {
"@babel/parser": {
"version": "7.14.7",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.7.tgz",
"integrity": "sha512-X67Z5y+VBJuHB/RjwECp8kSl5uYi0BvRbNeWqkaJCVh+LiTPl19WBUfG627psSgp9rSf6ojuXghQM3ha6qHHdA==",
"version": "7.16.4",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.4.tgz",
"integrity": "sha512-6V0qdPUaiVHH3RtZeLIsc+6pDhbYzHR8ogA8w+f+Wc77DuXto19g2QUwveINoS34Uw+W8/hQDGJCx+i4n7xcng==",
"dev": true
},
"lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
"dev": true,
"requires": {
"yallist": "^3.0.2"
}
}
}
},
"@vue/compiler-ssr": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.1.5.tgz",
"integrity": "sha512-CU5N7Di/a4lyJ18LGJxJYZS2a8PlLdWpWHX9p/XcsjT2TngMpj3QvHVRkuik2u8QrIDZ8OpYmTyj1WDNsOV+Dg==",
"version": "3.2.23",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.23.tgz",
"integrity": "sha512-Bqzn4jFyXPK1Ehqiq7e/czS8n62gtYF1Zfeu0DrR5uv+SBllh7LIvZjZU6+c8qbocAd3/T3I3gn2cZGmnDb6zg==",
"dev": true,
"requires": {
"@vue/compiler-dom": "3.1.5",
"@vue/shared": "3.1.5"
"@vue/compiler-dom": "3.2.23",
"@vue/shared": "3.2.23"
}
},
"@vue/ref-transform": {
"version": "3.2.23",
"resolved": "https://registry.npmjs.org/@vue/ref-transform/-/ref-transform-3.2.23.tgz",
"integrity": "sha512-gW0GD2PSAs/th7mC7tPB/UwpIQxclbApVtsDtscDmOJXb2+cdu60ny+SuHNgfrlUT/JqWKQHq7jFKO4woxLNaA==",
"dev": true,
"requires": {
"@babel/parser": "^7.15.0",
"@vue/compiler-core": "3.2.23",
"@vue/shared": "3.2.23",
"estree-walker": "^2.0.2",
"magic-string": "^0.25.7"
},
"dependencies": {
"@babel/parser": {
"version": "7.16.4",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.4.tgz",
"integrity": "sha512-6V0qdPUaiVHH3RtZeLIsc+6pDhbYzHR8ogA8w+f+Wc77DuXto19g2QUwveINoS34Uw+W8/hQDGJCx+i4n7xcng==",
"dev": true
}
}
},
"@vue/shared": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.1.5.tgz",
"integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==",
"version": "3.2.23",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.23.tgz",
"integrity": "sha512-U+/Jefa0QfXUF2qVy9Dqlrb6HKJSr9/wJcM66wXmWcTOoqg7hOWzF4qruDle51pyF4x3wMn6TSH54UdjKjCKMA==",
"dev": true
},
"@yarnpkg/lockfile": {
@ -3634,13 +3632,13 @@
}
},
"balena-config-json": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/balena-config-json/-/balena-config-json-4.1.1.tgz",
"integrity": "sha512-zpYt3SbXxe1OgmLcA8RVecrBgVH1sThYVAd9irfp/NOjKgk0bRiVjx2rG7yLROB7gcLxYDeivP4pLTTDtpylHQ==",
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/balena-config-json/-/balena-config-json-4.2.0.tgz",
"integrity": "sha512-k0/2y1FmZni2EAVYHfWFme/F1xchAcvHweDA9QCEHm83LU0+j6v/Hk9RZ1IhipRty2jDSEw1BorYVrYmx9+Psg==",
"requires": {
"balena-image-fs": "^7.0.4",
"balena-sdk": "^15.2.1",
"lodash": "^4.17.19"
"balena-image-fs": "^7.0.6",
"file-disk": "^8.0.1",
"partitioninfo": "^6.0.2"
}
},
"balena-device-init": {
@ -3785,9 +3783,9 @@
}
},
"balena-image-manager": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/balena-image-manager/-/balena-image-manager-7.0.3.tgz",
"integrity": "sha512-KLb/5JksYxCR7JClq+MAuCMYPNMAXB9MgqubUvzj4mI/Yec3XD4xZ2BMITwINrl4sMKui/G7t/R5tBNFZsFe9w==",
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/balena-image-manager/-/balena-image-manager-7.1.1.tgz",
"integrity": "sha512-ny0BrjdK23RFKG0VDk6PceYMtiekyqJEoh91Q/JxjWwayoV+CUajQJYN9YE95i2Qp/iqI97Hlm1V5N4E1YHLEg==",
"requires": {
"balena-sdk": "^15.2.1",
"mime": "^2.4.6",
@ -3814,9 +3812,9 @@
}
},
"balena-preload": {
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/balena-preload/-/balena-preload-10.5.0.tgz",
"integrity": "sha512-tgnTyOSOLB3HxIqlR1NFrTsy1eiiew5Vzmplb82/eZc/vJTrOqal2tFNn6aFay6UQ8+OASUJwANC99zBu1e8mQ==",
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/balena-preload/-/balena-preload-11.0.0.tgz",
"integrity": "sha512-2LuPTw6LoVxuasGhn+cLyA5n7kjvwBtQmBsg08KNhcbEpWLkzrV5jhpNxlMPUuoC2xcMv5Rcg6FWbY87QV5zYQ==",
"requires": {
"archiver": "^3.1.1",
"balena-sdk": "^15.44.0",
@ -3828,6 +3826,7 @@
"get-port": "^3.2.0",
"lodash": "^4.17.21",
"node-cleanup": "^2.1.2",
"request": "^2.88.2",
"request-promise": "^4.2.6",
"resin-cli-visuals": "^1.8.0",
"tar-fs": "^2.1.1",
@ -3885,11 +3884,6 @@
"readable-stream": "^3.4.0"
}
},
"moment": {
"version": "2.29.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
"integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ=="
},
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
@ -3900,14 +3894,6 @@
"util-deprecate": "^1.0.1"
}
},
"semver": {
"version": "7.3.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
"requires": {
"lru-cache": "^6.0.0"
}
},
"tar-fs": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
@ -3933,11 +3919,6 @@
}
}
},
"tslib": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
},
"zip-stream": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-2.1.3.tgz",
@ -4243,15 +4224,9 @@
"dev": true
},
"big-integer": {
"version": "1.6.48",
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.48.tgz",
"integrity": "sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w=="
},
"big.js": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
"integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==",
"dev": true
"version": "1.6.50",
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.50.tgz",
"integrity": "sha512-+O2uoQWFRo8ysZNo/rjtri2jIwjr3XfeAgRjAUADRqGG+ZITvyn8J1kvXLTaKVr3hhGXk+f23tKfdzmklVM9vQ=="
},
"binary": {
"version": "0.3.0",
@ -5208,12 +5183,6 @@
"integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==",
"dev": true
},
"colorette": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz",
"integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==",
"dev": true
},
"colors": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
@ -5428,15 +5397,6 @@
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
"integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
},
"consolidate": {
"version": "0.16.0",
"resolved": "https://registry.npmjs.org/consolidate/-/consolidate-0.16.0.tgz",
"integrity": "sha512-Nhl1wzCslqXYTJVDyJCu3ODohy9OfBMB5uD2BiBTzd7w+QY0lBzafkR8y8755yMYHAaMD4NuzbAw03/xzfw+eQ==",
"dev": true,
"requires": {
"bluebird": "^3.7.2"
}
},
"content-disposition": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
@ -5785,12 +5745,6 @@
"integrity": "sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==",
"dev": true
},
"cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
"dev": true
},
"csso": {
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/csso/-/csso-3.5.1.tgz",
@ -6042,9 +5996,9 @@
},
"dependencies": {
"@babel/parser": {
"version": "7.14.7",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.7.tgz",
"integrity": "sha512-X67Z5y+VBJuHB/RjwECp8kSl5uYi0BvRbNeWqkaJCVh+LiTPl19WBUfG627psSgp9rSf6ojuXghQM3ha6qHHdA==",
"version": "7.16.4",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.4.tgz",
"integrity": "sha512-6V0qdPUaiVHH3RtZeLIsc+6pDhbYzHR8ogA8w+f+Wc77DuXto19g2QUwveINoS34Uw+W8/hQDGJCx+i4n7xcng==",
"dev": true
},
"arrify": {
@ -6054,9 +6008,9 @@
"dev": true
},
"camelcase": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz",
"integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==",
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.1.tgz",
"integrity": "sha512-tVI4q5jjFV5CavAU8DXfza/TJcZutVKo/5Foskmsqcm0MsL91moHvwiGNnqaa2o6PF/7yT5ikDRcVcl8Rj6LCA==",
"dev": true
},
"debug": {
@ -6069,9 +6023,9 @@
}
},
"is-core-module": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.5.0.tgz",
"integrity": "sha512-TXCMSDsEHMEEZ6eCA8rwRDbLu55MRGmrctljsBX/2v1d9/GzqHOxW5c5oPSgrUt2vBFXebu9rGqckXGPWOlYpg==",
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz",
"integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==",
"dev": true,
"requires": {
"has": "^1.0.3"
@ -6777,12 +6731,6 @@
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"emojis-list": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
"integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
"dev": true
},
"encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
@ -8366,15 +8314,6 @@
}
}
},
"generic-names": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/generic-names/-/generic-names-2.0.1.tgz",
"integrity": "sha512-kPCHWa1m9wGG/OwQpeweTwM/PYiQLrUIxXbt/P4Nic3LbGjCP0YwrALHW1uNLKZ0LIMg+RF+XRlj2ekT9ZlZAQ==",
"dev": true,
"requires": {
"loader-utils": "^1.1.0"
}
},
"get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
@ -9393,12 +9332,6 @@
"resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz",
"integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw=="
},
"hash-sum": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-2.0.0.tgz",
"integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==",
"dev": true
},
"he": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
@ -9743,18 +9676,6 @@
"safer-buffer": ">= 2.1.2 < 3"
}
},
"icss-replace-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz",
"integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=",
"dev": true
},
"icss-utils": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz",
"integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==",
"dev": true
},
"ieee754": {
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
@ -10946,28 +10867,6 @@
"strip-bom": "^3.0.0"
}
},
"loader-utils": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz",
"integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==",
"dev": true,
"requires": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^1.0.1"
},
"dependencies": {
"json5": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
"dev": true,
"requires": {
"minimist": "^1.2.0"
}
}
}
},
"locate-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
@ -11030,12 +10929,6 @@
"resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz",
"integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI="
},
"lodash.camelcase": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
"integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=",
"dev": true
},
"lodash.defaults": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
@ -11870,15 +11763,6 @@
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
},
"merge-source-map": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz",
"integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==",
"dev": true,
"requires": {
"source-map": "^0.6.1"
}
},
"merge-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
@ -12368,7 +12252,7 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"multicast-dns": {
"version": "git+https://github.com/resin-io-modules/multicast-dns.git#a15c63464eb43e8925b187ed5cb9de6892e8aacc",
"version": "git+https://github.com/resin-io-modules/multicast-dns.git#db98d68b79bbefc936b6799de9de1038ba49f85d",
"from": "git+https://github.com/resin-io-modules/multicast-dns.git#listen-on-all-interfaces",
"requires": {
"dns-packet": "^1.0.1",
@ -13868,6 +13752,12 @@
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
},
"picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
"dev": true
},
"picomatch": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz",
@ -14089,91 +13979,24 @@
"integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs="
},
"postcss": {
"version": "8.3.5",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.5.tgz",
"integrity": "sha512-NxTuJocUhYGsMiMFHDUkmjSKT3EdH4/WbGF6GCi1NDGk+vbcUTun4fpbOqaPtD8IIsztA2ilZm2DhYCuyN58gA==",
"version": "8.4.1",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.1.tgz",
"integrity": "sha512-WqLs/TTzXdG+/A4ZOOK9WDZiikrRaiA+eoEb/jz2DT9KUhMNHgP7yKPO8vwi62ZCsb703Gwb7BMZwDzI54Y2Ag==",
"dev": true,
"requires": {
"colorette": "^1.2.2",
"nanoid": "^3.1.23",
"source-map-js": "^0.6.2"
"nanoid": "^3.1.30",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.1"
},
"dependencies": {
"nanoid": {
"version": "3.1.23",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz",
"integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==",
"version": "3.1.30",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.30.tgz",
"integrity": "sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ==",
"dev": true
}
}
},
"postcss-modules": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/postcss-modules/-/postcss-modules-4.1.3.tgz",
"integrity": "sha512-dBT39hrXe4OAVYJe/2ZuIZ9BzYhOe7t+IhedYeQ2OxKwDpAGlkEN/fR0fGnrbx4BvgbMReRX4hCubYK9cE/pJQ==",
"dev": true,
"requires": {
"generic-names": "^2.0.1",
"icss-replace-symbols": "^1.1.0",
"lodash.camelcase": "^4.3.0",
"postcss-modules-extract-imports": "^3.0.0",
"postcss-modules-local-by-default": "^4.0.0",
"postcss-modules-scope": "^3.0.0",
"postcss-modules-values": "^4.0.0",
"string-hash": "^1.1.1"
}
},
"postcss-modules-extract-imports": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz",
"integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==",
"dev": true
},
"postcss-modules-local-by-default": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz",
"integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==",
"dev": true,
"requires": {
"icss-utils": "^5.0.0",
"postcss-selector-parser": "^6.0.2",
"postcss-value-parser": "^4.1.0"
}
},
"postcss-modules-scope": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz",
"integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==",
"dev": true,
"requires": {
"postcss-selector-parser": "^6.0.4"
}
},
"postcss-modules-values": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz",
"integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==",
"dev": true,
"requires": {
"icss-utils": "^5.0.0"
}
},
"postcss-selector-parser": {
"version": "6.0.6",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz",
"integrity": "sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg==",
"dev": true,
"requires": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
}
},
"postcss-value-parser": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz",
"integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==",
"dev": true
},
"prebuild-install": {
"version": "5.3.6",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.6.tgz",
@ -14208,9 +14031,9 @@
"integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw="
},
"prettier": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz",
"integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==",
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.0.tgz",
"integrity": "sha512-FM/zAKgWTxj40rH03VxzIPdXmj39SwSjwG0heUcNFwI+EMZJnY93yAiKXM3dObIKAM5TA88werc8T/EwhB45eg==",
"dev": true
},
"pretty-bytes": {
@ -16156,9 +15979,9 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"sass": {
"version": "1.35.2",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.35.2.tgz",
"integrity": "sha512-jhO5KAR+AMxCEwIH3v+4zbB2WB0z67V1X0jbapfVwQQdjHZUGUyukpnoM6+iCMfsIUC016w9OPKQ5jrNOS9uXw==",
"version": "1.43.5",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.43.5.tgz",
"integrity": "sha512-WuNm+eAryMgQluL7Mbq9M4EruyGGMyal7Lu58FfnRMVWxgUzIvI7aSn60iNt3kn5yZBMR7G84fAGDcwqOF5JOg==",
"dev": true,
"requires": {
"chokidar": ">=3.0.0 <4.0.0"
@ -16738,9 +16561,9 @@
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
},
"source-map-js": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-0.6.2.tgz",
"integrity": "sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==",
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.1.tgz",
"integrity": "sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA==",
"dev": true
},
"source-map-resolve": {
@ -17048,12 +16871,6 @@
"editor": "^1.0.0"
}
},
"string-hash": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz",
"integrity": "sha1-6Kr8CsGFW0Zmkp7X3RJ1311sgRs=",
"dev": true
},
"string-template": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz",

View File

@ -1,6 +1,6 @@
{
"name": "balena-cli",
"version": "12.50.1",
"version": "12.55.5",
"description": "The official balena Command Line Interface",
"main": "./build/app.js",
"homepage": "https://github.com/balena-io/balena-cli",
@ -53,7 +53,7 @@
"build:src": "npm run lint && npm run build:fast && npm run build:test && npm run build:doc && npm run build:completion",
"build:fast": "gulp pages && tsc && npx oclif-dev manifest",
"build:test": "tsc -P ./tsconfig.dev.json --noEmit && tsc -P ./tsconfig.js.json --noEmit",
"build:doc": "ts-node --transpile-only automation/capitanodoc/index.ts > doc/cli.markdown",
"build:doc": "ts-node --transpile-only automation/capitanodoc/index.ts > docs/balena-cli.md",
"build:completion": "node completion/generate-completion.js",
"build:standalone": "ts-node --transpile-only automation/run.ts build:standalone",
"build:installer": "ts-node --transpile-only automation/run.ts build:installer",
@ -71,7 +71,9 @@
"catch-uncommitted": "ts-node --transpile-only automation/run.ts catch-uncommitted",
"ci": "npm run test && npm run catch-uncommitted",
"watch": "gulp watch",
"lint": "balena-lint -e ts -e js --fix automation/ completion/ lib/ typings/ tests/ bin/balena bin/balena-dev gulpfile.js .mocharc.js .mocharc-standalone.js",
"lint": "npm run lint-tsconfig && npm run lint-other",
"lint-tsconfig": "balena-lint -e ts -e js -t tsconfig.dev.json --fix automation/ lib/ tests/ typings/",
"lint-other": "balena-lint -e ts -e js --fix bin/balena bin/balena-dev completion/ gulpfile.js .mocharc.js .mocharc-standalone.js",
"update": "ts-node --transpile-only ./automation/update-module.ts",
"prepare": "echo {} > bin/.fast-boot.json",
"prepublishOnly": "npm run build"
@ -113,7 +115,7 @@
]
},
"devDependencies": {
"@balena/lint": "^6.1.1",
"@balena/lint": "^6.2.0",
"@oclif/config": "^1.17.0",
"@oclif/dev-cli": "^1.26.0",
"@oclif/parser": "^3.8.5",
@ -201,12 +203,12 @@
"@types/update-notifier": "^4.1.1",
"@zeit/dockerignore": "0.0.3",
"JSONStream": "^1.0.3",
"balena-config-json": "^4.1.1",
"balena-config-json": "^4.2.0",
"balena-device-init": "^6.0.0",
"balena-errors": "^4.7.1",
"balena-image-fs": "^7.0.6",
"balena-image-manager": "^7.0.3",
"balena-preload": "^10.5.0",
"balena-image-manager": "^7.1.1",
"balena-preload": "^11.0.0",
"balena-release": "^3.2.0",
"balena-sdk": "^15.51.1",
"balena-semver": "^2.3.0",
@ -218,6 +220,7 @@
"chalk": "^3.0.0",
"chokidar": "^3.4.3",
"cli-truncate": "^2.1.0",
"cli-ux": "^5.5.1",
"color-hash": "^1.0.3",
"columnify": "^1.5.2",
"common-tags": "^1.7.2",
@ -232,7 +235,6 @@
"express": "^4.13.3",
"fast-boot2": "^1.1.0",
"fast-levenshtein": "^3.0.0",
"file-disk": "^8.0.1",
"filenamify": "^4.3.0",
"get-stdin": "^8.0.0",
"glob": "^7.1.7",
@ -257,7 +259,6 @@
"node-unzip-2": "^0.2.8",
"oclif": "^1.18.1",
"open": "^7.1.0",
"partitioninfo": "^6.0.2",
"patch-package": "^6.4.7",
"prettyjson": "^1.1.3",
"progress-stream": "^2.0.0",
@ -289,6 +290,6 @@
"windosu": "^0.3.0"
},
"versionist": {
"publishedAt": "2021-09-30T00:16:32.621Z"
"publishedAt": "2021-12-13T23:29:59.373Z"
}
}

View File

@ -32,6 +32,7 @@ import {
ExpectedTarStreamFilesByService,
getDockerignoreWarn1,
getDockerignoreWarn2,
getDockerignoreWarn3,
} from '../projects';
const repoPath = path.normalize(path.join(__dirname, '..', '..'));
@ -184,6 +185,10 @@ describe('balena build', function () {
`[Info] No "docker-compose.yml" file found at "${projectPath}"`,
`[Info] Creating default composition with source: "${projectPath}"`,
'[Build] main Step 1/4 : FROM busybox',
...getDockerignoreWarn1(
[path.join(projectPath, 'src', '.dockerignore')],
'build',
),
];
if (isWindows) {
const fname = path.join(projectPath, 'src', 'windows-crlf.sh');
@ -383,7 +388,6 @@ describe('balena build', function () {
'file1.sh': { fileSize: 12, type: 'file' },
},
service2: {
'.dockerignore': { fileSize: 12, type: 'file' },
'Dockerfile-alt': { fileSize: 13, type: 'file' },
'file2-crlf.sh': {
fileSize: isWindows ? 12 : 14,
@ -513,13 +517,7 @@ describe('balena build', function () {
'[Build] service1 Step 1/4 : FROM busybox',
'[Build] service2 Step 1/4 : FROM busybox',
],
...[
`[Info] ---------------------------------------------------------------------------`,
'[Info] The --multi-dockerignore option is being used, and a .dockerignore file was',
'[Info] found at the project source (root) directory. Note that this file will not',
'[Info] be used to filter service subdirectories. See "balena help build".',
`[Info] ---------------------------------------------------------------------------`,
],
...getDockerignoreWarn3('build'),
];
if (isWindows) {
expectedResponseLines.push(
@ -602,13 +600,7 @@ describe('balena build', function () {
'[Build] service1 Step 1/4 : FROM busybox',
'[Build] service2 Step 1/4 : FROM busybox',
],
...[
`[Info] ---------------------------------------------------------------------------`,
'[Info] The --multi-dockerignore option is being used, and a .dockerignore file was',
'[Info] found at the project source (root) directory. Note that this file will not',
'[Info] be used to filter service subdirectories. See "balena help build".',
`[Info] ---------------------------------------------------------------------------`,
],
...getDockerignoreWarn3('build'),
];
if (isWindows) {
expectedResponseLines.push(

View File

@ -32,6 +32,7 @@ import {
ExpectedTarStreamFiles,
ExpectedTarStreamFilesByService,
getDockerignoreWarn1,
getDockerignoreWarn3,
} from '../projects';
const repoPath = path.normalize(path.join(__dirname, '..', '..'));
@ -396,13 +397,7 @@ describe('balena deploy', function () {
'[Build] service1 Step 1/4 : FROM busybox',
'[Build] service2 Step 1/4 : FROM busybox',
],
...[
`[Info] ---------------------------------------------------------------------------`,
'[Info] The --multi-dockerignore option is being used, and a .dockerignore file was',
'[Info] found at the project source (root) directory. Note that this file will not',
'[Info] be used to filter service subdirectories. See "balena help deploy".',
`[Info] ---------------------------------------------------------------------------`,
],
...getDockerignoreWarn3('deploy'),
];
if (isWindows) {
expectedResponseLines.push(

View File

@ -49,7 +49,6 @@ if (process.platform !== 'win32') {
api.expectGetApplication();
api.expectGetConfigDeviceTypes();
api.expectDownloadConfig();
api.expectApplicationProvisioning();
const command: string[] = [
`os configure ${tmpPath}`,
@ -59,6 +58,7 @@ if (process.platform !== 'win32') {
'--config-app-update-poll-interval 10',
'--config-network ethernet',
'--initial-device-name testDeviceName',
'--provisioning-key-name testKey',
];
const { err } = await runCommand(command.join(' '));

View File

@ -26,9 +26,11 @@ import { expectStreamNoCRLF, testPushBuildStream } from '../docker-build';
import { cleanOutput, runCommand } from '../helpers';
import {
addRegSecretsEntries,
exists,
ExpectedTarStreamFiles,
getDockerignoreWarn1,
getDockerignoreWarn2,
getDockerignoreWarn3,
setupDockerignoreTestData,
} from '../projects';
@ -36,6 +38,7 @@ const repoPath = path.normalize(path.join(__dirname, '..', '..'));
const projectsPath = path.join(repoPath, 'tests', 'test-data', 'projects');
const itNoV13 = isV13() ? it.skip : it;
const itNoWin = process.platform === 'win32' ? it.skip : it;
const commonResponseLines = {
'build-POST-v3.json': [
@ -451,12 +454,10 @@ describe('balena push', function () {
const projectPath = path.join(projectsPath, 'docker-compose', 'basic');
const expectedFiles: ExpectedTarStreamFiles = {
'.balena/balena.yml': { fileSize: 197, type: 'file' },
'.dockerignore': { fileSize: 22, type: 'file' },
'docker-compose.yml': { fileSize: 332, type: 'file' },
'service1/Dockerfile.template': { fileSize: 144, type: 'file' },
'service1/file1.sh': { fileSize: 12, type: 'file' },
'service2/Dockerfile-alt': { fileSize: 13, type: 'file' },
'service2/.dockerignore': { fileSize: 12, type: 'file' },
'service2/file2-crlf.sh': {
fileSize: isWindows ? 12 : 14,
testStream: isWindows ? expectStreamNoCRLF : undefined,
@ -503,7 +504,6 @@ describe('balena push', function () {
const projectPath = path.join(projectsPath, 'docker-compose', 'basic');
const expectedFiles: ExpectedTarStreamFiles = {
'.balena/balena.yml': { fileSize: 197, type: 'file' },
'.dockerignore': { fileSize: 22, type: 'file' },
'docker-compose.yml': { fileSize: 332, type: 'file' },
'service1/Dockerfile.template': { fileSize: 144, type: 'file' },
'service1/file1.sh': { fileSize: 12, type: 'file' },
@ -524,13 +524,7 @@ describe('balena push', function () {
);
const expectedResponseLines: string[] = [
...commonResponseLines[responseFilename],
...[
`[Info] ---------------------------------------------------------------------------`,
'[Info] The --multi-dockerignore option is being used, and a .dockerignore file was',
'[Info] found at the project source (root) directory. Note that this file will not',
'[Info] be used to filter service subdirectories. See "balena help push".',
`[Info] ---------------------------------------------------------------------------`,
],
...getDockerignoreWarn3('push'),
];
if (isWindows) {
expectedResponseLines.push(
@ -553,6 +547,66 @@ describe('balena push', function () {
responseCode: 200,
});
});
// Skip on Windows because this test uses Unix domain sockets
itNoWin('should create the expected tar stream (socket file)', async () => {
// This test creates project files dynamically in a temp dir, where
// a Unix domain socket file is created and listened on. A specific
// reason use use a temp dir is that Unix domain socket paths are
// limited in length to just over 100 characters, while the project
// paths in the test-data folder easily exceed that limit.
const tmp = await import('tmp');
tmp.setGracefulCleanup();
const projectPath = await new Promise<string>((resolve, reject) => {
const opts = { template: 'tmp-XXXXXX', unsafeCleanup: true };
tmp.dir(opts, (e, p) => (e ? reject(e) : resolve(p)));
});
console.error(`[debug] Temp project dir: ${projectPath}`);
// Create a Unix Domain Socket file that should not be included in the tar stream
const net = await import('net');
const server = net.createServer();
const socketPath = path.join(projectPath, 'socket');
await new Promise<void>((resolve, reject) => {
server.on('error', reject);
try {
server.listen(socketPath, resolve);
} catch (e) {
reject(e);
}
});
console.error(`[debug] Checking existence of socket at '${socketPath}'`);
expect(await exists(socketPath), 'Socket existence').to.be.true;
await fs.writeFile(path.join(projectPath, 'Dockerfile'), 'FROM busybox\n');
const expectedFiles: ExpectedTarStreamFiles = {
Dockerfile: { fileSize: 13, type: 'file' },
};
const responseFilename = 'build-POST-v3.json';
const responseBody = await fs.readFile(
path.join(builderResponsePath, responseFilename),
'utf8',
);
await testPushBuildStream({
builderMock: builder,
commandLine: `push testApp -s ${projectPath}`,
expectedFiles,
expectedQueryParams: commonQueryParams,
expectedResponseLines: commonResponseLines[responseFilename],
projectPath,
responseBody,
responseCode: 200,
});
// Terminate Unix Domain Socket server
await new Promise<void>((resolve, reject) => {
server.close((e) => (e ? reject(e) : resolve()));
});
expect(await exists(socketPath), 'Socket existence').to.be.false;
});
});
describe('balena push: project validation', function () {

View File

@ -0,0 +1,255 @@
/**
* @license
* Copyright 2020-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.
*/
/* tslint:disable: prefer-const no-empty */
import rewire = require('rewire');
import sinon = require('sinon');
import { expect } from 'chai';
const dataItem = {
name: 'item1',
id: 1,
thing_color: 'blue',
thing_shape: 'square',
};
const dataSet = [
{
name: 'item1',
id: 1,
thing_color: 'red',
thing_shape: 'square',
},
{
name: 'item2',
id: 2,
thing_color: 'blue',
thing_shape: 'round',
},
];
describe('outputData', function () {
let outputData: any;
let outputDataSetSpy: any;
let outputDataItemSpy: any;
this.beforeEach(() => {
const output = rewire('../../build/framework/output');
outputDataSetSpy = sinon.spy();
outputDataItemSpy = sinon.spy();
output.__set__('outputDataSet', outputDataSetSpy);
output.__set__('outputDataItem', outputDataItemSpy);
outputData = output.__get__('outputData');
});
it('should call outputDataSet function when data param is an array', async () => {
await outputData(dataSet);
expect(outputDataSetSpy.called).to.be.true;
expect(outputDataItemSpy.called).to.be.false;
});
it('should call outputDataItem function when data param is an object', async () => {
await outputData(dataItem);
expect(outputDataSetSpy.called).to.be.false;
expect(outputDataItemSpy.called).to.be.true;
});
});
describe('outputDataSet', function () {
let outputDataSet: any;
let printLineSpy: any;
this.beforeEach(() => {
const output = rewire('../../build/framework/output');
printLineSpy = sinon.spy();
output.__set__('printLine', printLineSpy);
outputDataSet = output.__get__('outputDataSet');
});
it('should only output fields specified in `fields` param, in that order', async () => {
const fields = ['id', 'name', 'thing_color'];
const options = {};
await outputDataSet(dataSet, fields, options);
// check correct number of rows (2 data, 2 header)
expect(printLineSpy.callCount).to.equal(4);
const headerLine = printLineSpy.firstCall.firstArg.toLowerCase();
// check we have fields we specified
fields.forEach((f) => {
expect(headerLine).to.include(f.replace(/_/g, ' '));
});
// check we don't have fields we didn't specify
expect(headerLine).to.not.include('thing_shape');
// check order
// split header using the `name` column as delimiter
const splitHeader = headerLine.split('name');
expect(splitHeader[0]).to.include('id');
expect(splitHeader[1]).to.include('thing');
});
/*
it('should output fields in the order specified in `fields` param', async () => {
const fields = ['thing_color', 'id', 'name'];
const options = {};
await outputDataSet(dataSet, fields, options);
const headerLine = printLineSpy.firstCall.firstArg.toLowerCase();
// split header using the `it` column as delimiter
const splitHeader = headerLine.split('id');
expect(splitHeader[0]).to.include('thing');
expect(splitHeader[1]).to.include('name');
});
*/
it('should only output fields specified in `options.fields` if present', async () => {
const fields = ['name', 'id', 'thing_color', 'thing_shape'];
const options = {
// test all formats
fields: 'Name,thing_color,Thing shape',
};
await outputDataSet(dataSet, fields, options);
const headerLine = printLineSpy.firstCall.firstArg.toLowerCase();
// check we have fields we specified
expect(headerLine).to.include('name');
expect(headerLine).to.include('thing color');
expect(headerLine).to.include('thing shape');
// check we don't have fields we didn't specify
expect(headerLine).to.not.include('id');
});
it('should output records in order specified by `options.sort` if present', async () => {
const fields = ['name', 'id', 'thing_color', 'thing_shape'];
const options = {
sort: 'thing shape',
'no-header': true,
};
await outputDataSet(dataSet, fields, options);
// blue should come before red
expect(printLineSpy.getCall(0).firstArg).to.include('blue');
expect(printLineSpy.getCall(1).firstArg).to.include('red');
});
it('should only output records that match filter specified by `options.filter` if present', async () => {
const fields = ['name', 'id', 'thing_color', 'thing_shape'];
const options = {
filter: 'thing color=red',
'no-header': true,
};
await outputDataSet(dataSet, fields, options);
// check correct number of rows (1 matched data, no-header)
expect(printLineSpy.callCount).to.equal(1);
expect(printLineSpy.getCall(0).firstArg).to.include('red');
});
it('should output data in json format, if `options.json` true', async () => {
const fields = ['name', 'thing_color', 'thing_shape'];
const options = {
json: true,
};
// TODO: I've run into an oclif cli-ux bug, where numbers are output as strings in json
// (this can be seen by including 'id' in the fields list above).
// Issue opened: https://github.com/oclif/cli-ux/issues/309
// For now removing id for this test.
const clonedDataSet = JSON.parse(JSON.stringify(dataSet));
clonedDataSet.forEach((d: any) => {
delete d.id;
});
const expectedJson = JSON.stringify(clonedDataSet, undefined, 2);
await outputDataSet(dataSet, fields, options);
expect(printLineSpy.callCount).to.equal(1);
expect(printLineSpy.getCall(0).firstArg).to.equal(expectedJson);
});
});
describe('outputDataItem', function () {
let outputDataItem: any;
let printLineSpy: any;
this.beforeEach(() => {
const output = rewire('../../build/framework/output');
printLineSpy = sinon.spy();
output.__set__('printLine', printLineSpy);
outputDataItem = output.__get__('outputDataItem');
});
it('should only output fields specified in `fields` param, in that order', async () => {
const fields = ['id', 'name', 'thing_color'];
const options = {};
await outputDataItem(dataItem, fields, options);
// check correct number of rows (3 fields)
expect(printLineSpy.callCount).to.equal(3);
// check we have fields we specified
fields.forEach((f, index) => {
const kvPair = printLineSpy.getCall(index).firstArg.split(':');
expect(kvPair[0].toLowerCase()).to.include(f.replace(/_/g, ' '));
expect(kvPair[1]).to.include((dataItem as any)[f]);
});
});
it('should only output fields specified in `options.fields` if present', async () => {
const fields = ['name', 'id', 'thing_color', 'thing_shape'];
const options = {
// test all formats
fields: 'Name,thing_color,Thing shape',
};
const expectedFields = ['name', 'thing_color', 'thing_shape'];
await outputDataItem(dataItem, fields, options);
// check correct number of rows (3 fields)
expect(printLineSpy.callCount).to.equal(3);
// check we have fields we specified
expectedFields.forEach((f, index) => {
const kvPair = printLineSpy.getCall(index).firstArg.split(':');
expect(kvPair[0].toLowerCase()).to.include(f.replace(/_/g, ' '));
expect(kvPair[1]).to.include((dataItem as any)[f]);
});
});
it('should output data in json format, if `options.json` true', async () => {
const fields = ['name', 'id', 'thing_color', 'thing_shape'];
const options = {
json: true,
};
const expectedJson = JSON.stringify(dataItem, undefined, 2);
await outputDataItem(dataItem, fields, options);
expect(printLineSpy.callCount).to.equal(1);
expect(printLineSpy.getCall(0).firstArg).to.equal(expectedJson);
});
});

View File

@ -15,12 +15,9 @@
* limitations under the License.
*/
import * as fs from 'fs';
import { promises as fs } from 'fs';
import * as path from 'path';
import type { Headers } from 'tar-stream';
import { promisify } from 'util';
const statAsync = promisify(fs.stat);
export interface ExpectedTarStreamFile {
contents?: string;
@ -49,6 +46,15 @@ export const projectsPath = path.join(
'projects',
);
export async function exists(fPath: string) {
try {
await fs.stat(fPath);
return true;
} catch (e) {
return false;
}
}
export async function setupDockerignoreTestData({ cleanup = false } = {}) {
const { copy, remove } = await import('fs-extra');
const dockerignoreProjDir = path.join(
@ -78,7 +84,7 @@ export async function addRegSecretsEntries(
): Promise<string> {
const regSecretsPath = path.join(projectsPath, 'registry-secrets.json');
expectedFiles['.balena/registry-secrets.json'] = {
fileSize: (await statAsync(regSecretsPath)).size,
fileSize: (await fs.stat(regSecretsPath)).size,
type: 'file',
};
return regSecretsPath;
@ -115,3 +121,13 @@ export function getDockerignoreWarn2(paths: string[], cmd: string) {
);
return lines;
}
export function getDockerignoreWarn3(cmd: string) {
return [
`[Info] ---------------------------------------------------------------------------`,
'[Info] The --multi-dockerignore option is being used, and a .dockerignore file was',
'[Info] found at the project source (root) directory. Note that this file will not',
`[Info] be used to filter service subdirectories. See "balena help ${cmd}".`,
`[Info] ---------------------------------------------------------------------------`,
];
}

View File

@ -1 +1,2 @@
service1/test-ignore*
**/.dockerignore

View File

@ -74,7 +74,6 @@ describe('detectEncoding() function', function () {
'node_modules/.bin/gulp',
'node_modules/.bin/tsc',
'node_modules/.bin/balena-lint',
'node_modules/.bin/balena-preload',
'node_modules/.bin/catch-uncommitted',
];

View File

@ -1,22 +0,0 @@
/**
* @license
* Copyright 2020 Balena Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.
*/
declare module 'balena-config-json' {
export function read(image: string, type: string): Promise<any>;
export function write(image: string, type: string, config: any): Promise;
}

View File

@ -22,4 +22,6 @@ declare module 'balena-image-manager' {
deviceType: string,
versionOrRange: string,
): Promise<NodeJS.ReadableStream & { mime: string }>;
export function isESR(version: string): boolean;
}