Compare commits

...

28 Commits

Author SHA1 Message Date
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
23 changed files with 634 additions and 325 deletions

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,208 @@
- 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,57 @@ 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.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

@ -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

@ -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
@ -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 &#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:
@ -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 &#60;image&#62; &#60;device-type&#62;

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

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

View File

@ -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,

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

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

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

@ -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[],

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,

39
npm-shrinkwrap.json generated
View File

@ -1,6 +1,6 @@
{
"name": "balena-cli",
"version": "12.50.1",
"version": "12.52.2",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -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",

View File

@ -1,6 +1,6 @@
{
"name": "balena-cli",
"version": "12.50.1",
"version": "12.52.2",
"description": "The official balena Command Line Interface",
"main": "./build/app.js",
"homepage": "https://github.com/balena-io/balena-cli",
@ -205,8 +205,8 @@
"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-24T18:31:20.576Z"
}
}

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

@ -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

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