mirror of
https://github.com/balena-io/balena-cli.git
synced 2025-06-24 18:45:07 +00:00
Compare commits
32 Commits
developmen
...
v12.53.0
Author | SHA1 | Date | |
---|---|---|---|
52138d41eb | |||
5acdc63068 | |||
b546e4dd97 | |||
e4870916e2 | |||
3ca93448cd | |||
f66395e2d5 | |||
952d782e90 | |||
d53c9b3c50 | |||
2f706c0200 | |||
d64b6deb81 | |||
55fc9b2ade | |||
6c29d0ae27 | |||
f46452f6de | |||
c166ec7597 | |||
7325c79888 | |||
2a29b386eb | |||
23b07f8a41 | |||
6d641b4841 | |||
7b498149b1 | |||
ae5ea0f4e8 | |||
f635f648da | |||
3d4e2cf823 | |||
ef3b630887 | |||
19040ccb6c | |||
8e712ac910 | |||
c401ed35ac | |||
94be97313b | |||
48053ecefc | |||
cc60e86507 | |||
bd774e8553 | |||
c493c33e38 | |||
112a7b8194 |
@ -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',
|
||||
};
|
||||
|
@ -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"
|
||||
|
@ -1,3 +1,227 @@
|
||||
- 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
|
||||
|
56
CHANGELOG.md
56
CHANGELOG.md
@ -4,6 +4,62 @@ 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.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]
|
||||
|
@ -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/).
|
||||
|
@ -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.
|
133
doc/cli.markdown
133
doc/cli.markdown
@ -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
|
||||
|
||||
@ -2240,6 +2261,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 +2276,28 @@ device type
|
||||
|
||||
### Options
|
||||
|
||||
#### --esr
|
||||
|
||||
select balenaOS ESR versions
|
||||
|
||||
## os download <type>
|
||||
|
||||
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:
|
||||
|
||||
@ -2294,11 +2323,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 <image> <device-type>
|
||||
|
||||
@ -2577,16 +2608,16 @@ the wifi key to use (used only if --network is set to wifi)
|
||||
|
||||
## config inject <file>
|
||||
|
||||
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 +2629,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 +2637,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 +2694,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 <key> <value>
|
||||
|
||||
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 +2726,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
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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,7 +57,7 @@ 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,
|
||||
};
|
||||
@ -81,7 +81,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');
|
||||
}
|
||||
|
@ -21,34 +21,38 @@ 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 async run() {
|
||||
@ -61,9 +65,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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,34 +21,45 @@ 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;
|
||||
@ -65,10 +76,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');
|
||||
}
|
||||
|
@ -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,13 +64,11 @@ 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 async run() {
|
||||
@ -82,14 +83,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');
|
||||
|
@ -43,18 +43,39 @@ 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';
|
||||
|
@ -187,6 +187,8 @@ export default class OsConfigureCmd extends Command {
|
||||
help: cf.help,
|
||||
};
|
||||
|
||||
public static authenticated = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
|
||||
OsConfigureCmd,
|
||||
|
@ -34,21 +34,22 @@ 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',
|
||||
@ -78,11 +79,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,
|
||||
|
@ -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'));
|
||||
}
|
||||
}
|
||||
|
@ -130,15 +130,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 +148,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 +188,29 @@ 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)) {
|
||||
if (version[0] === 'v') {
|
||||
version = version.slice(1);
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
const vs = (
|
||||
(await getBalenaSdk().models.hostapp.getAllOsVersions([deviceType]))[
|
||||
deviceType
|
||||
] ?? []
|
||||
).filter((v) => v.osType === 'default');
|
||||
const vs = await getFormattedOsVersions(deviceType, version === 'menu-esr');
|
||||
|
||||
const choices = vs.map((v) => ({
|
||||
value: v.rawVersion,
|
||||
name: `v${v.rawVersion}` + (v.isRecommended ? ' (recommended)' : ''),
|
||||
name: v.formattedVersion,
|
||||
}));
|
||||
|
||||
return getCliForm().ask({
|
||||
@ -214,3 +220,35 @@ 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 versions: SDK.OsVersion[] = (
|
||||
(await getBalenaSdk().models.hostapp.getAvailableOsVersions([deviceType]))[
|
||||
deviceType
|
||||
] ?? []
|
||||
)
|
||||
.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) {
|
||||
throw new ExpectedError(stripIndent`
|
||||
Error: No balenaOS versions found for device type '${deviceType}'.
|
||||
Double-check the device type slug with 'balena devices supported'.`);
|
||||
}
|
||||
return versions;
|
||||
}
|
||||
|
@ -113,6 +113,13 @@ 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',
|
||||
|
@ -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) {
|
||||
|
@ -1306,15 +1306,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 +1423,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[],
|
||||
|
@ -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,
|
||||
|
51
npm-shrinkwrap.json
generated
51
npm-shrinkwrap.json
generated
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "balena-cli",
|
||||
"version": "12.50.1",
|
||||
"version": "12.53.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@ -3634,13 +3634,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 +3785,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.0",
|
||||
"resolved": "https://registry.npmjs.org/balena-image-manager/-/balena-image-manager-7.1.0.tgz",
|
||||
"integrity": "sha512-W8Etn0PBQJUlTJ1m1rCuQQ8TTLdkBoNP2SRHQqez2HANQp+T38wFjQXVx9PhQ6J7eqOYuW87SkJiKLpnmlz79Q==",
|
||||
"requires": {
|
||||
"balena-sdk": "^15.2.1",
|
||||
"mime": "^2.4.6",
|
||||
@ -3814,9 +3814,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 +3828,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 +3886,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 +3896,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 +3921,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,9 +4226,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=="
|
||||
"version": "1.6.50",
|
||||
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.50.tgz",
|
||||
"integrity": "sha512-+O2uoQWFRo8ysZNo/rjtri2jIwjr3XfeAgRjAUADRqGG+ZITvyn8J1kvXLTaKVr3hhGXk+f23tKfdzmklVM9vQ=="
|
||||
},
|
||||
"big.js": {
|
||||
"version": "5.2.2",
|
||||
|
10
package.json
10
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "balena-cli",
|
||||
"version": "12.50.1",
|
||||
"version": "12.53.0",
|
||||
"description": "The official balena Command Line Interface",
|
||||
"main": "./build/app.js",
|
||||
"homepage": "https://github.com/balena-io/balena-cli",
|
||||
@ -201,12 +201,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.0",
|
||||
"balena-preload": "^11.0.0",
|
||||
"balena-release": "^3.2.0",
|
||||
"balena-sdk": "^15.51.1",
|
||||
"balena-semver": "^2.3.0",
|
||||
@ -289,6 +289,6 @@
|
||||
"windosu": "^0.3.0"
|
||||
},
|
||||
"versionist": {
|
||||
"publishedAt": "2021-09-30T00:16:32.621Z"
|
||||
"publishedAt": "2021-11-25T02:11:13.790Z"
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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 () {
|
||||
|
@ -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] ---------------------------------------------------------------------------`,
|
||||
];
|
||||
}
|
||||
|
@ -1 +1,2 @@
|
||||
service1/test-ignore*
|
||||
**/.dockerignore
|
||||
|
@ -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',
|
||||
];
|
||||
|
||||
|
22
typings/balena-config-json/index.d.ts
vendored
22
typings/balena-config-json/index.d.ts
vendored
@ -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;
|
||||
}
|
Reference in New Issue
Block a user