Compare commits

...

1029 Commits

Author SHA1 Message Date
27363d34cc v11.18.3 2022-10-25 13:37:05 +03:00
7b3a8b4ecf Add NYC code coverage report
After running tests, a code coverage report can be found at ./coverage/lcov-report/index.html

Change-type: patch
Signed-off-by: Lucian <lucian.buzzo@gmail.com>
2019-11-19 11:51:27 +00:00
a8bd5d332a v11.18.2 2019-11-15 14:41:22 +02:00
0462574d8d Merge pull request #1501 from balena-io/1485-filter-discontinued-devices
Hide discontinued device types in `balena devices supported`, `balena app create`.
2019-11-15 13:39:41 +01:00
1325fb8c9a Use helpers version of cleanOutput in tests.
Simplify expect semantics in tests.

Change-type: patch
Signed-off-by: Scott Lowe <scott@balena.io>
2019-11-15 12:01:56 +01:00
cf42dca777 Hide discontinued device types in balena app create.
Add basic tests for `balena app create`

Connects-to: #1485
Change-type: patch
Signed-off-by: Scott Lowe <scott@balena.io>
2019-11-15 12:01:56 +01:00
afd97bd304 Hide discontinued device types in balena devices supported.
Add tests for action.
Convert action to TypeScript.

Connects-to: #1485
Change-type: patch
Signed-off-by: Scott Lowe <scott@balena.io>
2019-11-15 12:01:55 +01:00
45ce442cf2 v11.18.1 2019-11-15 12:18:56 +02:00
e8c5c9e9ef Merge pull request #1514 from balena-io/1511-invalid-containerPort
Fix "Invalid containerPort" error with EXPOSE instructions in local QEMU builds
2019-11-15 10:17:10 +00:00
caac6855da Fix "Invalid containerPort" error with EXPOSE instructions in local QEMU builds
Bump docker-qemu-transpose package to v1.0.2

Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-11-15 09:31:14 +00:00
e379900526 v11.18.0 2019-11-12 23:47:46 +02:00
0bc1e7057f Merge pull request #1507 from balena-io/1504-os-configure
Fix 'os configure --config', and migrate it to oclif + TypeScript
2019-11-12 21:46:07 +00:00
d94a74dfee Fix 'os configure --config', and migrate it to oclif + TypeScript
Also add more non-interactive configuration options:
--config-network, --config-wifi-*, --config-app-update-poll-interval

Change-type: minor
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-11-12 21:09:18 +00:00
a25a52c21b Error handling: add ExpectedError type as alternative to exitWithExpectedError()
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-11-12 19:36:43 +00:00
268bc36843 v11.17.5 2019-11-12 21:14:37 +02:00
8e7eaaae24 Merge pull request #1506 from balena-io/miscellaneous-fixes
Miscellaneous commits (preload docs, long stack traces, exit code, ExpectedError)
2019-11-12 19:12:58 +00:00
b69ba0b617 Improve command usage format consistency in 'balena help' output
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-11-10 00:54:22 +00:00
1bedf937f8 Add Scott (@srlowe) to CODEOWNERS file
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-11-10 00:54:22 +00:00
74a521a271 Set the process exit code to 1 (instead of 0) for unknown commands
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-11-10 00:54:22 +00:00
6376ac28d5 Fix logged-in check in "logs" and "ssh" commands (async/await usage)
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-11-10 00:54:22 +00:00
2222a90884 Enable debug-mode "long stack traces" for Bluebird promises (async code)
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-11-10 00:54:22 +00:00
09f04be77d Fix Node.js download link and improve installation instructions
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-11-10 00:54:22 +00:00
636ecaf4e0 Improve help and docs (CLI install dependencies) for the preload command
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-11-09 14:04:50 +00:00
07fa504c78 v11.17.4 2019-11-06 13:08:49 +02:00
5f74023bb8 Merge pull request #1496 from balena-io/support-optional-containers
Support optional containers when pushing to local target
2019-11-06 11:07:15 +00:00
b31e253fa4 Support optional containers when pushing to local target
Change-type: patch
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-11-06 10:30:28 +00:00
f75f00e4d0 v11.17.3 2019-11-04 14:45:54 +02:00
13deb83517 Merge pull request #1472 from balena-io/fix-cache-usage
Use all available on-device images during local push
2019-11-04 12:43:46 +00:00
62b7d2fd1a Use all available on-device images during local push
Change-type: patch
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-11-04 10:27:35 +00:00
aa5062ea6f v11.17.2 2019-11-01 18:05:45 +02:00
394f2e0999 Merge pull request #1493 from balena-io/1491-bugfix-no-arguments
Fix warnings when executing CLI without arguments.
2019-11-01 17:03:33 +01:00
4a967b126b Fix warnings when executing CLI without arguments.
Change-type: patch
Signed-off-by: Scott Lowe <scott@balena.io>
2019-11-01 16:21:36 +01:00
5f780a0947 v11.17.1 2019-10-29 12:50:40 +02:00
0671ee0bef Merge pull request #1488 from balena-io/fix-undefined-contract
Fix potentialy undefined build task when livepushing
2019-10-29 10:48:43 +00:00
55b2b5a467 Fix potentialy undefined build task when livepushing
Change-type: patch
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-10-28 17:50:02 +00:00
f7a4160c3f v11.17.0 2019-10-23 14:03:38 +03:00
294290908b Merge pull request #1484 from balena-io/update-livepush-for-build-args
Update livepush to support build arguments
2019-10-23 12:01:47 +01:00
8ed4f547e0 Update livepush to support build arguments
Change-type: minor
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-10-22 13:24:00 +01:00
3393e797d0 v11.16.6 2019-10-22 13:49:34 +03:00
7de059b95e Merge pull request #1480 from balena-io/1479-catalina-warning
Add installation note regarding macOS Catalina (10.15)
2019-10-22 12:47:36 +02:00
c2b0091f86 Add installation note regarding macOS Catalina (10.15)
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-10-22 12:21:25 +02:00
d96ad93e1e v11.16.5 2019-10-22 13:18:17 +03:00
e0373e6f19 Merge pull request #1481 from balena-io/husky-shrinkwrap
Bump livepush and reconcile husky dependency
2019-10-22 12:16:48 +02:00
f50f169ff0 Bump livepush and reconcile husky dependency
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-10-22 11:01:02 +02:00
eada1ab87e v11.16.4 2019-10-18 04:40:16 +03:00
51d4ffc5d9 Merge pull request #1477 from balena-io/move-check-npm-version
Move npm version check from npm 'preinstall' to git 'pre-commit' hook
2019-10-18 02:38:24 +01:00
59a7b9d12b Move npm version check from npm 'preinstall' to git 'pre-commit' hook
This should allow end users to use npm v6.4.1 that ships with Node 8,
while still requiring CLI developers to use npm v6.9.0 or later.

Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-10-18 00:25:23 +01:00
3bb5ca50b2 v11.16.3 2019-10-17 22:38:45 +03:00
353ee4a576 Merge pull request #1476 from balena-io/fix-check-npm-version
Fix npm install (missing automation/check-npm-version.js)
2019-10-17 20:36:54 +01:00
eaffc2574f Fix npm install (missing automation/check-npm-version.js)
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-10-17 19:57:09 +01:00
5142274e9d v11.16.2 2019-10-17 21:36:16 +03:00
a05b592f00 Merge pull request #1475 from balena-io/check-npm-version
Add npm preinstall check for npm version 6.9.0 or later
2019-10-17 19:34:11 +01:00
e7c89cf77c Add npm preinstall check for npm version 6.9.0 or later
Older npm versions cause the npm-shrinkwrap.json file to be incorrectly
updated. This should avoid regression bugs related to issue #1332.
https://github.com/balena-io/balena-cli/issues/1332

Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-10-17 15:43:21 +01:00
09444f0cff v11.16.1 2019-10-17 14:23:47 +03:00
f329acc2ec Merge pull request #1474 from balena-io/re-run-npm-update
Fix shrinkwrap unwanted changes from older npm version
2019-10-17 14:22:05 +03:00
ee3a0cc630 Fix shrinkwrap unwanted changes from older npm version
Change-type: patch
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2019-10-17 13:59:21 +03:00
b40498786a v11.16.0 2019-10-16 23:33:34 +03:00
43f551dd2e Merge pull request #1473 from balena-io/1471-support-private-device-types
Add support for private device types
2019-10-16 23:32:08 +03:00
1f4382bea8 Add support for private device types
Resolves: #1471
Depends-on: https://github.com/balena-io/balena-sdk/pull/761
Depends-on: https://github.com/balena-io-modules/balena-config-json/pull/15
HQ: https://github.com/balena-io/balena/pull/1740
See: https://www.flowdock.com/app/rulemotion/resin-tech/threads/IGXgYQxfnJfGO8rtTMOJ2yUZ1RU
See: https://www.flowdock.com/app/rulemotion/resin-frontend/threads/FNuwNgrDfJGRcsoJI7yJV-bx5DD
Change-type: minor
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2019-10-16 16:26:19 +03:00
96bc8a829e Fix missing code formatting
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2019-10-16 15:47:05 +03:00
0cdf4d95e5 v11.15.0 2019-10-16 14:13:14 +03:00
a6ac0ee17b Merge pull request #1470 from balena-io/1064-release-tags-by-commit
Support managing tags using release commit hashes
2019-10-16 14:11:40 +03:00
1b943bdf7d Support managing tags using release commit hashes
The sdk version in the shrinkwrap already
supports setting tags by commit hashes and as a
result this already works in the cli as of v11.9.6.
This PR just adds some docs and some extra
handling when the commit param prefix is all
numeric.

Resolves: #1064
Change-type: minor
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2019-10-16 09:12:17 +03:00
f831bb4645 v11.14.5 2019-10-16 02:19:15 +03:00
82c486b202 Merge pull request #1455 from balena-io/1428-os-initialize
Fix 'os initialize' (auto sudo execution)
2019-10-16 00:17:28 +01:00
02b888f7c1 Fix privilege elevation for standalone zip package on Windows (windosu)
* Add pkgExec internal command
* Patch windosu to be aware of process.pkg and use pkgExec

Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-10-15 21:47:26 +01:00
69c97fed09 Remove 'internal sudo' command
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-10-15 21:47:26 +01:00
05cb89725e Fix privilege elevation (sudo) for 'os initialize', 'join', 'leave'
* sudo shell arguments required escaping for 'os initialize'
* sudo was not working for standalone zip packages (incorrect
  Node.js path in argv[0])
* Interactive 'join' and 'leave' not working on Windows because
  'windosu' does not capture stderr.

Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-10-15 21:47:26 +01:00
43ae9b672c v11.14.4 2019-10-14 19:53:05 +03:00
27270dd589 Merge pull request #1469 from balena-io/local-contract-support
Support container contracts when pushing to local devices
2019-10-14 17:51:27 +01:00
0c5ed7adfb Support container contracts when pushing to local devices
Change-type: patch
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-10-14 17:18:56 +01:00
4280a3cd4a v11.14.3 2019-10-14 16:55:01 +03:00
b8944ba65c Merge pull request #1467 from balena-io/update-windows-dev-install-instructions
Selected chore commits and docs updates
2019-10-14 14:53:19 +01:00
a8fcd85f1a Update/improve npm install instructions for Windows
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-10-14 13:34:02 +01:00
cc45d872c7 Update CLI tests to pass with Node 12
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-10-14 01:27:34 +01:00
e0e76a1aa8 Avoid stack trace and Sentry report if user answers No to confirmation prompts
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-10-10 16:02:41 +01:00
d9b417e9e5 Sort the output of balena devices supported alphabetically by slug
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-10-10 16:02:41 +01:00
101a4bc209 Chore: replace typings/intercept-stdout with @types/intercept-stdout
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-10-10 16:02:41 +01:00
449461afae v11.14.2 2019-10-09 16:22:55 +03:00
1987206b94 Merge pull request #1464 from balena-io/1132-build-windows-docker-qemu
Fix 'balena build --emulated' on Windows (including default docker socket)
2019-10-09 14:21:20 +01:00
7dd33adfd1 Fix 'balena build --emulated' on Windows (including default docker socket)
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-10-09 13:10:49 +01:00
63fe3d41cc v11.14.1 2019-10-04 16:00:33 +03:00
59e4eb5143 Merge pull request #1461 from balena-io/1355-SecretRemovalError-multibuild-script
Bump resin-multibuild to 4.3.1 (fix more cases of SecretRemovalError)
2019-10-04 13:58:47 +01:00
78e627a471 Bump resin-multibuild to 4.3.1 (fix more cases of SecretRemovalError)
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-10-04 12:33:39 +01:00
3adc06d11b v11.14.0 2019-10-03 14:33:52 +03:00
85fc9daa8b Merge pull request #1460 from balena-io/1459-env-rm-config-var
Allow deleting config vars with 'env rm'
2019-10-03 14:32:00 +03:00
d463a2f0e5 Allow deleting config vars with 'env rm'
Resolves: #1459
Change-type: minor
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2019-10-03 13:51:33 +03:00
863eae42c5 v11.13.1 2019-09-30 17:36:18 +03:00
4b373d5ed4 Merge pull request #1452 from balena-io/troubleshooting-ssh-line-wrapping
Update TROUBLESHOOTING doc re 'balena ssh' line wrapping / cursor behavior
2019-09-30 15:34:39 +01:00
4077da1491 Update TROUBLESHOOTING doc re 'balena ssh' line wrapping / cursor behavior
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-09-30 14:16:10 +01:00
3718473f05 v11.13.0 2019-09-30 15:59:02 +03:00
54bcbb5d91 Merge pull request #1454 from balena-io/1453-api-image-download
Bump balena-sdk to v12.12.0 to stop using image maker endpoints
2019-09-30 15:57:32 +03:00
408f739e2d Bump balena-sdk to v12.12.0 to stop using image maker endpoints
Resolves: #1453
Depends-on: https://github.com/balena-io/balena-sdk/pull/735
Depends-on: https://github.com/balena-io-modules/balena-image-manager/pull/45
HQ: https://github.com/balena-io/balena/issues/1744
Change-type: minor
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2019-09-30 15:36:46 +03:00
f481d5edae v11.12.0 2019-09-25 13:20:45 +03:00
cff81ae86d Merge pull request #1449 from balena-io/1448-mix-rpi-to-armv7-aarch64
Allow mixing armv7 & aarch64 devices in RPi 1 / zero apps
2019-09-25 13:19:08 +03:00
f9d6a0ee72 Allow mixing armv7 & aarch64 devices in RPi 1 / zero apps
Resolves: #1448
HQ: https://github.com/balena-io/balena/issues/1905
Change-type: minor
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2019-09-24 13:22:13 +03:00
9ea999f15f v11.11.3 2019-09-23 11:47:15 +03:00
eac229ab7c Merge pull request #1438 from balena-io/debug-to-stderr
Send all debug output to stderr
2019-09-23 09:45:53 +01:00
7a865b2e15 Send all debug output to stderr
Change-type: patch
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-09-19 13:25:40 +01:00
32c588db55 v11.11.2 2019-09-19 13:23:45 +03:00
aa670ad6f1 Merge pull request #1439 from balena-io/roman/mixpanel-token
Use balena analytics project name
2019-09-19 13:22:12 +03:00
c1ba73a2da Use balena analytics project name
It's needed to properly integrate CLI with balena
analytics proxy service.

Change-type: patch
Signed-off-by: Roman Mazur <roman@balena.io>
2019-09-18 18:00:35 +03:00
606b6c88ab v11.11.1 2019-09-18 16:12:54 +03:00
a6f329750c Merge pull request #1414 from balena-io/1391-refactor-oclif
Migrate "env rename" and "envs" to oclif and refactor the preparser
2019-09-18 14:11:14 +01:00
c07b28e694 Migrate 'envs' and 'env rename' commands to oclif
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-09-18 12:54:31 +01:00
b3bef9e556 Simplify/refactor 'env add' and 'env rm' implementation
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-09-17 17:20:16 +01:00
2ff427fb90 Refactor oclif integration and preparser
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-09-17 17:20:16 +01:00
3d89b0c7a1 v11.11.0 2019-09-16 21:59:27 +03:00
90db52db47 Merge pull request #1435 from balena-io/1431-balena-join-compatible-arch
Support `balena join` to applications of compatible architectures
2019-09-16 21:57:07 +03:00
87004621ce Support balena join to applications of compatible architectures
Resolves: #1431
Change-type: minor
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2019-09-16 21:31:07 +03:00
de8089b0bb v11.10.0 2019-09-16 21:13:23 +03:00
ae691391b6 Merge pull request #1434 from balena-io/1433-move-same-arch-apps
Support moving devices to applications of a compatible architecture
2019-09-16 21:11:02 +03:00
a64b36fdb9 Support moving devices to applications of a compatible architecture
Connects-to: #1433
Change-type: minor
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2019-09-16 19:16:51 +03:00
db5c473952 v11.9.7 2019-09-16 10:26:12 +03:00
2b0bff8f16 Merge pull request #1430 from balena-io/add-scrutinizer-config
ci: add scrutinizer config
2019-09-16 09:24:45 +02:00
2e7f606667 ci: add scrutinizer config
Change-type: patch
Signed-off-by: Stevche Radevski <stevche@balena.io>
2019-09-13 15:58:52 +02:00
73e9f801e2 v11.9.6 2019-09-12 15:27:34 +03:00
9a40f20004 Merge pull request #1425 from balena-io/1304-bump-balena-sdk
Update balena-sdk to v12 and mitigate MaxListenersExceededWarning
2019-09-12 13:25:51 +01:00
6631fb5a69 Mitigate "MaxListenersExceededWarning" by reusing Logger instance
The full warning output was:
(node:43572) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 drain listeners added. Use emitter.setMaxListeners() to increase limit
(node:43572) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 error listeners added. Use emitter.setMaxListeners() to increase limit
(node:43572) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 close listeners added. Use emitter.setMaxListeners() to increase limit

Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-09-12 01:49:40 +01:00
f76ca1804a Update balena-sdk from 11.18.0 to 12.10.0 (pre-req for #1153 and #1304)
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-09-11 16:08:30 +01:00
d25ad328f3 v11.9.5 2019-09-11 14:29:15 +03:00
dfbcdc2c1b Merge pull request #1416 from balena-io/1415-warn-emulation
Detect Docker Desktop (Docker for Mac) and warn about architecture emulation
2019-09-11 12:27:30 +01:00
d484c957bb Detect Docker Desktop (Docker for Mac) and warn about architecture emulation
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-09-11 12:05:07 +01:00
70714b6feb v11.9.4 2019-09-11 13:49:06 +03:00
c9db5fd856 Merge pull request #1423 from balena-io/1419-fix-pkg-target-node
Fix Node.js version errors in standalone package on Windows
2019-09-11 11:47:09 +01:00
d54a709e7c Fix mismatched Node.js version errors in standalone package on Windows
Resolves: #1419
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-09-11 01:17:26 +01:00
09f20ecc1c Update etcher-sdk to v2.0.14 (pre-requisite to supporting Node 12)
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-09-10 16:08:07 +01:00
b8b0221ce6 v11.9.3 2019-09-02 03:56:54 +03:00
596d1bdc21 Merge pull request #1410 from balena-io/oclif-mixpanel
Add missing oclif-based commands to mixpanel tracking
2019-09-02 01:55:02 +01:00
fb1dce9dbb Add missing oclif-based commands to mixpanel tracking
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-09-01 23:42:21 +01:00
fedfb603f6 v11.9.2 2019-08-30 21:00:06 +03:00
8478b95e45 Merge pull request #1405 from balena-io/1359-update-release-descriptions
Deploy scripts: edit GitHub release descriptions given semver ranges
2019-08-30 18:57:33 +01:00
7bb2741a5a Deploy scripts: edit GitHub release descriptions given semver ranges
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-08-30 18:09:58 +01:00
693a438d42 v11.9.1 2019-08-29 00:59:52 +03:00
2a8e68cec2 Merge pull request #1412 from balena-io/log-tarring
logging: note that tarring is occurring
2019-08-28 23:57:14 +02:00
e3435c66df logging: note that tarring is occurring
As noted in #1411, this tarring can be particularly expensive so cluing
in the user may help alleviate pain

Connects-to: #1411
Change-type: patch
Signed-off-by: Matthew McGinn <matthew@balena.io>
2019-08-28 20:37:50 +02:00
192b751e57 v11.9.0 2019-08-22 16:33:03 +03:00
d9643eb59e Merge pull request #1403 from balena-io/1355-SecretRemovalError
Fix 'SecretRemovalError' and enable certain emulated builds on a remote device
2019-08-22 14:31:21 +01:00
19c3178062 Enable emulated builds on remote devices running a different OS as the CLI
E.g. "balena build -e -h <IP> -p 2375" with the CLI running on a Mac laptop,
using balenaEngine on an Intel NUC device, building an image for the RPi (ARM
image arch). Previously, QEMU setup by the CLI assumed that docker ran on the
same OS as the CLI (Docker for Mac has built-in binfmt_misc support and does
not require additional setup, but balenaEngine on Linux requires explicit QEMU
setup.)

Change-type: minor
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-08-22 13:37:07 +01:00
516fa90a20 Fix SecretRemovalError (balena build) when docker daemon arch != target arch
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-08-21 22:25:18 +01:00
56aabad8ad v11.8.3 2019-08-20 04:34:24 +03:00
4a80beac35 Merge pull request #1400 from balena-io/1397-fix-build-registry-secrets
Fix failing registry secrets authentication under certain conditions
2019-08-20 02:32:59 +01:00
cdedc58ec1 Fix failing registry secrets authentication under certain conditions
('balena build' and 'balena deploy')

Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-08-19 16:41:59 +01:00
ed084edc48 v11.8.2 2019-08-19 18:29:03 +03:00
b435a0e7ac Merge pull request #1392 from balena-io/ssh-formatting
balena-ssh: add info about remote vs local connections
2019-08-19 17:25:30 +02:00
731db63e78 balena-ssh: add info about remote vs local connections
small formatting fixups

Closes: https://github.com/balena-io/docs/issues/974
Change-type: patch
Signed-off-by: Matthew McGinn <matthew@balena.io>
2019-08-19 16:14:26 +02:00
2ee2bc8b02 v11.8.1 2019-08-16 09:51:01 +03:00
b4c99dc03a Merge pull request #1398 from balena-io/oclif-args
Fix incorrect arguments passed to oclif bootstrap function
2019-08-16 07:49:09 +01:00
730c09989d Fix incorrect arguments passed to oclif bootstrap function
Adds tests for existing oclif commands to avoid regressions

Change-type: patch
Signed-off-by: Lucian <lucian.buzzo@gmail.com>
2019-08-15 18:26:42 +01:00
9e0733a143 v11.8.0 2019-08-13 11:40:19 +03:00
8dd1106a44 Merge pull request #1393 from balena-io/cli-tests
Implement full command testing, beginning with "balena version"
2019-08-13 09:38:32 +01:00
4d389bb6cc Implement full command testing, beginning with "balena version"
This also modifies the core CLI to be fed command programatically, which
is useful for being able to do thing like mock endpoints with tools like
"nock", and provide an easier debugging experience.
The tests utilise a "runCommand" helper that intercepts and captures
stdout/stderr writes and returns them once the command has finished
running. At this point the test implementation can parse the
stdout/stderr logs and assess nock interceptions to determine if the
command ran correctly.
This change also homogenises debug messages to start with `[debug]`,
however this is not strictly enforced by linting rules.

Change-type: minor
Signed-off-by: Lucian <lucian.buzzo@gmail.com>
2019-08-12 14:50:41 +01:00
6d6b3cb1a3 v11.7.10 2019-08-12 16:38:49 +03:00
e84482fbbe Merge pull request #1383 from balena-io/1380-env-rm
Improvements to the "env rm" command and oclif migration
2019-08-12 14:36:49 +01:00
f1d9c29786 Fix bug where "env rm" fails silently if an additional arg is present
Fixes #1380

Argument parsing of "env rm" command was improved by migrating it to oclif

Change-type: patch
Signed-off-by: Lucian <lucian.buzzo@gmail.com>
2019-08-12 13:47:31 +01:00
34f4c1f6cc Exit with a warning if "env rm" id value is not an integer
Change-type: patch
Signed-off-by: Lucian <lucian.buzzo@gmail.com>
2019-08-09 15:12:30 +01:00
2390ddc02d v11.7.9 2019-08-09 17:08:09 +03:00
70561705e5 Merge pull request #1387 from balena-io/tests-3
Tests part 3: Convert tests to Typescript
2019-08-09 15:06:35 +01:00
05d58d8248 Fix CI instability when building Typescript
The error appears to happen when symlinking typing files and manifests
on case sensitive file systems (like windows) with the error:
```
lib/actions-oclif/env/add.ts(73,16): error TS2742: The inferred type of 'flags' cannot be named without a reference to '../../../../../../../../../volumes/live/c64feead-f78e-4bd4-742d-ccd29aef53c4/volume/node_modules/@oclif/parser/lib/flags'. This is likely not portable. A type annotation is necessary.
lib/actions-oclif/version.ts(42,16): error TS2742: The inferred type of 'flags' cannot be named without a reference to '../../../../../../../../volumes/live/c64feead-f78e-4bd4-742d-ccd29aef53c4/volume/node_modules/@oclif/parser/lib/flags.js'. This is likely not portable. A type annotation is necessary.
```

This appears to be reported on the Typescript repo here https://github.com/microsoft/TypeScript/issues/29221
The suggested workaround is to explicitly set the type of the `flags` static
property.

Change-type: patch
Signed-off-by: Lucian <lucian.buzzo@gmail.com>
2019-08-09 12:01:46 +01:00
13610ef814 Convert test files to Typescript
Change-type: patch
Signed-off-by: Lucian <lucian.buzzo@gmail.com>
2019-08-08 16:50:50 +01:00
9c49890399 v11.7.8 2019-08-08 18:42:41 +03:00
b9884ec545 Merge pull request #1386 from balena-io/tests-2
Tests part 2: Decaffeinate existing test files
2019-08-08 16:40:35 +01:00
ebd8f348ca Decaffeinate test files
Change-type: patch
Signed-off-by: Lucian <lucian.buzzo@gmail.com>
2019-08-08 13:53:00 +01:00
90c4cbf2ae v11.7.7 2019-08-08 15:45:41 +03:00
987e16cad0 Merge pull request #1385 from balena-io/tests
Tests part 1: Remove mochainon dependency and replace with direct testing dependencies
2019-08-08 13:44:04 +01:00
15dfdc2229 Remove mochainon dependency and replace with direct testing dependencies
Change-type: patch
Signed-off-by: Lucian <lucian.buzzo@gmail.com>
2019-08-08 10:04:32 +01:00
35110e0610 v11.7.6 2019-08-07 12:31:08 +03:00
bf722f61c7 Merge pull request #1382 from balena-io/contributing-doc
Fix incorrect start command in contributing document
2019-08-07 10:29:14 +01:00
08e8151b1f Fix incorrect start command in contributing document
Change-type: patch
Signed-off-by: Lucian <lucian.buzzo@gmail.com>
2019-08-07 09:18:00 +01:00
9977451b07 v11.7.5 2019-08-06 12:23:48 +03:00
9f610a521e Merge pull request #1369 from balena-io/headless-remote-builds
Support headless remote builds
2019-08-06 10:22:10 +01:00
67a4e88e44 Support headless remote builds
Change-type: patch
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-08-06 08:45:05 +01:00
f0bd2f197d v11.7.4 2019-08-02 00:28:38 +03:00
5fe908bbbf Merge pull request #1375 from balena-io/unzip2-dependency
Update unzip2 dependency
2019-08-01 22:27:09 +01:00
f9fc8fd2e8 Update unzip2 dependency
That dependency has been updated upstream, but not published to npm,
thus we are pulling the latest (0.2.8) version from our own fork.
Fork is created temporarily.

Fixes: #1373
Change-type: patch
Signed-off-by: Gergely Imreh <gergely@balena.io>
2019-08-01 18:35:30 +01:00
9ea5198bda v11.7.3 2019-07-22 16:42:50 +03:00
35732515ac Merge pull request #1362 from balena-io/mixpanel-args
Update mixpanel tracking
2019-07-22 14:41:23 +01:00
3ea905dc68 Update mixpanel tracking
Change-type: patch
2019-07-22 13:42:44 +01:00
062fadfa49 v11.7.2 2019-07-18 17:46:59 +03:00
58983670f1 Merge pull request #1354 from balena-io/update-balena-preload-8.2.1
Update balena-preload to ^8.2.1
2019-07-18 16:45:10 +02:00
18fadf5634 Update balena-preload to ^8.2.1
Change-type: patch
2019-07-18 16:21:33 +02:00
80917b7198 v11.7.1 2019-07-17 23:21:01 +03:00
f21dc3e2f4 Merge pull request #1353 from balena-io/typos-gitter
chore: fix up small typos, remove gitter link
2019-07-17 21:17:32 +01:00
d92e076829 chore: fix up small typos, remove gitter link
Change-type: patch
Signed-off-by: Matthew McGinn <matthew@balena.io>
2019-07-17 20:12:52 +01:00
cd491a7935 v11.7.0 2019-07-15 19:51:36 +03:00
90269525be Merge pull request #1344 from balena-io/1164-local-secrets
Add build secrets and variables support for push/build/deploy to/on/via balena devices
2019-07-15 17:49:19 +01:00
225408c57d Add "build secrets" and "build variables" support for push/build/deploy
to/on/via balena devices

Change-type: minor
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-07-15 16:23:35 +01:00
8cfacc9cbc Fix truncated logs in local multicontainer image builds (balena build/deploy)
Resolves: #1346
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-07-15 16:19:41 +01:00
9a270539c6 v11.6.1 2019-07-15 18:17:55 +03:00
df22d42412 Merge pull request #1351 from balena-io/remove-beta-suffix
Remove BETA suffix from Windows and macOS installers, and update INSTALL.md
2019-07-15 16:15:49 +01:00
49a7eb30c0 Remove BETA suffix from Windows and macOS installers, and update INSTALL.md
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-07-15 14:08:27 +01:00
e41ea6fb1a v11.6.0 2019-07-09 18:30:13 +03:00
636ec2a8f2 Merge pull request #1341 from mwohlert/feature/include_sshKeys_in_os_configure
Add support for sshKeys in config file with os configure
2019-07-09 16:28:01 +01:00
9eae9dcee3 Add os.sshKeys to generateBaseConfig
Change-type: minor
2019-07-09 14:01:58 +02:00
6c26e1235c v11.5.0 2019-07-05 17:54:12 +03:00
bacca5383a Merge pull request #1298 from balena-io/integrate-balena-ci
Integrate with balena CI (installer signing)
2019-07-05 15:52:40 +01:00
32e72c832f Add release target in repo.yml
Change-type: patch
Signed-off-by: Giovanni Garufi <giovanni@balena.io>
2019-07-05 15:16:59 +02:00
05aaed07b2 Patch oclif to use "npx npm@6.9.0 install" if npm is older than 6.9.0
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-07-04 20:01:08 +01:00
7c750f9e43 balena CI: Add balena-cli executable signing step
Change-type: minor
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-07-04 20:01:08 +01:00
55bf4dc0f0 Add 'npm run package' command
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-07-04 20:01:07 +01:00
0afbd6f17a Refactor build:standalone / build:installer / run release
So that:
- Standalone zip files are created in the standalone step,
- oclif installers are renamed in the installer step, and
- npm run release (which is skipped by balena CI) is reduced to
  uploading the files to the GitHub releases page.

Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-07-04 20:01:07 +01:00
66b997d98c balena CI integration: Use C:\tmp to avoid 260-char path length limit
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-07-03 11:56:03 +01:00
d485fd00a0 v11.4.4 2019-07-03 02:18:55 +03:00
3322faeeb2 Merge pull request #1337 from balena-io/include-shrinkwrap-in-published
Add npm-shrinkwrap in package.json so that it gets published to the npm registry
2019-07-03 00:17:09 +01:00
c32d894e97 Add 'patches' to files section of package.json for npm publishing
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-07-02 21:00:01 +01:00
ad737b8e02 Add npm-shrinkwrap in package.json so that it gets published to the
registry

Change-type: patch
Signed-off-by: Giovanni Garufi <giovanni@balena.io>
2019-07-02 19:15:32 +02:00
bcc86fbcb6 v11.4.3 2019-07-01 13:23:47 +03:00
d5c7527f8d Merge pull request #1334 from balena-io/1332-shrinkwrap-web-stream-polyfill
Fix "Error: Cannot find module 'web-streams-polyfill'"
2019-07-01 11:22:09 +01:00
5df65f67c3 Fix "Error: Cannot find module 'web-streams-polyfill'"
Fix npm-shrinkwrap.json produced by npm v6.4.1, by using npm v6.9.0

Resolves: #1332
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-07-01 11:07:36 +01:00
79e65025cb v11.4.2 2019-07-01 12:15:22 +03:00
dff6dafe85 Merge pull request #1330 from balena-io/upgrade-livepush
Explicitly upgrade livepush version to 2.0.1 to pick up fix
2019-07-01 02:13:33 -07:00
adcc862acb Explicitly upgrade livepush version to 2.0.1 to pick up fix
Change-type: patch
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-07-01 09:59:07 +01:00
8bf884d425 v11.4.1 2019-06-28 18:55:39 +03:00
a6b282598b Merge pull request #1326 from balena-io/1293-add-npm-shrinkwrap
Add npm-shrinkwrap.json to control dependency updates
2019-06-28 16:54:04 +01:00
77089e31e4 Unpin selected dependencies following addition of npm-shrinkwrap.json
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-06-28 12:30:56 +01:00
7c6bae491f Add npm-shrinkwrap.json file to control dependency updates
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-06-28 12:29:50 +01:00
d5586e12d4 v11.4.0 2019-06-27 19:01:52 +03:00
7e1f4791ed Merge pull request #1328 from balena-io/1327-balena-version-options
Add options to `balena version` to show Node.js version
2019-06-27 17:00:14 +01:00
9d5ecb5f9c Add options to 'balena version' to show Node.js version
Change-type: minor
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-06-27 16:22:26 +01:00
236dce37da Pin the major Node version used by standalone zip packages to Node 10
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-06-27 13:10:55 +01:00
a2ee48f2fb v11.3.6 2019-06-27 15:06:08 +03:00
b74a0d1141 Merge pull request #1329 from balena-io/1306-patch-preload-tarfs
Patch 'pkg' package to resolve 'preload' issue in standalone installations
2019-06-27 13:04:19 +01:00
34d7b84d1e Patch 'pkg' package to resolve 'preload' issue in standalone installs
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-06-27 00:50:35 +01:00
d999b901bb v11.3.5 2019-06-26 15:29:12 +03:00
4a9d133c11 Merge pull request #1325 from balena-io/changelog
Add machine-readable changelog
2019-06-26 13:27:36 +01:00
3a7604368a Add machine-readable changelog
Change-type: patch
Signed-off-by: Gergely Imreh <gergely@balena.io>
2019-06-26 12:24:03 +01:00
df2e611c42 v11.3.4 2019-06-26 14:13:33 +03:00
30e48b658f Merge pull request #1324 from balena-io/1302-fix-ssh-numeric-short-uuid
Fix incorrect parsing of numeric short UUIDs in ssh and tunnel actions
2019-06-26 14:12:08 +03:00
f095ac169a patterns: Add debug logs in the getOnlineTargetUuid resolution
Change-type: patch
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2019-06-26 13:44:56 +03:00
f0030a1891 tunnel: Fix incorrect parsing of numeric short UUIDs
Change-type: patch
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2019-06-26 13:44:56 +03:00
1d3af3245a ssh: Fix incorrect parsing of numeric short UUIDs
Resolves: #1302
Change-type: patch
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2019-06-26 13:44:56 +03:00
f4612116b9 v11.3.3 2019-06-20 19:33:22 +03:00
65ab3008e6 Merge pull request #1318 from balena-io/fix-multiple-image-use-push
Fix using an image more than once in a balena push
2019-06-20 09:31:34 -07:00
36026d8556 Fix using an image more than once in a balena push
Change-type: patch
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-06-20 16:46:29 +01:00
436ad60f4e v11.3.2 2019-06-20 18:42:01 +03:00
e0ee333717 Merge pull request #1320 from balena-io/remove-double-printed-log
Remove the livepush initialisation double printed log
2019-06-20 08:40:38 -07:00
3b09c5ac91 Remove the livepush initialisation double printed log
Change-type: patch
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-06-20 15:20:19 +01:00
6994499f14 v11.3.1 2019-06-18 15:03:27 +03:00
9e19b5875b Merge pull request #1313 from balena-io/fix-livepush-newline
Fix output of seperation newline during livepush
2019-06-18 05:00:09 -07:00
c3e5147a19 Fix output of seperation newline during livepush
Change-type: patch
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-06-18 11:45:12 +01:00
5e46815ac7 v11.3.0 2019-06-18 13:44:55 +03:00
7b37c60e11 Merge pull request #1314 from balena-io/file-based-secrets
If a secrets file is not specified, read it from the data directory
2019-06-18 03:43:03 -07:00
cf9fdbe6e4 If a secrets file is not specified, read it from the data directory
Change-type: minor
Closes: #1164
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-06-18 11:13:09 +01:00
66dfddc96d v11.2.2 2019-06-16 17:20:25 +03:00
1fa2347608 Merge pull request #1307 from balena-io/1300-issue-template
docs: update GitHub issue template, Node versions and sample Dockerfile
2019-06-16 15:18:42 +01:00
6bed43fe1f docs: update GitHub issue template, required Node version and sample Dockerfile
Resolves: #1300
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-06-16 01:42:55 +01:00
46806c8377 v11.2.1 2019-06-12 16:00:40 +03:00
cf93438df1 Merge pull request #1305 from balena-io/live-ignore-dev
livepush: Ignore the .git directory when performing a livepush
2019-06-12 05:58:47 -07:00
ea43130135 livepush: Ignore the .git directory when performing a livepush
Change-type: patch
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-06-12 12:41:46 +01:00
5e4daf8c3d v11.2.0 2019-06-11 15:53:33 +03:00
20474aeb55 Merge pull request #1143 from balena-io/788-hup
Add device OS update action
2019-06-11 15:51:28 +03:00
825213c02a Add device OS update action
Resolves: #788
Depends-on: https://github.com/balena-io/balena-sdk/pull/638
Change-type: minor
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2019-06-11 15:27:23 +03:00
13cef01374 v11.1.0 2019-06-10 13:38:49 +03:00
7271f90dc6 Merge pull request #1301 from balena-io/cancellable-livepushes
Add cancellable livepushes, when a file changes during a current livepush
2019-06-10 03:36:51 -07:00
8b5ebe0645 Pin prettier and add formatting changes
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-06-10 11:07:51 +01:00
24e49bf131 Cancel ongoing livepushes when a new change occurs
Also fix livepush logging when a new container is created (previously
the logs of the commands would stop working after this has happened)

Change-type: minor
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-06-07 15:59:27 +01:00
5a0ef354f1 Fix ts-node invocation in balena-dev
Properly pull in the project file, as it exists in a different
directory.

Change-type: patch
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-06-07 15:02:25 +01:00
f8a9c10a77 v11.0.7 2019-06-07 04:01:30 +03:00
bace8b5c1e Merge pull request #1299 from balena-io/add-missing-doc
Update tunnel documentation after argument changes
2019-06-07 01:59:25 +01:00
d8c942c77e Fix "catch-uncommitted" build failure (npm run prettify)
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-06-07 01:30:32 +01:00
7fccd4a35e Update tunnel documentation after argument changes
Change-type: patch
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-06-06 17:56:20 +01:00
b78dd26f23 v11.0.6 2019-06-06 19:41:44 +03:00
47c63e0e1d Merge pull request #1296 from balena-io/fix-tunnel-device-prompt
Add single code path to get full, online-only device UUIDs
2019-06-06 17:38:59 +01:00
5d137f3c20 fix: Add single code path to get full, online-only device UUIDs
Both the tunnel and SSH commands require a full UUID for an online
device. A single code path was added to provide this, taking either
an application name or a partial UUID as a search parameter.

In the event of an application name being provided, a device select
form is presented to the user to pick from the online devices at that
time.

Change-type: patch
Signed-off-by: Rich Bayliss <rich@balena.io>
2019-06-06 17:05:08 +01:00
2bbdfda92e v11.0.5 2019-06-06 16:51:58 +03:00
2f2f16267f Merge pull request #1278 from balena-io/resin-cli-form.d.ts
Add initial typings for resin-cli-form
2019-06-06 16:49:53 +03:00
051268168a Add initial typings for resin-cli-form
Change-type: patch
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2019-06-06 16:21:35 +03:00
2b264df41b v11.0.4 2019-06-06 12:45:39 +03:00
eba193278e Merge pull request #1294 from balena-io/docs-scan
Add 'scan' command to the website reference documentation
2019-06-06 10:43:16 +01:00
462b41b4ea Add 'scan' command to the website reference documentation
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-06-05 23:17:59 +01:00
ab5815c277 v11.0.3 2019-06-05 18:38:08 +03:00
f75ffb53f5 Merge pull request #1292 from balena-io/1291-regex-s-flag
Fix 'npm help' SyntaxError on Node 8 (invalid 's' regex flag)
2019-06-05 16:35:08 +01:00
3387f8f656 Fix 'npm help' SyntaxError on Node 8 (invalid 's' regex flag)
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-06-05 16:12:16 +01:00
e8325e8268 v11.0.2 2019-06-05 16:23:27 +03:00
c2491497b5 Merge pull request #1290 from balena-io/1289-fix-patch-package-production
Fix "npm install --production" installation (missing patch-package dependency)
2019-06-05 14:21:27 +01:00
4596005a1f Fix "--production" installation (missing patch-package dependency)
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-06-05 13:42:27 +01:00
8d9cbbb526 v11.0.1 2019-06-04 22:32:52 +03:00
17e51799f5 Merge pull request #1286 from balena-io/fix-travis-release
Fix Travis release
2019-06-04 20:30:13 +01:00
df797cdc2c Fix Travis release
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-06-04 20:13:18 +01:00
57fc26c0f7 v11.0.0 2019-06-04 21:17:56 +03:00
abc2cfd14c Merge pull request #1285 from balena-io/release-v11
Release balena-cli v11.0.0
2019-06-04 19:16:16 +01:00
612fefcc65 Merge pull request #1284 from balena-io/1283-remove-signup
Remove 'signup' command
2019-06-04 17:24:52 +01:00
0bbe376e41 Remove 'signup' command
Change-type: major
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-06-04 17:06:46 +01:00
04223dbc58 Revert bin/balena (previously renamed bin/run for oclif compatibility)
Change-type: major
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-06-04 13:52:38 +01:00
b5c4348de1 balena CI integration: Patch @oclif/dev-cli to install 7zip on demand
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-06-04 13:52:38 +01:00
751749325f Add warning notices for replaced 'local' commands in v11
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-06-04 13:52:37 +01:00
1e2e48b149 Revert 'balena flash' to 'balena local flash'
Change-type: major
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-06-04 13:52:37 +01:00
01b454351b Fix SSH'ing into a device from application
Change-type: patch
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-06-04 13:52:36 +01:00
6696b1b5f7 Make livepush the default when pushing to a local device
Change-type: major
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-06-04 13:52:36 +01:00
5da307f02e Make the CommandDefinition option parameter a Partial
This ensures that no code accidentally relies on them being present, and
the types are then correct.

Change-type: patch
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-06-04 13:52:35 +01:00
b391c96e64 Allow multiple services to be tailed with balena logs and push
Also correctly type the input.

Change-type: patch
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-06-04 13:52:35 +01:00
0ee73f5164 Don't require a login for commands operating on local devices
Change-type: patch
Closes: #1195
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-06-04 13:52:35 +01:00
1a1861bfcb Remove or move most local namespaced commands
Change-type: major
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-06-04 13:52:34 +01:00
717c43f10b Update the CLI's installation instructions for executable installers
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-06-04 13:52:34 +01:00
dafbdd5f34 Add native installers for Windows and macOS
Change-type: minor
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-06-04 13:52:33 +01:00
c204dbd6cd Bump denymount version and delete redundant patch (chore task)
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-06-04 13:51:59 +01:00
6e7f51758e Add CONTRIBUTING.md and some guidance on commit messages and doc files.
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-06-04 13:51:59 +01:00
ea89a6f221 Update documentation markdown following v11-meta branch rebase
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-06-04 13:51:58 +01:00
94c9e13106 Fix windows straight-to-container SSH
Closes: #1211
Change-type: patch
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-06-04 13:51:58 +01:00
64c2f00d2a Update balena ssh command to support local devices and multicontainer
Change-type: major
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-06-04 13:51:57 +01:00
8f8d6b5f08 Sort 'balena help' primary commands in manually specified order
Connects-to: #1140
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-06-04 13:51:57 +01:00
c49a1d3fbf Remove --dockerPort's -p alias for balena preload
It was conflicting with --pin-device-to-release -p alias

Changelog-entry: Remove --dockerPort's -p alias for `balena preload`
Change-type: major
2019-06-04 13:51:56 +01:00
abf573fa47 Begin the transition to oclif with 'balena env add' (fix dropped leading
zero in device UUID).

This commit is fairly chunky because it adds the oclif dependency for
the first time, and refactors the CLI help and docs generation code to
accommodate both Capitano and oclif.

Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-06-04 13:51:56 +01:00
13e3e5e8ea Bump min Node.js version to 8.0, ts-node to 8.1 and typescript to 3.4.
Refactor typings folder for use with the tsconfig typeRoots option.

Change-type: major
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-06-04 13:51:55 +01:00
faa558b432 v10.17.5 2019-06-04 09:10:48 +03:00
4eea5e822b Merge pull request #1282 from balena-io/pin-moment-duration-format
Pin moment-duration-format package (ReferenceError: window is not defined)
2019-06-04 09:08:50 +03:00
fe3e348128 Pin moment-duration-format package (ReferenceError: window is not defined)
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-06-04 02:47:45 +01:00
7535b7110d v10.17.4 2019-06-03 14:19:41 +03:00
0aaf6dff41 Merge pull request #1277 from balena-io/gitignore-fast-boot
.gitignore: Add fast-boot.json generated by balena-dev command
2019-06-03 14:17:30 +03:00
aca58743ea .gitignore: Add fast-boot.json generated by balena-dev command
Change-type: patch
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2019-06-03 13:57:12 +03:00
f6a262bcde v10.17.3 2019-05-31 17:23:25 +03:00
9cc81866eb Merge pull request #1276 from balena-io/1275-npmrc
Use an .npmrc to prevent creating a package-lock on each install
2019-05-31 17:21:30 +03:00
0607c2f231 Use an .npmrc to prevent creating a package-lock on each install
Resolves: #1275
Change-type: patch
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2019-05-31 16:52:55 +03:00
fe0ba62026 v10.17.2 2019-05-30 18:05:43 +03:00
f2af7b2588 Merge pull request #1269 from balena-io/newline-build-arg
Allow newline characters in build/deploy --buildArg values
2019-05-30 16:03:40 +01:00
e145540132 Allow newline characters in build/deploy --buildArg values
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-05-30 15:03:56 +01:00
d21b84956c v10.17.1 2019-05-30 16:02:58 +03:00
841ce9fd68 Merge pull request #1270 from balena-io/fix-build-types-mz
Fix CI build error (missing @types/mz)
2019-05-30 14:00:05 +01:00
a4efc7c9c4 Fix CI build error (missing @types/mz)
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-05-30 12:57:03 +01:00
e6ecb0ec0b v10.17.0 2019-05-29 18:05:38 +03:00
c420d0f63c Merge pull request #1262 from balena-io/preload-add-certificate-option
Add preload --add-certificate option
2019-05-29 17:03:24 +02:00
f3ef7f6e18 Add preload --add-certificate option
Change-type: minor
2019-05-28 16:35:29 +02:00
e36435bb4c v10.16.0 2019-05-27 17:38:47 +03:00
825964fdc6 Merge pull request #1258 from balena-io/1070-remove-20-image-limit
compose: remove artificial 20 repo limit
2019-05-27 22:36:26 +08:00
5202e137d5 compose: remove artificial 20 repo limit
This issue has now been fixed server-side

Connects-to: #1070
Change-type: minor
Signed-off-by: Matthew McGinn <matthew@balena.io>
2019-05-27 22:12:58 +08:00
d23d837b8c v10.15.0 2019-05-27 17:06:11 +03:00
8c537c112d Merge pull request #1257 from balena-io/better-livepush-dockerfile-change-restart
Better livepush dockerfile change restart
2019-05-27 07:04:05 -07:00
106b971410 Add a much faster container replacement for livepush
Change-type: patch
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-05-27 13:48:53 +01:00
39cf86ed85 Add a containerId request function to the device api module
Change-type: minor
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-05-27 13:48:53 +01:00
5de7a50fc0 v10.14.0 2019-05-27 15:47:25 +03:00
ba4301487f Merge pull request #1256 from balena-io/1255-local-push-vars
Add the ability to specify an environment variable when pushing to local mode device
2019-05-27 05:45:41 -07:00
f77156772a Add the ability to specify an environment variable when pushing to local
mode device

Closes: #1255
Change-type: minor
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-05-27 13:23:55 +01:00
a6d6035725 v10.13.6 2019-05-22 18:51:56 +03:00
5e0d24a1f1 Merge pull request #1248 from balena-io/fix-latest-commit-alias
Fix `balena preload --commit current` alias
2019-05-22 17:50:06 +02:00
9434570c2d Improve preload's --commit parameter description
Change-type: patch
2019-05-22 17:29:08 +02:00
674c0ca7b8 Fix balena preload --commit current alias
Change-type: patch
2019-05-22 17:13:55 +02:00
cccc8012c9 v10.13.5 2019-05-22 15:55:18 +03:00
29bfcf7ac5 Merge pull request #1244 from balena-io/update-balena-preload
Update balena preload
2019-05-22 14:52:46 +02:00
2091768c84 Rename preload --commit latest to preload --commit current
`latest` is still supported

Change-type: patch
2019-05-21 18:00:01 +02:00
36ab6f5808 Update balena-preload to 8.1.4
Change-type: patch
2019-05-21 14:02:45 +02:00
b45e80654c v10.13.4 2019-05-20 19:54:18 +03:00
8c60c9e076 Merge pull request #1242 from balena-io/1241-apps-length-undefined
Fix TypeError when running 'balena apps'
2019-05-20 17:51:40 +01:00
d47fe0609f Fix TypeError when running 'balena apps'
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-05-20 17:21:01 +01:00
3b5f3c6665 v10.13.3 2019-05-17 21:16:35 +03:00
1bfba85d58 Merge pull request #1239 from balena-io/1238-fix-apps-device-counts
apps: Fix the device count columns being empty
2019-05-17 21:14:28 +03:00
cb14928866 apps: Fix the device count columns being empty
Connects-to: #1238
Change-type: patch
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2019-05-17 20:48:55 +03:00
4088e4c66e v10.13.2 2019-05-17 20:00:43 +03:00
01a1bcdc8a Merge pull request #1237 from balena-io/1236-rm-intermediate-containers
Remove intermediate containers when doing a local push
2019-05-17 09:58:32 -07:00
05c3d2a5db Remove intermediate containers when doing a local push
Change-type: patch
Closes: #1236
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-05-17 17:43:41 +01:00
7da250914e v10.13.1 2019-05-16 13:05:40 +03:00
eaad5377b4 Merge pull request #1232 from balena-io/1231-os-build-config-example-fix
docs: Fix os configure example in os build-config docs
2019-05-16 13:03:23 +03:00
9f15ee58df docs: Fix os configure example in os build-config docs
Connects-to: #1231
Change-type: patch
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2019-05-16 12:38:44 +03:00
ee267cd114 v10.13.0 2019-05-16 12:33:46 +03:00
b35a51ef3a Merge pull request #1228 from balena-io/1177-api-device-type-versions
Use the open-balena-api endpoints for device type & version info
2019-05-16 12:31:58 +03:00
7ce43f4018 Use the open-balena-api endpoints for device type & version info
Resolves: #1177
HQ: https://github.com/balena-io/balena/issues/1744
Change-type: minor
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2019-05-15 21:03:57 +03:00
3ba8be02e7 v10.12.1 2019-05-15 20:19:04 +03:00
9a9d3f5c32 Merge pull request #1230 from balena-io/preload
preload: bump version to fix preloading on logstream supervisors
2019-05-15 18:16:58 +01:00
0adaeb5465 preload: bump version to fix preloading on logstream supervisors
Change-type: patch
Signed-off-by: Gergely Imreh <gergely@balena.io>
2019-05-15 17:56:00 +01:00
783cab2e50 v10.12.0 2019-05-15 17:40:55 +03:00
a47d2d4454 Merge pull request #1229 from balena-io/update-cli-video-2
Fix video url
2019-05-15 16:38:45 +02:00
1f728050c8 Fix video url
Change-type: minor
Signed-off-by: Daniel Andrade <daniel@balena.io>
2019-05-15 14:33:54 +01:00
15ec99577a v10.11.1 2019-05-15 16:33:39 +03:00
fb2e498aa9 Merge pull request #1221 from balena-io/debounced-livepush
Debounce livepush invocations to collect changes together
2019-05-15 06:31:29 -07:00
7529a9a2a2 Debounce livepush invocations to collect changes together
Change-type: patch
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-05-15 14:01:30 +01:00
22b02c261f v10.11.0 2019-05-15 13:42:35 +03:00
8f014710c0 Merge pull request #1227 from balena-io/update-video-url
Update balena-cli video url
2019-05-15 12:40:14 +02:00
308d1afb83 Update balena-cli video url
Change-type: minor
Signed-off-by: Daniel Andrade <daniel@balena.io>
2019-05-15 11:16:52 +01:00
c15276d239 v10.10.5 2019-05-14 16:22:38 +03:00
ecae517de0 Merge pull request #1224 from balena-io/Jazzagi-patch-1
Update instructions for adding folder to path in MacOS
2019-05-14 14:19:52 +01:00
69cc2a0946 Update instructions for adding folder to path in MacOS
Change-type: patch
2019-05-14 13:53:02 +01:00
7a8fc14686 v10.10.4 2019-05-14 15:25:44 +03:00
eaa886c31c Merge pull request #1220 from balena-io/1219-tcp-keepalive
Use TCP keepalive probes to detect local log stream closing
2019-05-14 05:23:18 -07:00
20ae2bc57a Pin pkg version to avoid node 6 error
Change-type: patch
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-05-14 12:37:27 +01:00
96c975d17e Use TCP keepalive probes to detect local log stream closing
Change-type: patch
Closes: #1219
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-05-14 11:39:57 +01:00
ff8d784582 v10.10.3 2019-05-10 19:14:41 +03:00
53bee83047 Merge pull request #1213 from balena-io/977-denymount-pkg
Fix 'local configure' on macOS standalone installation
2019-05-10 17:12:43 +01:00
6e343c36a8 Fix 'local configure' on macOS standalone installation
Resolves: #977
Resolves: #1212
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-05-10 16:18:13 +01:00
e29c275b4c v10.10.2 2019-05-10 18:04:14 +03:00
7faf363180 Merge pull request #1218 from balena-io/update-deps
Update dependencies including a balena-preload fix for lots of releases
2019-05-10 16:02:09 +01:00
a503cb4757 Update dependencies including a balena-preload fix for lots of releases
Change-type: patch
2019-05-10 15:38:12 +01:00
b3470ac909 v10.10.1 2019-05-04 22:53:16 +03:00
ba4c93ccf5 Merge pull request #1203 from balena-io/842-update-notifier-standalone
Update daily upgrade notifier message for non-npm installations
2019-05-04 20:51:37 +01:00
87401ad569 Replace 'npm' upgrade notifier message with INSTALL.md URL
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-05-04 18:34:08 +01:00
181afb34f8 v10.10.0 2019-05-03 15:32:30 +03:00
bfdfa28922 Merge pull request #1206 from balena-io/qemu
qemu: use v4.0.0
2019-05-03 13:30:24 +01:00
21840d9245 qemu: use v4.0.0-balena
Also append the QEMU version to the locally cached copy, so the
CLI can correctly bump version whenever QEMU_VERSION is bumped
in the future.

Change-type: minor
Signed-off-by: Gergely Imreh <gergely@balena.io>
2019-05-03 12:37:36 +01:00
d9c3332cb2 v10.9.4 2019-05-02 16:19:15 +03:00
29d684f9c3 Merge pull request #1202 from balena-io/1196-better-detached-logging
Better livepush ux
2019-05-02 14:16:44 +01:00
a832f47508 Improve livepush UX
Change-type: patch
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-05-02 14:06:34 +01:00
4557cf626f Improve logging for detached mode + livepush
Change-type: patch
Closes: #1196
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-05-02 14:06:26 +01:00
8c68aaad49 v10.9.3 2019-05-02 15:38:03 +03:00
260c6fccd2 Merge pull request #1208 from balena-io/dependencies
update dependencies and tests
2019-05-02 13:36:05 +01:00
d40f2eb500 actions/auth: fix mixed indentation error
Change-type: patch
Signed-off-by: Gergely Imreh <gergely@balena.io>
2019-05-02 13:22:22 +01:00
b6f3975bc1 dependencies: bump gulp to v4
To fix the same error as here https://github.com/nodejs/node/issues/20285
Task changes as described at https://fettblog.eu/gulp-4-parallel-and-series/

Change-type: patch
Signed-off-by: Gergely Imreh <gergely@balena.io>
2019-05-02 11:53:54 +01:00
f2bd3c0ffb dependencies: bump etcher-sdk to pull in fixes
Change-type: patch
Signed-off-by: Gergely Imreh <gergely@balena.io>
2019-05-02 11:53:50 +01:00
3ae01fdaa0 v10.9.2 2019-05-02 13:53:25 +03:00
2452b42f81 Merge pull request #1204 from balena-io/1174-add-INSTALL-md-review
Update README and INSTALL docs (review typos and some rewording)
2019-05-02 11:51:23 +01:00
3303ac21c9 Update README and INSTALL docs (review typos and some rewording)
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-05-01 18:15:47 +01:00
1b277bda87 v10.9.1 2019-05-01 11:16:23 +03:00
5f67c243c0 Merge pull request #1205 from balena-io/better-.local-detection
Allow any amount of subdomains when parsing .local addresses
2019-05-01 09:14:36 +01:00
9bbfb31bf7 Allow any amount of subdomains when parsing .local addresses
Change-type: patch
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-05-01 08:50:27 +01:00
5b805fe1da v10.9.0 2019-04-29 16:50:50 +03:00
24ed25aa37 Merge pull request #1175 from balena-io/1174-add-INSTALL-md
Add INSTALL.md file and centralise other docs on README.md
2019-04-29 14:48:12 +01:00
2ad0b60aeb Unify the CLI instructions between capitanodoc.ts and README.md, move
the installation instructions to INSTALL.md, and update the markdown
generation scripts.

Change-type: minor
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-04-29 14:27:32 +01:00
37bd6be77b v10.8.2 2019-04-29 13:13:22 +03:00
88ad591a83 Merge pull request #1194 from balena-io/1187-numeric-app-name
Handle app names that look like a number (e.g. 1234)
2019-04-29 11:11:15 +01:00
30c36a26e2 Handle app names that look like a number (eg 1234)
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-04-27 00:54:15 +01:00
6d6afc5140 v10.8.1 2019-04-26 19:02:14 +03:00
fc79d89f10 Merge pull request #1191 from balena-io/better-detached-live
Add better semantics for detached mode + live for push
2019-04-26 16:59:49 +01:00
57fba32fa2 Add better semantics for detached mode + live for push
Now if you pass both --live and --detached, the logs won't be displayed
but livepush will continue to run.

Change-type: patch
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-04-26 16:50:27 +01:00
b41f9b9261 v10.8.0 2019-04-25 13:37:06 +03:00
a9aa7538f3 Merge pull request #1190 from balena-io/push-logs-.local
Allow specifying a .local address for logs and push
2019-04-25 11:34:55 +01:00
1b13d1b969 Allow specifying a .local address for logs and push
Change-type: minor
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-04-25 11:00:45 +01:00
e6b09f1b94 v10.7.0 2019-04-24 19:47:46 +03:00
8fa592dff0 Merge pull request #1188 from balena-io/system-logs
Allow filtering of system logs with push and logs commands
2019-04-24 17:45:40 +01:00
a6d2950260 Allow filtering of system logs with push and logs commands
Change-type: minor
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-04-24 17:30:18 +01:00
b22ddb50f1 v10.6.0 2019-04-24 17:56:56 +03:00
0aa10ba2a1 Merge pull request #1186 from balena-io/per-service-log-filtering
Add per-service filtering to logs and push
2019-04-24 15:54:41 +01:00
56c74af1ff Add per-service filtering to logs and push
Change-type: minor
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-04-24 15:05:05 +01:00
6460d850ca v10.5.0 2019-04-24 17:00:45 +03:00
b05aa7b385 Merge pull request #1185 from balena-io/add-detached-logs-push
push: Add detached flag to avoid streaming logs after local push
2019-04-24 14:58:12 +01:00
97c15208b5 push: Add detached flag to avoid streaming logs after local push
Change-type: minor
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-04-24 14:31:30 +01:00
375464eb1a v10.4.1 2019-04-24 16:03:11 +03:00
811262ed8b Merge pull request #1184 from balena-io/local-logs
Refactor and improve balena logs, with consistent interface and local mode support
2019-04-24 14:00:54 +01:00
f816cb4ce8 Fix and update log documentation
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-04-24 13:04:49 +01:00
7b5272e926 Add tslint config to enable consistent lint process
The lint configuration used seems to vary between build machines, and
this is a bug in resin-lint. Until that's fixed, we provide another
tslint which points to the resin-lint configuration.

Signed-off-by: Cameron Diver <cameron@balena.io>
2019-04-24 12:48:52 +01:00
d412d39164 Add ability to use balena logs with a local mode device
Change-type: patch
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-04-24 12:48:52 +01:00
d41fb72ded refactor: Convert logs action to typescript
Change-type: patch
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-04-24 12:48:51 +01:00
4676396b5f logs: Make device logs consistent across the CLI
Change-type: patch
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-04-24 12:25:09 +01:00
b97565d2e7 refactor: Create and use validation functions for input
This includes IP address, application name and dotlocal url parsing.

Change-type: patch
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-04-24 12:11:46 +01:00
a697121b97 v10.4.0 2019-04-24 13:17:19 +03:00
12615cd0dc Merge pull request #1180 from balena-io/tunnel-short-uuid
tunnel: allow using partial device uuids
2019-04-24 11:15:05 +01:00
cba73eec44 tunnel: allow using partial device uuids
Connects-to: #1173
Change-type: minor
Signed-off-by: Will Boyce <will@balena.io>
2019-04-24 10:51:42 +01:00
f5ed0648ba v10.3.0 2019-04-23 19:43:25 +03:00
ac5ffeda09 Merge pull request #1171 from balena-io/587-build-dockerfile-f-flag
Add --dockerfile option to the build, deploy and push commands
2019-04-23 17:41:17 +01:00
db25a65753 Add --dockerfile option to the build, deploy and push commands
It allows the selection of an alternative Dockerfile in single-
container projects that do not include a docker-compose file.

Change-type: minor
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-04-23 15:34:19 +01:00
296f1ae2de Fix push and deploy issues under Windows ('/' vs '\' path separators)
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-04-23 15:16:47 +01:00
579cdaa2e2 v10.2.0 2019-04-23 16:29:53 +03:00
69db3c0171 Merge pull request #1076 from balena-io/add-livepush
Add livepush to balena push
2019-04-23 14:27:54 +01:00
7c71098d86 Update livepush documentation and required versions
Change-type: patch
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-04-23 14:00:05 +01:00
490f833a33 Cleanup intermediate containers on exit of livepush
Change-type: patch
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-04-23 14:00:05 +01:00
a81c1971f1 livepush: Perform full rebuild on Dockerfile-like file change
Change-type: minor
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-04-23 14:00:05 +01:00
76034696e9 Fix lint warnings
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-04-23 14:00:04 +01:00
454f82883e Add supervisor version information to push documentation
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-04-23 14:00:04 +01:00
6a9a9e1fdb Add livepush ability to balena push
Change-type: minor
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-04-23 14:00:04 +01:00
cf2ad66955 log: Add livepush logging functions
Change-type: minor
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-04-23 12:14:09 +01:00
4cfaf6e666 Add device status endpoint api function
Change-type: minor
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-04-23 12:14:05 +01:00
bc563ea963 v10.1.1 2019-04-22 20:25:24 +03:00
65ac35a93e Merge pull request #1179 from balena-io/1178-filter-before-patching-release-status
Ensure not marking successful releases as canceled
2019-04-22 20:23:13 +03:00
1ee51ca9a7 Ensure not marking successful releases as canceled
Resolves: #1178
Change-type: patch
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2019-04-22 20:09:32 +03:00
e9e15dbbe3 v10.1.0 2019-04-18 18:44:46 +03:00
a665a3d153 Merge pull request #1170 from balena-io/update-readme
Add more information about the stantalone version
2019-04-18 16:43:00 +01:00
9da5f88ecf Updated CLI installation notes on README.md and ran prettier
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-04-18 14:52:51 +01:00
14e9b34636 Add more information about the stantalone version
Change-type: minor
Signed-off-by: Daniel Andrade <daniel@balena.io>
2019-04-17 17:56:33 +01:00
e619caea42 v10.0.1 2019-04-13 19:14:09 +03:00
a133fe8c6f Merge pull request #1163 from balena-io/fix-ignore
Fix file ignore rules matching metadata folders
2019-04-13 17:12:29 +01:00
29dd5e71a1 Fix docs markdown (deprecation messages for 'local push' and 'sync')
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-04-13 16:23:06 +01:00
9b52dec725 Fix file ignore rules matching metadata folders breaking qemu builds
Change-type: patch
2019-04-12 16:16:56 +03:00
6bc55ea7ab Merge pull request #1160 from balena-io/change-sync-deprecation-message
Remove information about livepush in sync deprecation message
2019-04-09 14:35:30 +01:00
717affa591 Remove information about livepush in sync deprecation message
Change-type: patch
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-04-09 11:08:40 +01:00
6a9eeaaba2 v10.0.0 2019-04-03 20:01:58 +03:00
98eaeddbfe Merge pull request #1152 from balena-io/1140-1141-deprecate-commands
Remove 'quickstart' command and deprecate 'local push'.
2019-04-03 18:00:15 +01:00
30698c62e3 Remove 'quickstart' command and deprecate 'local push'.
Change-type: major
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-04-03 17:34:55 +01:00
79e240f630 v9.15.6 2019-03-29 15:58:58 +02:00
6825ffe416 Merge pull request #1149 from balena-io/local-push-upgrades
Support nocache flag in push <ip>
2019-03-29 13:56:20 +00:00
b9bf00d329 Support nocache flag in push <ip>
Change-type: patch
Closes: #1128
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-03-28 13:29:52 +00:00
5ae7457f45 v9.15.5 2019-03-28 14:26:33 +02:00
d78dfcb1de Merge pull request #1147 from balena-io/bump-docker-progress-400
Bump docker-progress version (4.0.0) to improve `balena deploy` error handling
2019-03-28 12:23:55 +00:00
95c4c59ca0 Bump docker-progress (4.0.0) to improve balena deploy error
handling.

Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-03-26 23:30:13 +00:00
3a06c5df72 v9.15.4 2019-03-26 01:00:24 +02:00
d30144a16a Merge pull request #1146 from balena-io/resin-compose-parse-2.0.4
Update resin-compose-parse to 2.0.4 to fix an error with extra_hosts
2019-03-25 15:58:41 -07:00
c0990fe6c4 Update resin-compose-parse to v2.0.4
This fixes an issue parsing extra_hosts when building multicontainer projects.

Change-type: patch
Signed-off-by: Pablo Carranza Velez <pablo@balena.io>
2019-03-25 15:43:20 -07:00
af382bfee4 Update resin-multibuild to v2.1.5
Change-type: patch
Signed-off-by: Pablo Carranza Velez <pablo@balena.io>
2019-03-25 15:42:19 -07:00
6705369ca6 v9.15.3 2019-03-25 20:13:28 +02:00
fb05957198 Merge pull request #1145 from balena-io/1130-allow-not-logged-in-push-to-device
Allow 'balena push <deviceIpAddress>' when not logged in to balenaCloud.
2019-03-25 18:11:31 +00:00
6b21f5aa5a Allow 'balena push <deviceIpAddress>' when not logged in to balenaCloud.
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-03-25 17:46:38 +00:00
0fac8d8d3b v9.15.2 2019-03-18 16:32:00 +02:00
f39193ab61 Merge pull request #1138 from balena-io/bump-resin-lint-301-multibuild-214
Bump resin-lint major version (3.0.1), resin-multibuild (2.1.4) and docker-progress (3.0.5)
2019-03-18 14:30:09 +00:00
a883948d56 Bump resin-multibuild (2.1.4), docker-progress (3.0.5), resin-lint (3.0.1)
The new resin-multibuild and docker-progress versions widen the range
of errors caught by the 'balena push' and 'balena build' commands.

Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-03-18 14:09:06 +00:00
da86d3303f v9.15.1 2019-03-12 15:54:38 +02:00
2f3138208a Merge pull request #1137 from balena-io/codeowners
Add maintainer as code owner
2019-03-12 13:52:34 +00:00
e688e10684 Add maintainer, reviewers, and devexp team as code owners
Change-type: patch
Signed-off-by: Gergely Imreh <gergely@balena.io>
2019-03-12 13:34:27 +00:00
66b62df70b v9.15.0 2019-03-12 14:13:19 +02:00
a4dd45e6a6 Merge pull request #1136 from balena-io/ssh-no-suggest
ssh: add flag not to suggest devices to connect to
2019-03-12 12:11:19 +00:00
b4439b7d78 ssh: add --noninteractive flag not to suggest devices to connect to
The suggestion happens if the UUID supplied is not found. Because
of that function, it's impossible to do an atomic connect to a device
in non-interactive mode. The auto-suggestion results connecting to
the first available device, which is likely not the intended action.
The current workaround is running a `balena device UUID` and check
its exit code before running `balena ssh UUID`, but since these
are independent steps, still can connect to another device, if between
the two commands anything changes. With this flag used, one could never
connect accidentally to the wrong device due to suggestions.

Change-type: minor
Signed-off-by: Gergely Imreh <gergely@balena.io>
2019-03-12 11:50:17 +00:00
bf566b7bb7 v9.14.7 2019-03-11 18:46:47 +02:00
2c897a1b18 Merge pull request #1134 from balena-io/ssh-version
ssh: correct the minimum OS version that allows host OS connection
2019-03-11 16:45:00 +00:00
a5cfbb3181 ssh: correct the minimum OS version that allows host OS connection
Since openBalena API v0.11.0 (downstream API 9.16.0) the minimum
OS version has been lowered from 2.7.5 to 2.0.0 for host OS access.

Change-type: patch
Signed-off-by: Gergely Imreh <imrehg@gmail.com>
2019-03-11 15:24:12 +00:00
119a630643 v9.14.6 2019-03-08 15:51:06 +02:00
c6fe6b5e3e Merge pull request #1131 from balena-io/1102-owner-legacy-deploy
Fix 'unauthorized' error for additional members of legacy apps
2019-03-08 13:47:05 +00:00
6ff43b11b1 Fix 'unauthorized' error for additional members of legacy apps
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-03-07 18:01:19 +00:00
f35655028e v9.14.5 2019-03-05 16:19:34 +01:00
709af3e92b Merge pull request #1125 from balena-io/1010-retry-image-push-unknown-blob
Handle 'unknown blob' errors and retry image pushing
2019-03-05 15:17:55 +00:00
5ec9dce507 Retry image push a few times (balena deploy, 'unknown blob')
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-03-05 15:02:52 +00:00
1e81638433 Harden 'remote-build' error handling (balena push)
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-03-05 13:17:37 +00:00
145b613f5d v9.14.4 2019-03-05 11:00:31 +01:00
a243c3f577 Merge pull request #1126 from balena-io/update-resin-multibuild
Update resin-multibuild to pick up fixes
2019-03-05 09:58:15 +00:00
75b9ba907f Update resin-multibuild to pick up fixes
Change-type: patch
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-03-05 09:51:08 +00:00
1a368ac4d4 v9.14.3 2019-03-04 21:06:12 +01:00
2833e8ba23 Merge pull request #1124 from balena-io/conditional-debug-hint
Minor doc updates (add DEBUG=1 hint)
2019-03-04 20:04:19 +00:00
de3837f777 Minor doc updates (add DEBUG hint)
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-03-04 19:53:18 +00:00
8dc5eaca52 v9.14.2 2019-03-01 12:54:23 +01:00
5c41de0c9d Merge pull request #1122 from balena-io/bump-resin-multibuild-context-bug
Bump resin-multibuild version to fix docker-compose 'context' issue
2019-03-01 11:52:45 +00:00
7a258f022f Bump resin-multibuild version to fix docker-compose 'context' issue
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-03-01 11:38:16 +00:00
cbdf1c3ccf v9.14.1 2019-02-28 11:21:18 +01:00
dcab2404fa Merge pull request #1119 from balena-io/registry-secrets-help-msg-build-deploy
Add registry-secrets help documentation for the build and deploy commands
2019-02-28 10:19:30 +00:00
05e80094de Add registry-secrets help msg for build and deploy commands
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-02-27 21:58:44 +00:00
9fab994dec v9.14.0 2019-02-27 16:23:49 +01:00
6eddd1ccd3 Merge pull request #1118 from balena-io/1116-private-reg-build-deploy
Extend private registry support to balena build and deploy commands
2019-02-27 15:21:46 +00:00
211fb824a1 Extend private registry support to balena build and deploy commands
Resolves: #1116
Change-type: minor
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-02-27 13:16:09 +00:00
17c7b97abe v9.13.0 2019-02-27 14:14:56 +01:00
ac3c539d45 Merge pull request #1115 from balena-io/1114-private-reg-compose-image
Integrate new resin-multibuild major version (private registry auth for docker-compose 'image' instruction)
2019-02-27 13:13:15 +00:00
c1e94e661f Integrate new resin-multibuild major version (private docker registry
authentication support for the docker-compose 'image' instruction).

Resolves: #1114
Change-type: minor
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-02-27 10:33:25 +00:00
8a6ee5905a v9.12.7 2019-02-27 11:05:02 +01:00
36c636474d Merge pull request #1112 from balena-io/1108-device-uuid-starts-with-zero
Fix parsing of not-really-numeric device UUID parameters
2019-02-27 10:02:54 +00:00
0bff122b1c Fix parsing of not-really-numeric device UUID parameters
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-02-26 10:43:57 +00:00
2ffb9bb574 v9.12.6 2019-02-22 18:54:30 +01:00
6190d00644 Merge pull request #1111 from balena-io/fix-docs-toc
Fix regression in ee75ff and restore functionality to ToC in docs
2019-02-22 17:53:12 +00:00
67673a55f7 Fix regression in ee75ff and restore functionality to ToC in docs
Change-type: patch
Signed-off-by: Chris Crocker-White <chriscw@balena.io>
2019-02-22 16:37:57 +00:00
4448509d92 v9.12.5 2019-02-22 11:01:02 +01:00
8482961f7f Merge pull request #1110 from balena-io/1109-prettier-on-master
Re-run newest prettier on master
2019-02-22 11:59:17 +02:00
552f8cc4ef Re-run newest prettier on master
Resolves: #1109
Change-type: patch
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2019-02-22 10:02:02 +02:00
21b32633c5 v9.12.4 2019-02-20 22:53:48 +01:00
8863132e8e Merge pull request #1105 from balena-io/refactor-tunnel-command
tunnel: Refactor to improve log output
2019-02-20 21:52:18 +00:00
f72b556d92 tunnel: Refactor to improve log output
Improve the log output and error handling in the tunnel
command code.

Signed-off-by: Rich Bayliss <rich@balena.io>
Change-type: patch
2019-02-20 21:42:59 +00:00
4b7e0a19eb v9.12.3 2019-02-19 17:14:01 +01:00
3db92322ba Merge pull request #1101 from balena-io/add-tunnel-command
tunnel: Add the tunnel command
2019-02-19 16:12:24 +00:00
aac668dfca tunnel: Add the tunnel command
This allows a user to easily use the tunneling service
to open connections into their balena-managed devices.

Signed-off-by: Rich Bayliss <rich@balena.io>
Change-type: patch
2019-02-15 16:08:00 +00:00
0636dcf19d v9.12.2 2019-02-07 19:42:22 +01:00
3fca56e819 Merge pull request #1095 from balena-io/mixpanel-api-proxy
Remove fetching of Mixpanel token
2019-02-07 18:40:38 +00:00
6124d8c493 Remove fetching of Mixpanel token
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-02-07 17:06:10 +00:00
9ef99a3aa9 v9.12.1 2019-02-05 18:09:34 +01:00
66fc47edae Merge pull request #1094 from balena-io/commit-to-release
Rename localcommit to localrelease in target state for local mode
2019-02-05 17:07:22 +00:00
af948e76f3 Rename localcommit to localrelease in target state for local mode
Change-type: patch
Signed-off-by: Cameron Diver <cameron@balena.io>
2019-02-05 16:55:49 +00:00
dfd98efe8b v9.12.0 2019-01-22 15:02:07 +01:00
a8de833c43 Merge pull request #1083 from balena-io/dont-offer-to-disable-automatic-updates-when-device-is-pinned
Don't offer to disable automatic application updates when using pinning
2019-01-22 15:00:21 +01:00
3bff748fbe Don't offer to disable automatic application updates when using pinning
--pin-device-to-release disables the automatic updates disabling
message.

Change-type: minor
2019-01-22 14:44:16 +01:00
8adf66512b v9.11.2 2019-01-18 01:39:02 +01:00
d4313e6f95 Merge pull request #1079 from balena-io/692-local-flash-zip-img-doc
Update 'balena help local flash' documentation re zipped images
2019-01-18 00:37:48 +00:00
24fdfc9aef Update 'balena help local flash' documentation re zipped images
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-01-17 17:52:28 +00:00
e5f454bac3 v9.11.1 2019-01-16 20:56:29 +01:00
ca9ce5ed16 Merge pull request #1077 from balena-io/733-typescript-migration-notice
typescript: Add TypeScript migration notice to README file
2019-01-16 19:54:59 +00:00
2087622bd6 typescript: Add TypeScript migration notice to README file
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-01-16 17:45:01 +00:00
a651e27a20 v9.11.0 2019-01-16 15:13:33 +01:00
0fd0b6e1fd Merge pull request #1078 from balena-io/flash-compressed-images
Support compressed images in `balena local flash`
2019-01-16 15:12:03 +01:00
c63569d592 Support compressed images in balena local flash
Change-type: minor
2019-01-16 14:56:31 +01:00
7b7d00c642 v9.10.1 2019-01-15 21:37:37 +01:00
9e27889f91 Merge pull request #1056 from balena-io/typo
Fix up small docs typo
2019-01-15 15:36:19 -05:00
8bbb1966a4 Merge branch 'master' into typo 2019-01-15 14:27:02 -05:00
5d00e295fd v9.10.0 2019-01-14 13:58:32 +01:00
0d4a2b65a0 Merge pull request #1071 from balena-io/fast-boot
Improve startup time by adding fast-boot
2019-01-14 12:57:12 +00:00
2ba53649bd Improve startup time by adding fast-boot
Change-type: minor
Signed-off-by: Shaun Mulligan <shaun@balena.io>
2019-01-14 12:43:51 +00:00
31f4af721d v9.9.4 2019-01-14 12:20:58 +01:00
ce734ba783 Merge pull request #1073 from balena-io/lazy-load-sdk
Lazy load the sdk as much as possible
2019-01-14 11:19:24 +00:00
77196746b3 Lazy load the sdk as much as possible
Change-type: patch
2019-01-13 18:03:06 +00:00
99650ab732 v9.9.3 2019-01-13 15:17:19 +01:00
96b7d4a15d Merge pull request #1074 from balena-io/lazy-load-docker-toolbelt
Lazy-load docker-toolbelt
2019-01-13 14:15:50 +00:00
ce1aff1557 Lazy-load docker-toolbelt
Change-type: patch
2019-01-13 13:43:21 +00:00
9d5949e9d1 Merge branch 'master' into typo 2019-01-13 12:15:00 +01:00
3ca681a4a6 v9.9.2 2019-01-11 19:49:35 +01:00
49449e42be Merge pull request #1072 from balena-io/lazy-load-patterns
Lazy-load resin-cli-form and resin-cli-visuals to speed up startup
2019-01-11 18:48:05 +00:00
dad3167f16 Lazy-load drive list 2019-01-11 18:36:13 +00:00
3cc632fbbb Lazy-load etcher-sdk to speed up startup
Change-type: patch
2019-01-11 18:29:58 +00:00
f780d47198 Lazy-load resin-cli-form and resin-cli-visuals to speed up startup
Change-type: patch
2019-01-11 18:11:32 +00:00
e0bd6b9d4e v9.9.1 2019-01-11 18:23:33 +01:00
5cf0f7030d Merge pull request #1052 from balena-io/update-local-flash-action
Update "local flash" and "util available-drives" actions
2019-01-11 18:22:04 +01:00
77b763a88f Update util available-drives action
* switch from coffeescript to typescript
 * use etcher-sdk instead of drivelist

Change-Type: patch
2019-01-11 17:56:34 +01:00
f9390ceb10 Update lib/actions/local/flash.coffee
* switch to typescript
 * replace etcher-image-stream with etcher-sdk

Change-type: patch
2019-01-11 17:56:34 +01:00
bc41ff0540 v9.9.0 2019-01-10 14:52:32 +01:00
54e91eb074 Merge pull request #1057 from balena-io/access_old_repos
Request access to previously pushed release via `balena deploy`
2019-01-10 08:50:32 -05:00
a42a1a97ba Request access to previously pushed release via balena deploy
This access is used to cross mount the old layers, rather than
reuploading the layers each time.

Connects-to: #1045
Change-type: minor
Signed-off-by: Matthew McGinn <mamcgi@gmail.com>
2019-01-10 08:27:53 -05:00
f3d5e26e1e v9.8.0 2019-01-07 19:45:17 +01:00
99eae385b8 Merge pull request #1059 from balena-io/balena-cli-docs-deps
Moving docs from PR #1055
2019-01-07 10:43:52 -08:00
f6d67b94f3 Escape backticks in JS template literal
Escape backticks in JS template literal

Change-type: minor
Signed-off-by: Trevor Sullivan <trevor@balena.io>
2019-01-01 08:53:09 -08:00
2d9bb2130e Moving docs from PR #1055
Added documentation about the dependencies required to build balena-cli

Change-type: minor
Signed-off-by: Trevor Sullivan <trevor@balena.io>
2018-12-31 11:58:33 -08:00
10fff8f0f5 Merge branch 'master' of github.com:balena-io/balena-cli into typo 2018-12-31 08:22:32 -05:00
8ee994ce7d v9.7.0 2018-12-28 23:11:23 +01:00
86aed2185d Merge pull request #1055 from balena-io/balena-cli-deps
Adding information about dependency installation
2018-12-28 14:09:51 -08:00
64ec151e4b Added documentation about the dependencies required to build balena-cli
Change-type: minor
Signed-off-by: Trevor Sullivan <trevor@balena.io>
2018-12-28 13:00:43 -08:00
3e4e661b28 Fix up small docs typo
Change-type: patch
Signed-off-by: Matthew McGinn <mamcgi@gmail.com>
2018-12-28 08:40:18 -05:00
7713ca31e5 v9.6.0 2018-12-18 22:19:12 +01:00
b0da1b4811 Merge pull request #1041 from balena-io/1015-build-secrets-command-line-options
Add --registry-secrets option for balena push (private registry auth)
2018-12-18 21:17:14 +00:00
0f302d30ec Add push --registry-secrets option for private docker registry authentication
Change-type: minor
Signed-off-by: Paulo Castro <paulo@balena.io>
2018-12-18 00:01:15 +00:00
140e851fcd v9.5.0 2018-12-14 17:07:40 +02:00
b9b4343fd5 Merge pull request #1047 from balena-io/1013-os-configure-device-type
Add explicit device type option to `os configure` & `config generate`
2018-12-14 17:06:05 +02:00
eff49beb36 Wait for the device type compatibility check before showing the form
Also now fetches the device type from the image/API only once.

Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2018-12-14 15:46:27 +02:00
952d74207d Check that the provided device type option is of the same arch
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2018-12-14 15:46:27 +02:00
853d146457 Update the os configure examples to better explain --device-type
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2018-12-14 15:46:27 +02:00
97d6a39677 Add explicit device type option to os configure & config generate
Resolves: #1013
Change-type: minor
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2018-12-14 12:56:43 +02:00
095a597381 v9.4.1 2018-12-14 12:38:50 +02:00
a66aec6965 Merge pull request #1050 from balena-io/node6-types
Fix deploy action on node 6
2018-12-14 11:37:07 +01:00
03a3ef38e1 Fix deploy action on node 6
Downgrade @types/node to version 6 as we support node6

Change-type: patch
2018-12-14 11:26:59 +01:00
464d706920 v9.4.0 2018-12-10 23:22:40 +02:00
61dd5acb80 Merge pull request #1046 from balena-io/866-resource-tags
actions: Add resource tag operations
2018-12-10 23:21:04 +02:00
1e5cf8655e actions: Add resource tag operations
Resolves: #866
HQ: https://github.com/resin-io/hq/issues/150
HQ: https://github.com/resin-io/hq/pull/281
Change-type: minor
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2018-12-10 23:05:48 +02:00
f096f4f55f v9.3.6 2018-12-03 15:25:48 +02:00
85442c4634 Merge pull request #1042 from balena-io/fix-arch-docs
Make architecture checking more stringent when installing emulators
2018-12-03 14:23:58 +01:00
a357405f3a Make architecture checking more stringent when installing emulators
Also change the documentation to an armv7hf.

Change-type: patch
Signed-off-by: Cameron Diver <cameron@balena.io>
2018-12-03 13:10:54 +00:00
6070ee0f83 v9.3.5 2018-11-28 20:30:15 +02:00
f8721a324d Merge pull request #1040 from balena-io/fix-event-stream-vulnerability
Fix potential dependency security issue
2018-11-28 20:28:48 +02:00
ca861a6349 Fix potential dependency security issue
Until further investigation it is recommended to pin event-stream
to v3.3.4.

Change-type: patch
See: https://github.com/dominictarr/event-stream/issues/116
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2018-11-28 20:15:28 +02:00
493c6576c3 v9.3.4 2018-11-28 19:53:54 +02:00
a8765af589 Merge pull request #1037 from balena-io/update-docker-qemu-transpose
Update dependencies and pin event-stream
2018-11-28 18:52:12 +01:00
ca8484b466 Update dependencies
Change-type: patch
Signed-off-by: Cameron Diver <cameron@balena.io>
2018-11-28 17:03:47 +00:00
7a8d746a54 v9.3.3 2018-11-27 17:32:16 +02:00
1cffcd9b9e Merge pull request #1039 from balena-io/1038-is-initialize-nonbluebird-promise
actions/os-initialize: Convert Promise to a Bluebird one
2018-11-27 17:30:13 +02:00
b6c041c9b5 actions/os-initialize: Convert Promise to a Bluebird one
Resolves: #1038
Change-type: patch
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2018-11-27 10:34:21 +02:00
47b35db03e v9.3.2 2018-11-26 11:04:04 +02:00
78985ff633 Merge pull request #1034 from balena-io/1033-fix-os-configure-wo-version
actions/os: Fix os configure using bluebird methods on plain promise
2018-11-26 11:02:50 +02:00
93a5380c09 actions/os: Fix os configure using bluebird methods on plain promise
Resolves: #1033
Connects-to: #1007
Change-type: patch
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2018-11-26 10:00:49 +02:00
6677f1faf5 v9.3.1 2018-11-26 10:00:27 +02:00
e7b32e941a Merge pull request #1035 from balena-io/1007-document-configure-version
actions/config: Fix examples to include --version as required
2018-11-26 09:58:59 +02:00
5abd240d50 actions/config: Fix examples to include --version as required
Connects-to: #1007
Change-type: patch
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2018-11-23 16:01:01 +02:00
759c2d4a6f v9.3.0 2018-11-22 16:28:28 +02:00
92772952fd Merge pull request #1027 from balena-io/update-node-ext2fs-and-preload
Stop pinning ext2fs and update preload to ^8.0.4
2018-11-22 15:26:55 +01:00
2f53cbf088 Stop pinning ext2fs and update preload to ^8.0.4
* ext2fs fixes build issues on 32 bit Linux platforms
 * preload fixes issues with the --dont-check-arch flag

Change-type: minor
2018-11-22 14:46:45 +01:00
c3b74a869a v9.2.2 2018-11-20 15:38:57 +02:00
841d1927a9 Merge pull request #1029 from balena-io/fix-os-config
Fix missing import in `os configure`
2018-11-20 15:36:24 +02:00
06c450e9a5 Fix missing import in os configure
Fixes #1028

Change-type: patch
2018-11-20 13:14:49 +02:00
67de638c76 v9.2.1 2018-11-19 16:15:29 +01:00
c90b8eef97 Merge pull request #1026 from balena-io/case-insensitive-push
Add case-insensitive checking for application names in balena push
2018-11-19 16:13:17 +01:00
6ad4598e7e Add case-insensitive checking for application names in balena push
The filter is added with an `as any`, as the typings dont yet support
using $eq and $ne.

Change-type: patch
Signed-off-by: Cameron Diver <cameron@balena.io>
2018-11-19 12:49:00 +00:00
fd580083d5 v9.2.0 2018-11-16 19:14:15 +01:00
a0003c5f13 Merge pull request #1012 from balena-io/optional-os-version
Make specifying the version during configuration optional
2018-11-16 20:09:35 +02:00
8291c96e69 Make specifying the version during configuration optional
`version` used to be optional but it seems we recently had to make it a required parameter. However it really feels redundant when all it’s used for is to determine whether the command should issue a legacy user API key or a provisioning key.

This makes version optional but tries to figure it out by itself by reading os-release from the image's boot partition. This is not foul-proof however, and while it'll work with most recent images it won't work with all and in that case it'll bail out and only then warn the user to specify it via the --version argument.

Change-type: minor
2018-11-16 19:39:43 +02:00
561325e66d v9.1.4 2018-11-16 18:21:48 +01:00
a840f39a91 Merge pull request #1023 from balena-io/gh-templates
Extend the github repository templates
2018-11-16 18:32:44 +02:00
64f9b50e40 Extend the github repository templates
Change-type: patch
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2018-11-16 17:45:20 +02:00
0273d2e02c v9.1.3 2018-11-16 16:00:43 +01:00
daf3b980ef Merge pull request #1025 from balena-io/add-promote-to-docs
Include `join` and `leave` commands in API documentation
2018-11-16 16:58:35 +02:00
6e36cd139a Include join and leave commands in API documentation
Change-type: patch
2018-11-16 16:02:43 +02:00
9ca76348ff v9.1.2 2018-11-13 19:08:00 +01:00
58a5725ad2 Merge pull request #1022 from balena-io/fix-standalone-deploy
Fix build & deploy commands in standalone build
2018-11-13 19:06:01 +01:00
116c3c787c Fix build & deploy commands in standalone build
Change-type: patch
2018-11-13 18:43:00 +01:00
74a896b3cd v9.1.1 2018-11-12 17:17:16 +01:00
e2ebac27ea Merge pull request #1020 from balena-io/1019-fix-qemu
Fix the architecture string used when downloading qemu versions
2018-11-12 17:15:49 +01:00
b799f3a46d Fix the architecture string used when downloading qemu versions
Closes: #1019
Change-type: patch
Signed-off-by: Cameron Diver <cameron@balena.io>
2018-11-12 15:57:04 +00:00
3a3cfbc85e v9.1.0 2018-11-08 11:27:20 +01:00
161b9454c2 Merge pull request #1016 from balena-io/qemu3-aarch-support
Qemu3 multiple architecture support
2018-11-08 11:25:40 +01:00
b83b7145af Remove unnecessary parentheses 2018-11-07 20:58:27 +01:00
26c4e466bd Store separate local qemu binaries for aarch64 and arm architectures.
Copy the correct binary into the local build context when executing a build.
2018-11-07 17:49:20 +01:00
42f752e400 Use existing tar-stream dependency to untar qemu archive rather than adding a new dependency. 2018-11-07 17:49:20 +01:00
0b67a40d57 Update qemu to v3, and automatically use the correct architecture (arm/aarch64)
When building with emulation mode enabled, this downloads the version of qemu
appropriate to the architecture of the project (either arm or aarch64).

Change-type: minor
2018-11-07 17:49:20 +01:00
69ab9788fc v9.0.3 2018-11-07 16:47:02 +01:00
7972187b77 Merge pull request #1006 from balena-io/api-key-graduation
Mark api keys in the CLI as non-experimental
2018-11-07 16:45:02 +01:00
a809847d60 Mark api keys in the CLI as non-experimental
After the recent SDK updates, they should now work everywhere

Change-type: patch
2018-11-07 16:30:56 +01:00
203285bab9 v9.0.2 2018-11-06 13:08:14 +01:00
52c7a098cc Merge pull request #1014 from balena-io/disable-broken-config-validation
Stop validating device config, now that it's API-generated
2018-11-06 13:06:17 +01:00
75bc937995 Stop validating device config, now that it's API-generated
Change-type: patch
2018-11-06 12:31:05 +01:00
dd41145912 v9.0.1 2018-11-01 15:18:58 +01:00
0983bf02e2 Merge pull request #1009 from balena-io/ignored-balena-dir
Dont ignore balena metadata directories when balena pushing
2018-11-01 15:17:26 +01:00
0deb59b6e2 Dont ignore balena metadata directories when balena pushing
Change-type: patch
Signed-off-by: Cameron Diver <cameron@balena.io>
2018-11-01 13:58:54 +00:00
fdc9fd67d8 v9.0.0 2018-10-29 22:46:27 +01:00
01eb4b473d Merge pull request #1005 from resin-io/v9-meta-branch
Release Balena-CLI
2018-10-29 22:44:16 +01:00
4ff42c11e6 Remove rename warning 2018-10-29 22:30:21 +01:00
85d82ab9ca Merge pull request #997 from resin-io/984-the-big-rename
Rename everything from 'resin' to 'balena'
2018-10-29 22:29:03 +01:00
dc6cde2cf1 Change env var commands to set app-wide env vars, using the new SDK
Change-type: major
2018-10-29 22:29:03 +01:00
ea1c1bb8d4 Merge pull request #994 from resin-io/978-oss-flow-slug
utils/promote: Use the application slug for filtering & presenting
2018-10-29 22:29:02 +01:00
c6eca9f895 Rewrite the env commands in TypeScript 2018-10-29 22:29:02 +01:00
e71f622453 Merge pull request #979 from resin-io/978-oss-flow
Add support for the Opensource provisioning flow
2018-10-29 22:29:02 +01:00
b6266878d4 utils/promote: Use the application slug for filtering & presenting
Change-type: minor
Depends-on: https://github.com/resin-io/resin-api/pull/1570
Depends-on: https://github.com/resin-io/resin-sdk/pull/596
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2018-10-29 22:29:02 +01:00
4907fccf48 Rename everything from 'resin' to 'balena'
Change-type: major
2018-10-29 22:29:02 +01:00
f4b84941cd package.json: Use the pre-release balena SDK
Signed-off-by: Thodoris Greasidis <thodoris@resin.io>
2018-10-29 22:29:02 +01:00
c2df87bcc6 Code formatting
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2018-10-29 22:29:02 +01:00
79f33c749b fix deploy 2018-10-29 22:29:02 +01:00
fd316167d8 Sort device types by name
Change-type: minor
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2018-10-29 22:29:02 +01:00
f60d857c93 utils/promote: Do not rely on the user to always be there
Change-type: minor
Depends-on: https://github.com/resin-io/resin-sdk/pull/595
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2018-10-29 22:29:02 +01:00
31628cfdcb promote: Use ResinSdk type namespace
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2018-10-29 22:29:02 +01:00
4d42f74c0c Add support for the Opensource provisioning flow
Connects-to: #978
Change-type: major
Depends-on: https://github.com/resin-io/resin-sdk/pull/594
HQ: https://github.com/resin-io/balena/pull/1140
Signed-off-by: Thodoris Greasidis <thodoris@resin.io>
2018-10-29 22:29:02 +01:00
13729ec4b6 Merge pull request #985 from pdcastro/resin_rename_notice
Add rename notice to resin-cli
2018-10-29 22:28:08 +01:00
8dc4c0871a v8.1.0 2018-10-24 12:44:15 +02:00
207e080b9e Merge pull request #995 from resin-io/add-dev-bin
chore: Add on the fly transpiled bin
2018-10-24 13:41:21 +03:00
39fe63fb2d README: Add development guidelines section
Change-type: minor
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2018-10-24 13:06:03 +03:00
24c2ffefc9 chore: Add on the fly transpiled bin
Adds an alternative bin file that does not require building the project but
loads the source files directly.

Change-type: minor
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2018-10-24 13:05:02 +03:00
c293a1742d v8.0.3 2018-10-22 18:39:13 +02:00
cb46756d31 Merge pull request #992 from resin-io/v8-meta-branch-sshsdk
ssh: Move SSH from resin-sdk-preconfigured to resin-sdk
2018-10-22 18:37:20 +02:00
332e731023 ssh: Move from resin-sdk-preconfigured to resin-sdk
Change-type: patch
Signed-off-by: Will Boyce <will@resin.io>
2018-10-22 16:46:56 +01:00
f9263975bc Add rename notice to resin-cli
Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
2018-10-22 14:09:07 +01:00
67ebf7aa19 v8.0.2 2018-10-20 19:09:46 +02:00
2b52d5edbc Merge pull request #990 from resin-io/drop-sdk-preconfigured-app-patterns
Drop sdk preconfigured app patterns
2018-10-20 19:07:57 +02:00
948e6ea6f8 utils/patterns: Drop resin-sdk-preconfigured
Change-type: patch
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2018-10-20 18:35:34 +02:00
ca9247fb19 actions/app: Drop resin-sdk-preconfigured
Change-type: patch
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
2018-10-20 18:35:34 +02:00
73455b4264 v8.0.1 2018-10-20 15:26:04 +02:00
28b0793fc9 Merge pull request #993 from resin-io/fix-push
Update dockerignore to fix escSL bug
2018-10-20 15:24:28 +02:00
c904726259 Update dockerignore to fix escSL bug
Change-type: patch
2018-10-20 14:54:33 +02:00
6606b65c9b v8.0.0 2018-10-19 17:31:41 +02:00
61160fd2f5 Merge pull request #991 from resin-io/v8-meta-branch
Release CLI v8
2018-10-19 17:29:40 +02:00
bf71f9ea16 Merge pull request #981 from resin-io/local-mode-v2
Local mode v2
2018-10-19 17:09:28 +02:00
fe751fdb23 Check supervisor version before attempting to do a local push
Signed-off-by: Cameron Diver <cameron@resin.io>
2018-10-19 16:45:23 +02:00
947f91d570 Support multicontainer local mode in resin push
Change-type: minor
Signed-off-by: Cameron Diver <cameron@resin.io>
2018-10-19 16:44:56 +02:00
c5d4e30e24 logger: Add logs logging function
Signed-off-by: Cameron Diver <cameron@resin.io>
2018-10-19 16:44:53 +02:00
f560aa7523 export resolveProject function from compose module
Signed-off-by: Cameron Diver <cameron@resin.io>
2018-10-19 16:44:49 +02:00
6bcfb2dd51 logs: Add log build function to logger
Signed-off-by: Cameron Diver <cameron@resin.io>
2018-10-19 16:44:44 +02:00
bf062124f7 compose: Add compose typings
Signed-off-by: Cameron Diver <cameron@resin.io>
2018-10-19 16:44:39 +02:00
221666f59a Stop accepting resin-compose.yml as a build composition definition
These files are not supported by any other part of the resin
infrastructure, and it could cause confusion with it not being
supported everywhere. The idea was originally added because we
thought we might need to make extensions on docker-compose, but
that hasn't happened.

Change-type: major
Signed-off-by: Cameron Diver <cameron@resin.io>
2018-10-19 16:43:49 +02:00
4369a2d161 tconfig: Add skipLibCheck to tsconfig
Signed-off-by: Cameron Diver <cameron@resin.io>
2018-10-19 16:43:46 +02:00
cd6ee4ef5e Send push source packages as gzipped data
Change-type: minor
Signed-off-by: Cameron Diver <cameron@resin.io>
2018-10-19 16:43:39 +02:00
872b17cf24 refactor: Allow setting of a remote build error message
Signed-off-by: Cameron Diver <cameron@resin.io>
2018-10-19 16:43:32 +02:00
88e11347bc tests: Add tests for ignore files
Signed-off-by: Cameron Diver <cameron@resin.io>
2018-10-19 16:43:28 +02:00
a3dd489c70 Respect ignore files when tarring sources
This commit brings in the ignore and dockerignore libraries, which when
provided with the patterns in the aforementioned files will ignore them.

Change-type: major
Closes: 889
Signed-off-by: Cameron Diver <cameron@resin.io>
2018-10-19 16:43:22 +02:00
0c1c108b2b Check for correct architecture when preloading, instead of correct device type
Preload will now propose to preload any app that matches the image
architecture.

Change-type: major
Signed-off-by: Alexis Svinartchouk <alexis@resin.io>
2018-10-19 16:43:02 +02:00
f02ed43f33 Default preload boolean parameters to false
Change-type: patch
Signed-off-by: Alexis Svinartchouk <alexis@resin.io>
2018-10-19 16:42:51 +02:00
63c3d7ceee fix: Apply prettier to merged files
Signed-off-by: Cameron Diver <cameron@resin.io>
2018-10-19 16:42:48 +02:00
dac45a884e dev: Add fast test npm task, to speed development
Currently running the tests is painfully slow, this commit adds a task
which will run the bare minimum build, and then the tests, speeding up
the process by an order of magnitude.

I had to repeat `gulp test`, instead of reusing `npm run test`, so that
the pretest task isn't ran too.

Signed-off-by: Cameron Diver <cameron@resin.io>
2018-10-19 16:42:42 +02:00
ec589c2639 Correctly error out on failed remote builds
The push command was relying on the output from the builder to indicate
the build status, but this isn't helpful for CI. This commit makes the
remote build module respect the `isError` flag which the builder sends
in any errors. Any errors which come from the builder indicate the
release will not be deployed.

Change-type: patch
Signed-off-by: Cameron Diver <cameron@resin.io>
2018-10-19 16:42:14 +02:00
f65e777d1b Bump tsconfig target to es6
Change-type: major
Signed-off-by: Cameron Diver <cameron@resin.io>
2018-10-19 16:42:06 +02:00
684ac9fa24 v7.10.9 2018-10-18 21:08:35 +02:00
330cbc6a68 Merge pull request #989 from resin-io/sdk-references-update
Update sdk references in wizzard.coffee
2018-10-18 21:06:38 +02:00
14bfca8c3a v7.10.8 2018-10-18 20:14:43 +02:00
20c07d31b2 Merge pull request #987 from resin-io/sdk-references-update
Update sdk references in device.coffee
2018-10-18 20:12:08 +02:00
64b4f67477 Update sdk references in wizzard.coffee
Change-type:patch
2018-10-18 18:53:03 +02:00
a8ceadc300 v7.10.7 2018-10-18 17:25:57 +02:00
973d25f467 Merge pull request #986 from resin-io/sdk-references-update
Update sdk sdk references in auth.coffee
2018-10-18 17:24:02 +02:00
0d06701e2f Update sdk references in notes.coffee
Change-type:patch
2018-10-18 16:22:35 +02:00
379f1cc217 Update sdk references in device.coffee
Change-type:patch
2018-10-18 16:08:29 +02:00
7b7ae4ff89 Update sdk sdk references in auth.coffee
Change-type:patch
2018-10-18 14:51:03 +02:00
8e83a401eb v7.10.6 2018-10-03 06:58:41 -07:00
2d1891a182 Merge pull request #976 from resin-io/975-fix-preload-examples
Fix formatting of preload examples
2018-10-03 15:56:56 +02:00
8df066df12 Fix formatting of preload examples
Based on https://github.com/resin-io/docs/pull/915 from @drjasonharrison-vp-eio

Change-type: patch
Signed-off-by: Tim Perry <tim@resin.io>
2018-10-03 15:31:24 +02:00
bd59f95e1a v7.10.5 2018-09-25 07:09:26 -07:00
2b982a1c0c Merge pull request #972 from resin-io/readme-typo
README: Fix typo
2018-09-25 15:08:01 +01:00
ab64fbc904 README: Fix typo
Change-type: patch
Signed-off-by: Lucian Buzzo <lucian.buzzo@gmail.com>
2018-09-25 13:35:35 +01:00
733b98f072 v7.10.4 2018-09-24 10:08:55 -07:00
7c538a3658 Merge pull request #967 from resin-io/966-register-print-uuid
device: When registering, print the uuid
2018-09-24 19:07:15 +02:00
8298ba5765 device: When registering, print the uuid
This restores the behavior from before #911,
which is useful from some users.

Closes #966

Change-type: patch
Signed-off-by: Pablo Carranza Velez <pablocarranza@gmail.com>
2018-09-24 15:18:40 +02:00
33a23773d8 v7.10.3 2018-09-19 09:17:52 -07:00
21a3b82845 Merge pull request #971 from resin-io/add-emulated-to-build-docs
Include --emulated in the example resin build parameters
2018-09-19 18:16:28 +02:00
8688eb5da0 Include --emulated in the example resin build parameters
Change-type: patch
Signed-off-by: Tim Perry <tim@resin.io>
2018-09-19 15:34:29 +02:00
5b0ea9673f v7.10.2 2018-09-18 09:17:50 -07:00
44fd8adeba Merge pull request #970 from resin-io/969-resin-semver
dependencies: Update resin-semver version to support Balena OS
2018-09-18 17:15:09 +01:00
a5e03d55c3 dependencies: Update resin-semver version to support Balena OS
Connects to #969

Change-type: patch
Signed-off-by: Lucian Buzzo <lucian.buzzo@gmail.com>
2018-09-18 14:23:10 +01:00
80629322ea v7.10.1 2018-09-11 05:29:41 -07:00
946efbcb7f Merge pull request #965 from resin-io/964-drop-npm-deploy
Stop Travis deploying to npm (now handled by concourse)
2018-09-11 14:28:11 +02:00
be8a314d2b Stop Travis deploying to npm (now handled by concourse)
Change-type: patch
Signed-off-by: Tim Perry <tim@resin.io>
2018-09-11 13:58:06 +02:00
0a7203cafe v7.10.0 2018-09-11 04:21:19 -07:00
786fed0151 Merge pull request #963 from resin-io/update-resin-cli-form
Update resin-cli-form to 2.x
2018-09-11 12:17:22 +01:00
9cd8228a20 Update resin-cli-form to 2.x
Change-type: minor
Signed-off-by: Pagan Gazzard <page@resin.io>
2018-09-10 18:31:51 +01:00
652b5f22dd v7.9.4 2018-09-10 06:34:48 -07:00
eed3c06789 Merge pull request #911 from resin-io/fix_pre_provision
Device api keys are no longer used in the registration process
2018-09-10 15:33:37 +02:00
3b283d4a98 Device api keys are no longer used in the registration process
Change-type: patch
Signed-off-by: Theodor Gherzan <theodor@resin.io>
2018-09-10 12:30:51 +01:00
bc6b5ba7b3 Auto-merge for PR #952 via VersionBot
Fix configuration hangs with some images using a larger threadpool
2018-08-20 15:37:22 +00:00
74789ae88f v7.9.3 2018-08-20 15:29:02 +00:00
295d6dee74 Fix configuration hangs with some images by expanding the threadpool
Change-type: patch
Signed-off-by: Tim Perry <tim@resin.io>
2018-08-20 17:06:26 +02:00
5010a1e312 Auto-merge for PR #946 via VersionBot
Add warning about re-enabling automatic updates
2018-08-15 21:39:23 +00:00
3c2f7ea622 v7.9.2 2018-08-15 21:31:24 +00:00
94f02f0ad8 Add warning about re-enabling automatic updates
Change-type: patch
Signed-off-by: Pagan Gazzard <page@resin.io>
2018-08-15 14:20:11 -07:00
375f84b24e Auto-merge for PR #942 via VersionBot
Fix errors in `getRequestStream` not being propogated
2018-08-15 18:08:59 +00:00
06c649dfd0 v7.9.1 2018-08-15 17:59:46 +00:00
71eca70a22 Fix errors in getRequestStream not being propogated
Change-type: patch
Signed-off-by: Pagan Gazzard <page@resin.io>
2018-08-14 18:21:10 -07:00
53c7bc622c Auto-merge for PR #903 via VersionBot
Support emulated and nocache options for remote builds
2018-08-09 14:52:03 +00:00
975ae45e49 v7.9.0 2018-08-09 14:42:30 +00:00
e7c68c1a5c Support emulated and nocache options for remote builds
Change-type: minor
Closes: #901
Signed-off-by: Cameron Diver <cameron@resin.io>
2018-08-09 14:36:34 +01:00
5beeb78220 Auto-merge for PR #939 via VersionBot
Fix bug where the sudo helper failed in os initialize
2018-08-09 10:37:53 +00:00
c90ba7aa0f v7.8.6 2018-08-09 10:29:50 +00:00
802ccc1b9a Fix bug where the sudo helper failed in os initialize
Change-type: patch
Signed-off-by: Tim Perry <tim@resin.io>
2018-08-09 12:11:26 +02:00
b6ef251625 Auto-merge for PR #934 via VersionBot
Add an env vars example config to the local push docs
2018-08-09 10:09:37 +00:00
fd707d6a07 v7.8.5 2018-08-09 10:01:55 +00:00
392cd8569f Make build trigger hash examples clearer
Signed-off-by: Tim Perry <tim@resin.io>
2018-08-09 11:47:21 +02:00
e32eda26d9 Update .resin-sync.yml docs for local push and include example env vars
Change-type: patch
Signed-off-by: Tim Perry <tim@resin.io>
2018-08-09 11:21:45 +02:00
d8aaccf80c Update typed-error to fix some TS complaints
Signed-off-by: Tim Perry <tim@resin.io>
2018-08-09 11:21:45 +02:00
d5fd5f5f2d Auto-merge for PR #936 via VersionBot
Update klaw now that the fork changes has been finished & released
2018-08-02 10:37:40 +00:00
2cb69c12f1 v7.8.4 2018-08-02 10:29:33 +00:00
7c75346a1a Update klaw
The changes from our fork have now been completed and released

Change-type: patch
Signed-off-by: Tim Perry <tim@resin.io>
2018-08-01 16:43:26 +02:00
148d15b6d9 Auto-merge for PR #931 via VersionBot
Follow links found during builds
2018-07-25 14:07:19 +00:00
a46a79df59 v7.8.3 2018-07-25 13:58:28 +00:00
e350f9b335 Follow links found during builds
Change-Type: patch
2018-07-25 12:38:17 +02:00
bd00773f1b Auto-merge for PR #929 via VersionBot
Update reconfix to fix volume signature errors in local configure
2018-07-25 10:23:37 +00:00
ef3c7f0fd6 v7.8.2 2018-07-25 10:13:48 +00:00
f4f44f978e Update reconfix to fix volume signature errors in local configure
Change-Type: patch
2018-07-24 20:57:40 +02:00
442416efc3 Auto-merge for PR #930 via VersionBot
Be explicit about how much initial history log tailing includes
2018-07-20 18:07:00 +00:00
ef33ffedcf v7.8.1 2018-07-20 17:38:09 +00:00
430d4aeaa7 Be explicit about how much initial history log tailing includes
Change-Type: patch
2018-07-20 16:32:31 +02:00
171632f83f Auto-merge for PR #895 via VersionBot
Add join/leave commands to promote and move devices between platforms
2018-07-20 12:36:20 +00:00
1fa7141b58 v7.8.0 2018-07-20 10:40:22 +00:00
916cc36430 Lazily import resin-image-fs
If for whatever reason resin-image-fs is not importable — eg. if it’s built for another arch — any command that imports `helpers.ts` will just quit without any error/traceback.
2018-07-20 13:04:26 +03:00
27b877dd33 Forward root CA to device config if one is present 2018-07-19 22:34:31 +03:00
5cbe1c410f Add join/leave commands to promote and move devices between platforms
Both commands work with local devices by remotely invoking the `os-config` executable via SSH. This requires an as of yet unreleased resinOS (that will most likely be v2.14) and the commands ascertain compatibility merely by looking for the `os-config` executable in the device, and bail out if it’s not present.

`join` and `leave` accept a couple of optional arguments and implement a wizard-style interface if these are not given. They allow to interactively select the device and the application to promote to. If the user has no apps, `join` will offer the user to create one. `join` will also offer the user to login or create an account if they’re not logged in already without exiting the wizard.

`resin-sync` (that's used internally to discover local devices) requires admin privileges. If no device has been specified as an argument, the commands will launch the device scanning process in a privileged subprocess via two new internal commands: `internal sudo` and `internal scanDevices`. This avoids having the user to invoke the commands with sudo and only request escalation if truly needed. This commit also removes the dependency to “president”, implementing “sudo” functionality within the CLI.

Change-Type: minor
2018-07-19 22:18:02 +03:00
7846af390e Improve selectFromList function signature to be much more reusable 2018-07-19 21:53:43 +03:00
79d9ebc805 Auto-merge for PR #923 via VersionBot
Update OS & config actions to the MC SDK, and add a --version option
2018-07-17 15:43:30 +00:00
25b853c535 v7.7.4 2018-07-17 15:35:26 +00:00
a93141343f Update TypeScript to 2.8.1
Change-Type: patch
2018-07-17 16:48:14 +02:00
9a467c5ecd Pin all type modules 2018-07-17 15:59:31 +02:00
70be2ae596 Tweaks to config options handling after review 2018-07-17 15:38:38 +02:00
36eb0a108e Post-review tweaks to OS actions 2018-07-13 19:34:59 +02:00
0bf6fb1739 Add --version options to os configure & config generate
This is used to ensure the correct type of API key is used in all
configuration.

Change-Type: patch
2018-07-13 19:34:59 +02:00
892adf4c47 Update OS & config actions to the latest SDK
Fixes #915
Change-Type: patch
2018-07-13 19:34:59 +02:00
5d1d004b72 Auto-merge for PR #927 via VersionBot
Update the CLI deploy key since npm invalidated the old one
2018-07-13 17:21:18 +00:00
dea5a60b2d v7.7.3 2018-07-13 17:05:32 +00:00
652a1b7650 Update the deploy key since npm invalidated the old one
Change-Type: patch
2018-07-13 16:39:56 +02:00
350843af1e Auto-merge for PR #926 via VersionBot
Pin ext2fs to 1.0.7 to avoid temporary deployment issues
2018-07-13 11:40:25 +00:00
e04c4a8ee3 v7.7.2 2018-07-13 11:33:13 +00:00
9d0c3f7535 Pin ext2fs to 1.0.7 to avoid temporary deployment issues
Change-Type: patch
2018-07-13 13:20:53 +02:00
9561d4da2e Auto-merge for PR #925 via VersionBot
Update logs to use new v10 MC SDK
2018-07-12 13:59:28 +00:00
8296dcf946 v7.7.1 2018-07-12 13:52:10 +00:00
e62e8b88c2 Simplify logs promises after review 2018-07-12 15:38:27 +02:00
4388a248b9 Make sure we don't duplicate historical logs when streaming 2018-07-12 15:23:33 +02:00
f9cf0aaf23 Remove a couple of artifacts of the pubnub logs implementation 2018-07-12 15:10:16 +02:00
dc9ee09838 Update CLI to SDK v10 (include new API logs)
Change-Type: patch
2018-07-12 01:03:16 +02:00
7cb27283c5 Update logs action to use the MC SDK 2018-07-12 01:03:16 +02:00
10a9840b34 Auto-merge for PR #921 via VersionBot
Add --generate-device-api-key parameter to config generate
2018-07-11 04:28:25 +00:00
ce3e04bfe8 v7.7.0 2018-07-11 04:21:42 +00:00
52f93f8f12 Add --generate-device-api-key parameter to config generate
Change-Type: minor
2018-07-10 19:57:56 +02:00
af9e1a122d Auto-merge for PR #910 via VersionBot
Make local commands more resilient to unnamed containers
2018-06-28 16:26:11 +00:00
9017b8ec11 v7.6.2 2018-06-28 12:55:34 +00:00
bf4f687a2a Make local commands more resilient to unnamed containers
Change-Type: patch
2018-06-28 12:34:31 +02:00
9d4e6eb825 Auto-merge for PR #907 via VersionBot
Make sure 'resin push' is included in the CLI docs
2018-06-26 17:22:44 +00:00
fba4afb7d2 v7.6.1 2018-06-26 17:15:20 +00:00
8c74f784f7 Make sure 'resin push' is included in the docs
Fixes #906
Change-Type: patch
2018-06-26 19:00:20 +02:00
69ca1ffa59 Auto-merge for PR #896 via VersionBot
Support pinned release preloading
2018-06-20 17:00:12 +00:00
7d1b00877e v7.6.0 2018-06-20 16:50:01 +00:00
1a48fed1f7 Support pinned release preloading
Change-type: minor
Closes: #886
Signed-off-by: Cameron Diver <cameron@resin.io>
2018-06-13 12:29:30 +01:00
bc86359e63 Auto-merge for PR #893 via VersionBot
Document Python native build dependency
2018-06-12 18:33:47 +00:00
f6822f1502 v7.5.2 2018-06-12 18:26:12 +00:00
398c34d842 Includes new prettier changes, and pin prettier to stop more appearing 2018-06-12 17:43:15 +02:00
72a893be95 Document Pyhton native build dependency
Change-Type: patch
2018-06-12 17:11:45 +02:00
7b23b0e103 Auto-merge for PR #887 via VersionBot
Add a multicontainer caveat to the env var commands
2018-06-01 11:10:33 +00:00
0ce7878042 v7.5.1 2018-06-01 10:49:15 +00:00
da8483e6a6 Add a multicontainer caveat to the env var commands
Change-Type: patch
2018-06-01 12:37:29 +02:00
16f70fd946 Auto-merge for PR #883 via VersionBot
Update resin-compose-parse dependency version
2018-05-31 16:16:47 +00:00
78aa898b37 v7.5.0 2018-05-31 16:07:38 +00:00
b7f94a222d Update resin-compose-parse dependency version to 1.10.2
Change-type: minor
2018-05-30 11:57:04 -03:00
7bea2c26b8 Auto-merge for PR #879 via VersionBot
Update SDK for device commands, so we show new device dashboard URLs
2018-05-24 14:13:01 +00:00
7c178b8095 v7.4.1 2018-05-24 14:03:02 +00:00
865f085094 Make sure we still show the device commit, despite API changes 2018-05-24 14:43:45 +01:00
28fe69fe94 Update to latest SDK in lots of easy device commands 2018-05-18 20:05:24 +02:00
232cf8d426 Update SDK in resin device(s) to ensure the dashboard URL is correct
Fixes #768

Change-Type: patch
2018-05-18 20:00:40 +02:00
22e74983b0 Auto-merge for PR #868 via VersionBot
Add push command which starts a build on remote resin servers
2018-05-10 12:44:43 +00:00
c88dd2257a v7.4.0 2018-05-10 12:28:32 +00:00
439d8d396f Add push command which starts a build on remote resin servers
Change-type: minor
Connects-to: #843
2018-05-10 11:43:45 +01:00
6d8086c09b Auto-merge for PR #874 via VersionBot
Handle failed requires & missing bindings
2018-05-03 17:56:53 +00:00
e85f252f29 v7.3.8 2018-05-03 17:49:06 +00:00
4b818ad51c Style improvements after review 2018-05-03 18:59:28 +02:00
c2518448a3 Catch require errors and provide helpful instructions
Change-Type: patch
2018-05-03 16:01:40 +02:00
e7a8deed05 Inline the entire resin-cli-errors module
It's awkward that error handling requires you to go to a different
package, it makes things more complicated, and there's nowhere else that
really should be reusing this logic. Let's inline it, so we can
deprecate the module entirely.

Change-Type: patch
2018-05-03 15:15:03 +02:00
0ac599d20c Auto-merge for PR #871 via VersionBot
Pin node types to v9.0.0 to avoid build errors with transient dependencies
2018-04-30 15:25:23 +00:00
7d7074e6b7 v7.3.7 2018-04-30 15:18:31 +00:00
35ca34d07d Pin node types to v9.0.0 to avoid build errors with transient dependencies
Change-type: patch
Signed-off-by: Cameron Diver <cameron@resin.io>
2018-04-30 16:09:12 +01:00
90d7316b4c Auto-merge for PR #870 via VersionBot
Update resin-image-fs to stop non-config commands failing in node 10
2018-04-30 09:54:09 +00:00
904b4e96d9 v7.3.6 2018-04-30 09:34:40 +00:00
2c46c59a79 Update resin-image-fs to stop non-config commands failing in node 10
This doesn't fix actual usage of image fs, just makes it possible to
stop commands that don't use it from failing entirely.

Connects-To: #869
Change-Type: patch
2018-04-30 11:14:39 +02:00
297ff86895 Auto-merge for PR #858 via VersionBot
Don't show Docker container status from devices, as it can be wrong
2018-04-18 19:08:16 +00:00
a154401424 v7.3.5 2018-04-18 19:00:21 +00:00
ad2713fc00 Don't show Docker container status from devices, as it can be wrong
The status includes a description of how long the device has been in
this state (Up 6 weeks), which is frequently wrong as when the device
first starts up its clock isn't up to date. It's confusing and messy,
best to just remove it entirely.

Fixes #828
Change-Type: patch
2018-04-18 20:16:44 +02:00
6388cfaf40 Auto-merge for PR #865 via VersionBot
Include resin compose schemas in the standalone build
2018-04-18 16:41:50 +00:00
167f38e342 v7.3.4 2018-04-18 16:27:52 +00:00
919b3c3435 Include resin compose schemas in the standalone build
Fixes #844
Change-Type: patch
2018-04-18 13:34:35 +02:00
2e1ab22173 Auto-merge for PR #861 via VersionBot
727 sentry improvements
2018-04-17 14:46:13 +00:00
0a23563d7e v7.3.3 2018-04-17 14:01:51 +00:00
37e4ec6364 Rename expectedError to exitWithExpectedError 2018-04-17 15:18:06 +02:00
6a8b947c2e Don't report lots of user input errors
Change-Type: patch
2018-04-17 15:18:06 +02:00
a16ac37625 Include Sentry breadcrumbs for context in error reports
Change-Type: patch
2018-04-17 15:18:06 +02:00
cf4c7826b2 Update to Sentry 2.x
Change-Type: patch
2018-04-17 15:18:06 +02:00
a0a26f0a1e Auto-merge for PR #862 via VersionBot
Update Dockerode to fix local push issue in standalone builds
2018-04-16 16:21:22 +00:00
a921139a12 v7.3.2 2018-04-16 15:21:33 +00:00
36da7b66c8 Update Dockerode to fix local push issue in standalone builds
Connects-To: #824
Change-Type: patch
2018-04-16 16:43:17 +02:00
3aa87544eb Auto-merge for PR #849 via VersionBot
Update resin-compose-parse to v1.8.1 to fix a problem parsing ports
2018-04-13 19:43:58 +00:00
6121fa505e v7.3.1 2018-04-13 19:38:01 +00:00
a5ba5befd1 Update resin-compose-parse to v1.8.1 to fix a problem parsing ports
Connects-to: https://github.com/resin-io/resin-supervisor/issues/618

Change-Type: patch
Signed-off-by: Pablo Carranza Velez <pablo@resin.io>
2018-04-13 11:17:18 -07:00
b7214a306c Auto-merge for PR #854 via VersionBot
Add 'api-key generate' command
2018-04-12 10:24:39 +00:00
d7616e941a v7.3.0 2018-04-12 10:06:09 +00:00
834a2f1e4d Warn user that api keys will not be shown again in future 2018-04-11 19:31:03 +02:00
0e5f2fe748 Remove now-unused stream-to-promise dependency 2018-04-11 19:30:29 +02:00
e0bcb5e0b9 Always call done() for api key generation, not just if we're successful 2018-04-11 19:27:58 +02:00
59d4890eae Add 'api-key generate' command
Change-Type: minor
2018-04-10 19:21:37 +02:00
51da5360da Auto-merge for PR #852 via VersionBot
Explicitly depend on tar-stream
2018-04-10 14:14:38 +00:00
2655aef28b v7.2.4 2018-04-10 13:49:09 +00:00
45d3a7a124 Explicitly depend on tar-stream
Change-Type: patch
2018-04-10 13:10:25 +02:00
662e4f8940 Merge pull request #853 from resin-io/document-version-rec
Correct documented node version requirement to 6+
2018-04-10 13:10:02 +02:00
c06993cb8e Correct documented node version requirement to 6+
Change-Type: patch
2018-04-09 16:55:36 +02:00
a650f30ce8 Auto-merge for PR #847 via VersionBot
Add a fast build script to package.json
2018-04-06 17:11:31 +00:00
0a924b2dcb v7.2.3 2018-04-06 16:27:32 +00:00
89f62683ce Add a fast build script to package.json
This doesn't run a linter or any documentation generation, aiding in
quick development time.

Change-type: patch
Signed-off-by: Cameron Diver <cameron@resin.io>
2018-04-06 15:40:08 +01:00
143d88f3df Auto-merge for PR #846 via VersionBot
Throw a clear error when logging in with an invalid token
2018-04-04 19:34:56 +00:00
d166a65422 v7.2.2 2018-04-04 18:56:26 +00:00
dd268993b3 Throw a clear error when logging in with an invalid token
Change-Type: patch
2018-04-04 15:43:34 +02:00
13a35b288f Auto-merge for PR #839 via VersionBot
Update docker-qemu-transpose to avoid the broken 0.4.1 release
2018-03-29 14:47:30 +00:00
81e653d31b v7.2.1 2018-03-29 13:52:06 +00:00
875ec8b8bd Update docker-qemu-transpose to avoid the broken 0.4.1 release
Change-Type: patch
2018-03-29 15:28:56 +02:00
989df9b857 Auto-merge for PR #835 via VersionBot
Initial support for api keys in the CLI
2018-03-29 10:15:31 +00:00
0829d3c176 v7.2.0 2018-03-29 10:09:08 +00:00
ce64889b04 Clarify isTokenValid logic 2018-03-29 11:11:25 +02:00
d3a0bfc5f6 Fix auth utils tests to work with new SDK 2018-03-29 11:11:25 +02:00
e965c603d2 Use spec test reporter, so we can debug with output 2018-03-29 11:11:25 +02:00
0e2fb8c96c Promisify auth utils tests 2018-03-29 11:11:25 +02:00
2db1d84d3c Do not require a login for builds
Fixes: #578
Change-Type: patch
2018-03-29 11:11:25 +02:00
12a1916007 Allow (experimental!) login with API keys
Change-Type: minor
2018-03-29 11:11:25 +02:00
b4526e9895 Auto-merge for PR #838 via VersionBot
Fix build emulation for multi-stage builds
2018-03-29 09:03:40 +00:00
a2d867c860 v7.1.6 2018-03-29 08:56:07 +00:00
05b1c37379 Fix build emulation for multi-stage builds
Fixes #814
Change-Type: patch
2018-03-29 10:18:31 +02:00
906cfe9268 Auto-merge for PR #834 via VersionBot
Fix crash when an app is not specified for build command
2018-03-28 12:01:10 +00:00
3c8054faa7 v7.1.5 2018-03-27 17:51:36 +00:00
c6c9046826 Fix crash when an app is not specified for build command
This is a regression introduced in #818

Change-Type: patch
2018-03-27 19:12:31 +03:00
2bbbbf6fdd Auto-merge for PR #832 via VersionBot
Upgrade resin-sync to pull in the fix for #824
2018-03-26 16:31:46 +00:00
9cce4001af v7.1.4 2018-03-26 16:09:22 +00:00
2e944cf2f4 Upgrade resin-sync to pull in the fix for #824
Change-Type: patch
2018-03-26 17:39:47 +02:00
2b0143775c Auto-merge for PR #831 via VersionBot
Prefix all pine options with '$' in preload to avoid pine warnings.
2018-03-23 15:57:54 +00:00
49fec7d8f2 v7.1.3 2018-03-23 15:49:16 +00:00
ca1ac2bb83 Prefix all pine options with '$' in preload to avoid pine warnings.
Change-Type: patch
2018-03-23 15:20:18 +00:00
50b1a7e6b0 Auto-merge for PR #830 via VersionBot
Update resin-preload to 6.2.0 and resin-sdk to 9.0.0-beta16
2018-03-23 13:56:14 +00:00
69ce2c0473 v7.1.2 2018-03-23 13:49:24 +00:00
a3b446dbe7 Update resin-preload to 6.2.0 and resin-sdk to 9.0.0-beta16
Change-Type: patch
2018-03-23 13:41:16 +00:00
1032d9927f Auto-merge for PR #827 via VersionBot
Remove explicit anchor links in CLI docs
2018-03-22 18:03:07 +00:00
12e8a50abc v7.1.1 2018-03-22 17:06:07 +00:00
a4142097f8 Merge branch 'master' into doc-headings 2018-03-22 09:17:32 -05:00
b388ccb6f3 Auto-merge for PR #818 via VersionBot
Restore legacy deployment method
2018-03-22 11:43:59 +00:00
e011502b7e v7.1.0 2018-03-22 11:36:41 +00:00
4f167cb836 Address review feedback 2018-03-22 13:26:47 +02:00
9455d438e2 Formatting fixes 2018-03-22 13:26:47 +02:00
a356ecf9b6 Remove unused code 2018-03-22 13:26:47 +02:00
066ac591ac Warn early if deploying a multicontainer project to an incompatible app
Change-Type: patch
2018-03-22 13:26:47 +02:00
62f006b89a Add legacy deploy method back
This mostly reverts the removal of the legacy deploy code that pushed image tars via the builder. It’s needed for users to avoid having to switch between CLI versions in order to push to legacy apps as well.

Note: this pins resin-sdk to 9.0.0-beta14 as I couldn’t get it to install otherwise — npm would always install 9.0.0-beta9 instead.

Change-Type: minor
2018-03-22 13:26:47 +02:00
ee75ff2753 Remove explicit anchor links in CLI docs
Our docs markdown renderer doesn't process explicit anchor tags, as it generates its own. The script that generates the markdown has been updated to not include these tags and to properly build the TOC links.

Change-type: patch
2018-03-20 13:10:07 -05:00
e4c9defb70 Auto-merge for PR #821 via VersionBot
Update resin-preload to 6.1.2
2018-03-20 15:54:28 +00:00
bb102c1918 v7.0.7 2018-03-20 15:44:13 +00:00
24ebe2946c Update resin-preload to 6.1.2
Connects-To: #820

Change-Type: patch
2018-03-20 15:22:59 +00:00
ba82b1fa27 Auto-merge for PR #815 via VersionBot
Build/deploy commands improvements
2018-03-20 10:43:31 +00:00
e3b145e7b7 v7.0.6 2018-03-20 10:33:01 +00:00
242c3731ee Remove redundant import 2018-03-19 20:52:51 +02:00
5f7eee8eac Make sure image name is all lowercase
Change-Type: patch
2018-03-19 20:52:51 +02:00
1833f6ff0a Improve handling of build log output
This makes sure build logs don’t leak escape sequences and new lines and they don’t break the output. Also improved “inline” logs by normalising the stream before passing it to “transpose build stream”.

Fixes: #808
Change-Type: patch
2018-03-19 20:52:51 +02:00
e5fb954645 Auto-merge for PR #801 via VersionBot
add bash completions
2018-03-15 20:03:33 +00:00
13f76dc020 v7.0.5 2018-03-15 18:51:46 +00:00
b409bdcc73 add blurb about bash completion
Add brief information about tab completions for bash and instructions to enable it.
2018-03-15 18:06:04 +01:00
8c3cb3f585 Add bash completions
This contains bash completion functionality for the resin CLI, including completion for sub-commands.

Change-type: patch
2018-03-15 18:05:50 +01:00
76a8b4df50 Auto-merge for PR #813 via VersionBot
Properly generate consistent working anchors for both our md output & resin docs
2018-03-15 12:09:16 +00:00
a03680311d v7.0.4 2018-03-15 12:01:05 +00:00
6ee36cb5c7 Generate consistent working anchors for both our md output & resin docs
Change-Type: patch
2018-03-15 11:40:29 +01:00
5625326c65 Auto-merge for PR #812 via VersionBot
Fix getting window size when there’s no TTY attached
2018-03-15 08:54:19 +00:00
b912419839 v7.0.3 2018-03-15 08:47:28 +00:00
fe01ead023 Fix getting window size when there’s no TTY attached
Change-Type: patch
2018-03-15 10:30:54 +02:00
229c105d0c Auto-merge for PR #807 via VersionBot
Update full CLI docs with recent installation improvements too
2018-03-13 12:00:31 +00:00
b6e044345f v7.0.2 2018-03-13 10:47:55 +00:00
d9906121e1 Update full CLI docs with recent installation improvements too
Change-Type: patch
2018-03-12 22:17:20 +01:00
3e019f7f34 Remove leftover capitanodoc.coffee file (it's now TS) 2018-03-12 20:06:44 +01:00
eb34cb6f27 Auto-merge for PR #805 via VersionBot
Recommend unsafe-perm to fix some install issues and cleanup dependencies after MC
2018-03-12 16:36:28 +00:00
3a3178bcb9 v7.0.1 2018-03-12 15:36:10 +00:00
cdf6580ecc Recommend using unsafe-prem to avoid permission issues on install
Change-Type: patch
2018-03-12 13:36:24 +01:00
c42bc74f1f Remove unnecessary resin-cli-auth dependency
Change-Type: patch
2018-03-12 11:41:58 +01:00
35fd79f577 Remove (duplicated) runtime ts-node dependency 2018-03-12 11:41:14 +01:00
4ef0682e5a Auto-merge for PR #792 via VersionBot
Multicontainer
2018-03-09 22:12:00 +00:00
d0b7047189 v7.0.0 2018-03-09 22:04:51 +00:00
ae3f936b66 Update resin-preload to v6.0.0 2018-03-09 21:53:34 +00:00
1ef492809b Update resin-preload to v6.0.0-beta11 2018-03-09 20:40:13 +00:00
5bf9dd3a9d Update resin-preload to v6.0.0-beta10 2018-03-09 17:50:20 +00:00
b18a66f66b Update resin-preload to v6.0.0-beta9 2018-03-09 17:02:44 +00:00
1dadfdc699 Fix some formatting to make prettier+resin-lint happy 2018-03-07 16:16:07 +01:00
14a3f51b73 Add docker-compose-aware builds and deployments
Legacy behaviour is mostly retained. The most notable change in behaviour is that invoking `resin deploy` without options is now allowed (see help string how it behaves).

In this commit there are also the following notable changes:

- Deploy/Build are promoted to primary commands
- Extracts QEMU-related code to a new file
- Adds a utility file to retrieve the CLI version and its parts
- Adds a helper that can be used to manipulate display on capable clients
- Declares several new dependencies. Most are already indirectly installed via some dependency

Change-Type: minor
2018-03-07 14:48:05 +00:00
96116aeaec Fix invoking undefined method
Have no idea how this used to work.
2018-03-07 14:47:16 +00:00
7fd31b6a64 Update YAML parser
New version is 3.10.0
2018-03-07 14:47:16 +00:00
299bc0db13 Update docker-toolbelt
New version is 3.1.0.

The updated version is not backwards compatible as it removes all *Async methods that are in wide use in the CLI. The workaround for now is to manually promisify the client and replace all `new Docker()` calls with a shared function that returns a promisified client.
2018-03-07 14:47:15 +00:00
4b9ccae442 Update bundle-resolve and docker-build to latest
This brings in maintainance improvements.

New versions are:

- resin-bundle-resolve: 0.5.1
- resin-docker-build: 0.6.2
2018-03-07 14:46:35 +00:00
079ce552e3 *BREAKING*: Remove support for plugins entirely
There are very few plugins in real-world use, we're not actively working
on this at all, and the current approach won't work once we move to
standalone node-less binary installation anyway.

Change-Type: major
2018-03-07 14:46:35 +00:00
163684e3a9 Update dashboard login to use the multicontainer SDK
Change-Type: patch
2018-03-07 14:46:35 +00:00
f698f561c9 Multicontainer preload: Update resin-preload to 6.0.0-beta4
Change-Type: minor
2018-03-07 14:46:35 +00:00
cb207f18a5 Update the keys action to use the multicontainer SDK
Change-Type: patch
2018-03-07 14:46:34 +00:00
76a5cdc977 Require multicontainer SDK
* require('resin-sdk') => multicontainer SDK
 * require('resin-sdk-preconfigured') => 6.15.0 SDK
 * all 'resin-sdk' requires replaced with 'resin-sdk-preconfigured'
 * resin-sdk-preconfigured TS typings are copy pasted from the current resin-sdk master

The idea is to progressively replace all 'resin-sdk-preconfigured'
requires with 'resin-sdk' (multicontainer sdk) and eventually remove
resin-sdk-preconfigured from package.json.

Change-Type: patch
2018-03-07 14:46:31 +00:00
a82af1d2d1 Auto-merge for PR #802 via VersionBot
Fix CLI prettier configuration to avoid linting errors
2018-03-07 14:46:08 +00:00
ac7d51ad80 v6.13.5 2018-03-07 14:38:49 +00:00
797a739c92 Fix prettier configuration to avoid linting errors
Change-Type: patch
2018-03-05 16:02:09 +01:00
666b59b463 Auto-merge for PR #796 via VersionBot
Fix issue where emulated builds broke Docker `ENV` commands
2018-02-22 18:30:14 +00:00
a83d9a070c v6.13.4 2018-02-22 18:23:29 +00:00
7637377471 Fix issue where emulated builds broke Docker ENV commands
Connects-to: #795
Change-type: patch
2018-02-22 18:12:17 +00:00
6515f88d92 Auto-merge for PR #793 via VersionBot
Tweak TS & add missing deps that may cause build failures in some envs
2018-02-20 22:07:21 +00:00
92534b9c82 v6.13.3 2018-02-20 21:30:39 +00:00
c12360daa8 Tweak TS & add missing deps that may cause build failures in some envs
Connects-To: #765
Change-Type: patch
2018-02-20 20:26:18 +01:00
3d28118f3e Auto-merge for PR #794 via VersionBot
Ensure login does not wait for the browser process to close
2018-02-20 19:00:32 +00:00
04adfde064 v6.13.2 2018-02-20 17:12:45 +00:00
d8aabfd448 Ensure login does not wait for the browser process to close
Unclear why, but for some reason this only actually blocked on the
browser on OSX.

Connects-To: #791
Change-Type: patch
2018-02-16 17:28:19 +01:00
cf95870d9d Auto-merge for PR #786 via VersionBot
Assorted tiny fixes
2018-02-07 12:07:01 +00:00
55f8876bcc v6.13.1 2018-02-07 11:59:58 +00:00
9fb66186f0 Move to the correct coffeescript (no hyphen) dependency
Change-Type: patch
2018-02-07 11:20:49 +01:00
da8fe99ca4 Add typings for 'ent'
Change-Type: patch
2018-02-07 11:20:08 +01:00
20374fde36 Auto-merge for PR #777 via VersionBot
Add support for Balena in local ssh
2018-02-06 11:40:40 +00:00
5131f722a7 v6.13.0 2018-02-06 11:34:29 +00:00
1ef0a1028f Add support for Balena in local ssh
Change-Type: minor
2018-02-06 12:05:28 +01:00
0fd1f04eda Auto-merge for PR #781 via VersionBot
Switch back to upstream global-tunnel-ng
2018-02-05 19:07:08 +00:00
5c0ba5d06c v6.12.9 2018-02-05 19:00:36 +00:00
d9532b6fa0 Switch back to upstream global-tunnel-ng
Connects-To: #780

Change-Type: patch
2018-02-05 15:55:26 +00:00
b96065514f Auto-merge for PR #774 via VersionBot
Fix uuid params being parsed a numbers
2018-02-03 15:58:03 +00:00
0e9b8e4140 v6.12.8 2018-02-03 15:50:09 +00:00
d1c773360f Fix uuid params being parsed a numbers
Connects-To: #489
Change-Type: patch
2018-02-01 17:48:01 +02:00
74538bba8d Auto-merge for PR #767 via VersionBot
Add 'or mounted resinOS image'
2018-01-30 18:10:12 +00:00
64c95e3811 v6.12.7 2018-01-30 17:29:59 +00:00
33fd70291a Add 'or mounted resinOS image'
Connects-To: #764

Change-Type: patch
2018-01-30 17:30:18 +01:00
0cb4bc951a Auto-merge for PR #759 via VersionBot
Don't use the deprecated 'os configure' format in internal calls
2018-01-29 14:56:00 +00:00
3761ab9610 v6.12.6 2018-01-29 14:44:44 +00:00
8c29bba108 Don't use the deprecated 'os configure' format in internal calls
Change-Type: patch
2018-01-16 17:54:46 +01:00
4e41261237 Auto-merge for PR #754 via VersionBot
Fix breakage in deploy command
2018-01-11 11:32:17 +00:00
77529ef3b1 v6.12.5 2018-01-11 10:16:56 +00:00
0ba96adbbc Fix breakage in deploy command from recent TS conversion
Change-Type: patch
2018-01-11 10:33:08 +01:00
7df277c0bc Auto-merge for PR #753 via VersionBot
Add prettier
2018-01-10 10:18:18 +00:00
c94f7b10bd v6.12.4 2018-01-10 09:48:26 +00:00
83a76f7d6f Start using Prettier
Change-Type: patch
2018-01-10 09:23:00 +01:00
6c988241eb Move capitanodoc into the automation folder 2018-01-10 08:41:19 +01:00
29145dfc2d Auto-merge for PR #743 via VersionBot
Start seriously converting the CLI to TypeScript
2018-01-09 22:37:41 +00:00
4b74e8ec70 v6.12.3 2018-01-09 21:00:55 +00:00
612012aff8 Lots of small TypeScript tweaks & clarifications from review 2018-01-09 17:14:49 +01:00
6ab60d0ccd Avoid awkward multiline strings in doc generation code 2018-01-09 17:14:49 +01:00
6daed83d88 Lint TypeScript and CoffeeScript with resin-lint
Change-Type: patch
2018-01-09 17:14:49 +01:00
f25442c036 Move documentation generation to TypeScript
Change-Type: patch
2018-01-09 17:14:49 +01:00
ffffd447f2 Convert most of utils to TypeScript
Change-Type: patch
2018-01-09 17:14:48 +01:00
4b511c47f0 Start on some easy TS conversion 2018-01-09 17:14:48 +01:00
158d471a98 Auto-merge for PR #751 via VersionBot
Convert windows paths to posix when passing to tar
2018-01-09 15:33:28 +00:00
107a90395c v6.12.2 2018-01-09 14:03:45 +00:00
ce5fd53822 convert windows paths to posix when passing to tar
Due to https://github.com/mafintosh/tar-stream/issues/3, the tar module
needs posix style paths but system-specific paths are being supplied

Change-Type: patch
2018-01-08 22:50:11 +00:00
810ca78215 Auto-merge for PR #744 via VersionBot
Fix deprecation warning for os configure, when passing a bare UUID
2018-01-02 10:58:30 +00:00
eb945b3315 v6.12.1 2018-01-02 10:49:06 +00:00
34f24fe331 Fix deprecation warning for os configure, when passing a bare UUID
Change-Type: patch
2017-12-22 16:07:19 +01:00
743392017d Auto-merge for PR #737 via VersionBot
Add ssh option for direct host OS access
2017-12-19 08:45:56 +00:00
15b877f005 v6.12.0 2017-12-19 08:37:19 +00:00
0653769156 fixed example 2017-12-18 17:03:16 +01:00
3ed319872a refactored 2017-12-18 17:03:16 +01:00
ee124671d8 mention Resin OS version requirement 2017-12-18 17:03:16 +01:00
1b4dabd37c Add ssh option for direct host OS access
Use the `--host` (short `-H`) option in the ssh command to access
the host OS of the device.
Direct host OS is enabled for devices with Resin OS >= 2.7.5.

Change-Type: minor
Connects-To: #736
Signed-off-by: Andreas Fitzek <andreas@resin.io>
2017-12-18 17:03:16 +01:00
fdd253f042 Auto-merge for PR #729 via VersionBot
Standalone binary for Resin-CLI
2017-12-18 15:06:13 +00:00
1a15fdd2f0 v6.11.0 2017-12-18 14:48:09 +00:00
2c66280b3f Build standalone zips into a separate folder 2017-12-18 15:03:17 +01:00
778c39d947 Ensure MDNS service definitions are included in standalone binaries 2017-12-18 14:55:07 +01:00
fa15addfb2 Add standalone install instructions to the readme 2017-12-18 14:55:07 +01:00
afbb9474b7 Use proper strict settings for automation TS 2017-12-18 14:55:07 +01:00
0acb4f8cb1 Fix docs generation when building on windows
Change-Type: patch
2017-12-18 14:55:07 +01:00
08de0938a0 Autodeploy built standalone binaries for all platforms to github
Change-Type: minor
2017-12-18 14:55:07 +01:00
2c9b80c177 Add manual script to deploy built CLI binaries to GitHub 2017-12-18 14:55:07 +01:00
e8c19df8c9 Set up a script to automate builds, and support native extensions 2017-12-18 14:55:07 +01:00
7681003512 Package the CLI into a standalone runnable binary
This has no native modules yet, which means it works on Linux,
but ignoring any ext4 image data. Drivelist will fail for
some windows operations, but most other things should work.

This is only building a folder with a runnable binary, this needs
packaging before it can be distributable.

Change-Type: minor
2017-12-18 14:55:07 +01:00
dba8db19cb Move from open to opn
Change-Type: patch
2017-12-18 14:55:07 +01:00
d199cdf088 Auto-merge for PR #730 via VersionBot
Ensure logout works even with invalid credentials, or if not logged in
2017-12-15 17:52:38 +00:00
f2840c5ca4 v6.10.3 2017-12-15 17:48:25 +00:00
1c7a0ba4e1 Ensure logout works even with invalid credentials, or if not logged in
Before this point, if you had an invalid token, an expired token, or a
token for a different site, you couldn't log out to clear it properly.

Not a big deal, but awkward and messy, and easily fixed.

Change-Type: patch
2017-12-14 15:34:36 +01:00
198 changed files with 43131 additions and 4437 deletions

1
.github/CODEOWNERS vendored Normal file
View File

@ -0,0 +1 @@
* @CameronDiver @hedss @pdcastro @srlowe @thgreasi

View File

@ -1,2 +1,16 @@
- **resin-cli version:**
- **Operating system and architecture:**
- **balena CLI version:** e.g. 11.2.1 (output of the `"balena version"` command)
- **Operating system version:** e.g. Windows 10, Ubuntu 18.04, macOS 10.14.5
- **32/64 bit OS and processor:** e.g. 32-bit Windows on 64-bit Intel processor
- **Install method:** npm or zip or executable installer
- **If npm install, Node.js and npm version:** e.g. Node v8.16.0 and npm v6.4.1
---
*Please keep in mind that we try to use the issue tracker of this repository for specific bug
reports & CLI feature requests. General & troubleshooting questions are encouraged to be posted to
the [balena forums](https://forums.balena.io), which are monitored by balena's support team and
where the community can both contribute and benefit from the answers.*
*Before submitting this issue please check that this issue is not a duplicate. If there is another
issue describing the same problem or feature please add your information to the existing issue's
comments.*

11
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,11 @@
<!-- You can remove tags that do not apply. -->
Resolves: # <!-- Refer an issue of this repository that this PR fixes -->
See: <url> <!-- Refer to any external resource, like a PR, document or discussion -->
Depends-on: <url> <!-- This change depends on a PR to get merged/deployed first -->
Change-type: major|minor|patch <!-- The change type of this PR -->
---
##### Contributor checklist
<!-- For completed items, change [ ] to [x]. -->
- [ ] Introduces security considerations
- [ ] Affects the development, build or deployment processes of the component

17
.gitignore vendored
View File

@ -10,8 +10,9 @@ pids
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
# Coverage directory used by tools like istanbul/nyc
coverage
.nyc_output
# node-waf configuration
.lock-wscript
@ -24,14 +25,22 @@ build/Release
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
node_modules
npm-shrinkwrap.json
package-lock.json
.resinconf
.balenaconf
resinrc.yml
balenarc.yml
.idea
.vscode
.DS_Store
.idea
.nvmrc
.vscode
/tmp
build/
build-bin/
build-zip/
dist/
# Ignore fast-boot cache file
**/.fast-boot.json

5
.prettierrc Normal file
View File

@ -0,0 +1,5 @@
{
"singleQuote": true,
"trailingComma": "all",
"useTabs": true
}

36
.resinci.yml Normal file
View File

@ -0,0 +1,36 @@
---
npm:
platforms:
- name: linux
os: alpine
architecture: x86_64
node_versions:
- "8"
- "10"
- name: linux
os: alpine
architecture: x86
node_versions:
- "8"
- "10"
- name: darwin
os: macos
architecture: x86_64
node_versions:
- "8"
- "10"
- name: windows
os: windows
architecture: x86_64
node_versions:
- "8"
- "10"
- name: windows
os: windows
architecture: x86
node_versions:
- "8"
- "10"
docker:
publish: false

View File

@ -1,22 +1,25 @@
language: node_js
os:
- linux
- osx
node_js:
- "10"
matrix:
include:
- node_js:
- '6'
env:
- CAN_DEPLOY=true
before_install:
- npm -g install npm@4
script: npm run ci
exclude:
node_js: "10"
script:
- node --version
- npm --version
- npm run ci
# - npm run build:standalone
# - npm run build:installer
notifications:
email: false
deploy:
provider: npm
email: accounts@resin.io
api_key:
secure: phet6Du13hc1bzStbmpwy2ODNL5BFwjAmnpJ5wMcbWfI7fl0OtQ61s2+vW5hJAvm9fiRLOfiGAEiqOOtoupShZ1X8BNkC708d8+V+iZMoFh3+j6wAEz+N1sVq471PywlOuLAscOcqQNp92giCVt+4VPx2WQYh06nLsunvysGmUM=
skip_cleanup: true
on:
tags: true
condition: "$CAN_DEPLOY = 'true' && $TRAVIS_TAG =~ ^v?[[:digit:]]+\\.[[:digit:]]+\\.[[:digit:]]+"
repo: resin-io/resin-cli
- provider: script
script: npm run release
skip_cleanup: true
on:
tags: true
condition: "$TRAVIS_TAG =~ ^v?[[:digit:]]+\\.[[:digit:]]+\\.[[:digit:]]+"
repo: balena-io/balena-cli

5497
.versionbot/CHANGELOG.yml Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

65
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,65 @@
# Contributing
The balena CLI is an open source project and your contribution is welcome!
After cloning this repository and running `npm install`, the CLI can be built with `npm run build`
and executed with `./bin/balena`. In order to ease development:
* `npm run build:fast` skips some of the build steps for interactive testing, or
* `./bin/balena-dev` uses `ts-node/register` and `coffeescript/register` to transpile on the fly.
Before opening a PR, please be sure to test your changes with `npm test`.
## Semantic versioning and commit messages
The CLI version numbering adheres to [Semantic Versioning](http://semver.org/). The following
header/row is required in the body of a commit message, and will cause the CI build to fail if absent:
```
Change-type: patch|minor|major
```
Version numbers and commit messages are automatically added to the `CHANGELOG.md` file by the CI
build flow, after a pull request is merged. It should not be manually edited.
## Editing documentation files (CHANGELOG, README, website...)
The `doc/cli.markdown` file is automatically generated by running `npm run build:doc` (which also
runs as part of `npm run build`). That file is then pulled by scripts in the
[balena-io/docs](https://github.com/balena-io/docs/) GitHub repo for publishing at the [CLI
Documentation page](https://www.balena.io/docs/reference/cli/).
The content sources for the auto generation of `doc/cli.markdown` are:
* Selected sections of the README file.
* The CLI's command documentation in source code (both Capitano and oclif commands), for example:
* `lib/actions/build.coffee`
* `lib/actions-oclif/env/add.ts`
The README file is manually edited, but subsections are automatically extracted for inclusion in
`doc/cli.markdown` by the `getCapitanoDoc()` function in
[`automation/capitanodoc/capitanodoc.ts`](https://github.com/balena-io/balena-cli/blob/master/automation/capitanodoc/capitanodoc.ts).
The `INSTALL.md` and `TROUBLESHOOTING.md` files are also manually edited.
## Windows
Please note that `npm run build:installer` (which generates the `.exe` executable installer on
Windows) requires [MSYS2](https://www.msys2.org/) to be installed. Other than that, the standard
Command Prompt or PowerShell can be used.
## TypeScript vs CoffeeScript, and Capitano vs oclif
The CLI was originally written in [CoffeeScript](https://coffeescript.org), but we decided to
migrate to [TypeScript](https://www.typescriptlang.org/) in order to take advantage of static
typing and formal programming interfaces. The migration is taking place gradually, as part of
maintenance work or the implementation of new features.
Similarly, [Capitano](https://github.com/balena-io/capitano) was originally adopted as the CLI's
framework, but we recently decided to take advantage of [oclif](https://oclif.io/)'s features such
as native installers for Windows, macOS and Linux, and support for custom flag parsing (for
example, we're still battling with Capitano's behavior of dropping leading zeros of arguments that
look like integers such as some abbreviated UUIDs, and migrating to oclif is a solution). Again the
migration is taking place gradually, with some CLI commands parsed by oclif and others by Capitano
(a simple command line pre-parsing takes place in `app.ts` to decide whether to route full parsing
to Capitano or oclif).

204
INSTALL.md Normal file
View File

@ -0,0 +1,204 @@
# balena CLI Installation Instructions
There are 3 options to choose from to install balena's CLI:
* [Executable Installer](#executable-installer): the easiest method, using the traditional
graphical desktop application installers for Windows and macOS (coming soon for Linux users too).
* [Standalone Zip Package](#standalone-zip-package): these are plain zip files with the balena CLI
executable in them. Recommended for scripted installation in CI (continuous integration)
environments.
* [NPM Installation](#npm-installation): recommended for developers who may be interested in
integrating the balena CLI in their existing Node.js projects or workflow.
Some specific CLI commands have a few extra installation steps: see section [Additional
Dependencies](#additional-dependencies).
> **Windows users:**
> * There is a [YouTube video tutorial](https://www.youtube.com/watch?v=2LApclXFqsg) for installing
> and getting started with the balena CLI on Windows. (The video uses the standalone zip package
> option.)
> * If you are using Microsoft's [Windows Subsystem for
> Linux](https://docs.microsoft.com/en-us/windows/wsl/about) (WSL), the recommendation is to
> install a balena CLI release for Linux rather than Windows, like the Linux standalone zip
> package. An installation with the graphical executable installer for Windows will not run on
> WSL.
## Executable Installer
1. Download the latest installer from the [releases page](https://github.com/balena-io/balena-cli/releases).
Look for a file name that ends with "-installer", for example:
`balena-cli-v11.6.0-windows-x64-installer.exe`
`balena-cli-v11.6.0-macOS-x64-installer.pkg`
2. Double click the downloaded file to run the installer.
_If you are using macOS Catalina (10.15), [check this known issue and
workaround](https://github.com/balena-io/balena-cli/issues/1479)._
3. After the installation completes, close and re-open any open [command
terminal](https://www.balena.io/docs/reference/cli/#choosing-a-shell-command-promptterminal)
windows so that the changes made by the installer to the PATH environment variable can take
effect. Check that the installation was successful by running the following commands on a
command terminal:
* `balena version` - should print the installed CLI version
* `balena help` - should print the balena CLI help
> Note: If you had previously installed the CLI using a standalone zip package, it may be a good
> idea to check your system's `PATH` environment variable for duplicate entries, as the terminal
> will use the entry that comes first. Check the [Standalone Zip Package](#standalone-zip-package)
> instructions for how to modify the PATH variable.
By default, the CLI is installed to the following folders:
OS | Folders
--- | ---
Windows: | `C:\Program Files\balena-cli\`
macOS: | `/usr/local/lib/balena-cli/` <br> `/usr/local/bin/balena`
## Standalone Zip Package
1. Download the latest zip file from the [releases page](https://github.com/balena-io/balena-cli/releases).
Look for a file name that ends with the word "standalone", for example:
`balena-cli-v10.13.6-linux-x64-standalone.zip`
`balena-cli-v10.13.6-macOS-x64-standalone.zip`
`balena-cli-v10.13.6-windows-x64-standalone.zip`
2. Extract the zip file contents to any folder you choose. The extracted contents will include a
`balena-cli` folder.
3. Add the `balena-cli` folder to the system's `PATH` environment variable.
See instructions for:
[Linux](https://stackoverflow.com/questions/14637979/how-to-permanently-set-path-on-linux-unix) |
[macOS](https://www.architectryan.com/2012/10/02/add-to-the-path-on-mac-os-x-mountain-lion/#.Uydjga1dXDg) |
[Windows](https://www.computerhope.com/issues/ch000549.htm)
> _If you are using macOS Catalina (10.15), [check this known issue and
> workaround](https://github.com/balena-io/balena-cli/issues/1479)._
To update the CLI to a new version, download a new release zip file and replace the previous
installation folder. To uninstall, simply delete the folder and edit the PATH environment variable
as described above.
## NPM Installation
If you are a Node.js developer, you may wish to install the balena CLI via [npm](https://www.npmjs.com).
The npm installation involves building native (platform-specific) binary modules, which require
some additional development tools to be installed first:
* [Node.js](https://nodejs.org/) version 8, 10 or 12
* **Linux, macOS** and **Windows Subsystem for Linux (WSL):**
Installing Node via [nvm](https://github.com/nvm-sh/nvm/blob/master/README.md) is recommended.
With some Linux distributions like Ubuntu, users sometimes report permission errors when using
the system's Node installation (i.e. when Node is installed via `apt-get`), hence the
[nvm](https://github.com/creationix/nvm) recommendation. This [sample
Dockerfile](https://gist.github.com/pdcastro/5d4d96652181e7da685a32caf629dd44) shows the CLI
installation steps on an Ubuntu 18.04 base image.
* If using **Node v8,** upgrade `npm` to version 6.9.0 or later with `"npm install -g npm"`
* [Python 2.7](https://www.python.org/), [git](https://git-scm.com/), [make](https://www.gnu.org/software/make/), [g++](https://gcc.gnu.org/)
* **Linux** and **Windows Subsystem for Linux (WSL):**
`sudo apt-get install -y python git make g++`
* **macOS:** install Apple's Command Line Tools by running on a Terminal window:
`xcode-select --install`
On **Windows (not WSL),** the dependencies above and additional ones can be met by installing:
* Node.js from the [Nodejs.org download page](https://nodejs.org/en/download/).
* The [MSYS2 shell](https://www.msys2.org/), which provides `git`, `make`, `g++`, `ssh`, `rsync`
and more:
* `pacman -S git openssh rsync gcc make`
* [Set a Windows environment variable](https://www.onmsft.com/how-to/how-to-set-an-environment-variable-in-windows-10): `MSYS2_PATH_TYPE=inherit`
* Note that a bug in the MSYS2 launch script (`msys2_shell.cmd`) makes text-based
interactive CLI menus to misbehave. [Check this Github issue for a
workaround](https://github.com/msys2/MINGW-packages/issues/1633#issuecomment-240583890).
* The Windows Driver Kit (WDK), which is needed to compile some native Node modules. It is **not**
necessary to install Visual Studio, only the WDK, which is "step 2" in the following guides:
* [WDK for Windows 10](https://docs.microsoft.com/en-us/windows-hardware/drivers/download-the-wdk#download-icon-step-2-install-wdk-for-windows-10-version-1903)
* [WDK for earlier versions of Windows](https://docs.microsoft.com/en-us/windows-hardware/drivers/other-wdk-downloads#step-2-install-the-wdk)
* The [windows-build-tools](https://www.npmjs.com/package/windows-build-tools) npm package (which
provides Python 2.7 and more), by running the following command on an [administrator
console](https://www.howtogeek.com/194041/how-to-open-the-command-prompt-as-administrator-in-windows-8.1/):
`npm install -g --production windows-build-tools`
With these dependencies in place, the balena CLI installation command is:
```sh
$ npm install balena-cli -g --production --unsafe-perm
```
`--unsafe-perm` is only required on systems where the global install directory is not user-writable.
This allows npm install steps to download and save prebuilt native binaries. You may be able to omit it,
especially if you're using a user-managed node install such as [nvm](https://github.com/creationix/nvm).
## Additional Dependencies
* The `balena ssh` command requires a recent version of the `ssh` command-line tool to be available:
* macOS and Linux usually already have it installed. Otherwise, search for the available packages
on your specific Linux distribution, or for the Mac consider the [Xcode command-line
tools](https://developer.apple.com/xcode/features/) or [homebrew](https://brew.sh/).
* Microsoft started distributing an SSH client with Windows 10, which we understand is
automatically installed through Windows Update, but can be manually installed too
([more information](https://docs.microsoft.com/en-us/windows-server/administration/openssh/openssh_install_firstuse)).
For other versions of Windows, there are several ssh/OpenSSH clients provided by 3rd parties.
* If you need SSH to work behind a proxy, you will also need to install
[`proxytunnel`](http://proxytunnel.sourceforge.net/) (available as a `proxytunnel` package
for Ubuntu, for example).
Check the [README](https://github.com/balena-io/balena-cli/blob/master/README.md) file
for proxy configuration instructions.
* The `balena preload`, `balena build` and `balena deploy --build` commands require
[Docker](https://docs.docker.com/install/overview/) or [balenaEngine](https://www.balena.io/engine/)
to be available:
* The `balena preload` command requires the Docker Engine to support the [AUFS storage
driver](https://docs.docker.com/storage/storagedriver/aufs-driver/). Docker Desktop for Mac and
Windows dropped support for the AUFS filesystem in Docker CE versions greater than 18.06.1, so
the workaround is to downgrade to version 18.06.1 (links: [Docker CE for
Windows](https://docs.docker.com/docker-for-windows/release-notes/#docker-community-edition-18061-ce-win73-2018-08-29)
and [Docker CE for
Mac](https://docs.docker.com/docker-for-mac/release-notes/#docker-community-edition-18061-ce-mac73-2018-08-29)).
See more details in [CLI issue 1099](https://github.com/balena-io/balena-cli/issues/1099).
* Commonly, Docker is installed on the same machine where the CLI is being used, but the
`balena build` and `balena deploy` commands can also use a remote Docker Engine (daemon)
or balenaEngine (which could be a remote device running a [balenaOS development
image](https://www.balena.io/docs/reference/OS/overview/2.x/#dev-vs-prod-images)) by specifying
its IP address and port number as command-line options. Check the documentation for each
command, e.g. `balena help build`, or the [online
reference](https://www.balena.io/docs/reference/cli/#cli-command-reference).
* If you are using Microsoft's [Windows Subsystem for
Linux](https://docs.microsoft.com/en-us/windows/wsl/about) (WSL) and Docker Desktop for
Windows, check the [FAQ item "Docker seems to be
unavailable"](https://github.com/balena-io/balena-cli/blob/master/TROUBLESHOOTING.md#docker-seems-to-be-unavailable-error-when-using-windows-subsystem-for-linux-wsl).
## Configuring SSH keys
The `balena ssh` command requires an SSH key to be added to your balena account. If you had
already added a SSH key in order to [deploy with 'git push'](https://www.balena.io/docs/learn/getting-started/raspberrypi3/nodejs/#adding-an-ssh-key),
then you are probably done and may skip this section. You can check whether you already have
an SSH key in your balena account with the `balena keys` command, or by visiting the
[balena web dashboard](https://dashboard.balena-cloud.com/), clicking on your name -> Preferences
-> SSH Keys.
> Note: An "SSH key" actually consists of a public/private key pair. A typical name for the private
> key file is "id_rsa", and a typical name for the public key file is "id_rsa.pub". Both key files
> are saved to your computer (with the private key optionally protected by a password), but only
> the public key is saved to your balena account. This means that if you change computers or
> otherwise lose the private key, _you cannot recover the private key through your balena account._
> You can however add new keys, and delete the old ones.
If you don't have an SSH key in your balena account:
* If you have an existing SSH key in your computer that you would like to use, you can add it
to your balena account through the balena web dashboard (Preferences -> SSH Keys), or through
the CLI itself:
```bash
# Windows 10 (cmd.exe prompt) example:
$ balena key add MyKey %userprofile%\.ssh\id_rsa.pub
# Linux / macOS example:
$ balena key add MyKey ~/.ssh/id_rsa.pub
```
* To generate a new key, you can follow [GitHub's documentation](https://help.github.com/en/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent),
skipping the step about adding the key to your GitHub account, and instead adding the key to
your balena account as described above.

141
README.md
View File

@ -1,98 +1,99 @@
Resin CLI
=========
# balena CLI
> The official resin.io CLI tool.
The official balena CLI tool.
[![npm version](https://badge.fury.io/js/resin-cli.svg)](http://badge.fury.io/js/resin-cli)
[![dependencies](https://david-dm.org/resin-io/resin-cli.svg)](https://david-dm.org/resin-io/resin-cli)
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/resin-io/chat)
[![npm version](https://badge.fury.io/js/balena-cli.svg)](http://badge.fury.io/js/balena-cli)
[![dependencies](https://david-dm.org/balena-io/balena-cli.svg)](https://david-dm.org/balena-io/balena-cli)
Requisites
----------
## About
- [NodeJS](https://nodejs.org) (>= v4)
- [Git](https://git-scm.com)
- The following executables should be correctly installed in your shell environment:
- `ssh`: Any recent version of the OpenSSH ssh client (required by `resin sync` and `resin ssh`)
- if you need `ssh` to work behind the proxy you also need [`proxytunnel`](http://proxytunnel.sourceforge.net/) installed (available as `proxytunnel` package for Ubuntu, for example)
- `rsync`: >= 2.6.9 (required by `resin sync`)
The balena CLI (Command-Line Interface) allows you to interact with the balenaCloud and the
[balena API](https://www.balena.io/docs/reference/api/overview/) through a terminal window
on Linux, macOS or Windows. You can also write shell scripts around it, or import its Node.js
modules to use it programmatically.
As an [open-source project on GitHub](https://github.com/balena-io/balena-cli/), your contribution
is also welcome!
##### Windows Support
## Installation
Before installing resin-cli, you'll need a working node-gyp environment. If you don't already have one you'll see native module build errors during installation. To fix this, run `npm install -g --production windows-build-tools` in an administrator console (available as 'Command Prompt (Admin)' when pressing windows+x in Windows 7+).
Check the [balena CLI installation instructions on GitHub](https://github.com/balena-io/balena-cli/blob/master/INSTALL.md).
`resin sync` and `resin ssh` have not been thoroughly tested on the standard Windows cmd.exe shell. We recommend using bash (or a similar) shell, like Bash for Windows 10 or [Git for Windows](https://git-for-windows.github.io/).
## Getting Started
If you still want to use `cmd.exe` you will have to use a package manager like MinGW or chocolatey. For MinGW the steps are:
### Choosing a shell (command prompt/terminal)
1. Install [MinGW](http://www.mingw.org).
2. Install the `msys-rsync` and `msys-openssh` packages.
3. Add MinGW to the `%PATH%` if this hasn't been done by the installer already. The location where the binaries are places is usually `C:\MinGW\msys\1.0\bin`, but it can vary if you selected a different location in the installer.
4. Copy your SSH keys to `%homedrive%%homepath\.ssh`.
5. If you need `ssh` to work behind the proxy you also need to install [proxytunnel](http://proxytunnel.sourceforge.net/)
On **Windows,** the standard Command Prompt (`cmd.exe`) and
[PowerShell](https://docs.microsoft.com/en-us/powershell/scripting/getting-started/getting-started-with-windows-powershell?view=powershell-6)
are supported. We are aware of users also having a good experience with alternative shells,
including:
Getting Started
---------------
* [MSYS2](https://www.msys2.org/):
* Install additional packages with the command:
`pacman -S git openssh rsync`
* [Set a Windows environment variable](https://www.onmsft.com/how-to/how-to-set-an-environment-variable-in-windows-10): `MSYS2_PATH_TYPE=inherit`
* Note that a bug in the MSYS2 launch script (`msys2_shell.cmd`) makes text-based interactive CLI
menus to break. [Check this Github issue for a
workaround](https://github.com/msys2/MINGW-packages/issues/1633#issuecomment-240583890).
* [MSYS](http://www.mingw.org/wiki/MSYS): select the `msys-rsync` and `msys-openssh` packages too
* [Git for Windows](https://git-for-windows.github.io/)
* Microsoft's [Windows Subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/about)
(WSL). In this case, a Linux distribution like Ubuntu is installed via the Microsoft Store, and a
balena CLI release **for Linux** is recommended. See
[FAQ](https://github.com/balena-io/balena-cli/blob/master/TROUBLESHOOTING.md) for using balena
CLI with WSL and Docker Desktop for Windows.
### Install
On **macOS** and **Linux,** the standard terminal window is supported. _Optionally,_ `bash` command
auto completion may be enabled by copying the
[balena-completion.bash](https://github.com/balena-io/balena-cli/blob/master/balena-completion.bash)
file to your system's `bash_completion` directory: check [Docker's command completion
guide](https://docs.docker.com/compose/completion/) for system setup instructions.
This might require elevated privileges in some environments.
### Logging in
Several CLI commands require access to your balenaCloud account, for example in order to push a
new release to your application. Those commands require creating a CLI login session by running:
```sh
$ npm install --global --production resin-cli
$ balena login
```
### Login
### Proxy support
```sh
$ resin login
```
HTTP(S) proxies can be configured through any of the following methods, in order of preference:
_(Typically useful, but not strictly required for all commands)_
* Set the `BALENARC_PROXY` environment variable in URL format (with protocol, host, port, and
optionally basic auth).
* Alternatively, use the [balena config file](https://www.npmjs.com/package/balena-settings-client#documentation)
(project-specific or user-level) and set the `proxy` setting. It can be:
* A string in URL format, or
* An object in the [global-tunnel-ng options format](https://www.npmjs.com/package/global-tunnel-ng#options) (which allows more control).
* Alternatively, set the conventional `https_proxy` / `HTTPS_PROXY` / `http_proxy` / `HTTP_PROXY`
environment variable (in the same standard URL format).
### Run commands
To get a proxy to work with the `balena ssh` command, check the
[installation instructions](https://github.com/balena-io/balena-cli/blob/master/INSTALL.md).
Take a look at the full command documentation at [https://docs.resin.io/tools/cli/](https://docs.resin.io/tools/cli/#table-of-contents
), or by running `resin help`.
## Command reference documentation
---
The full CLI command reference is available [on the web](https://www.balena.io/docs/reference/cli/
) or by running `balena help` and `balena help --verbose`.
Plugins
-------
## Support, FAQ and troubleshooting
The Resin CLI can be extended with plugins to automate laborious tasks and overall provide a better experience when working with Resin.io. Check the [plugin development tutorial](https://github.com/resin-io/resin-plugin-hello) to learn how to build your own!
If you come across any problems or would like to get in touch:
FAQ
---
* 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).
* For bug reports or feature requests,
[have a look at the GitHub issues or create a new one](https://github.com/balena-io/balena-cli/issues/).
### Where is my configuration file?
## Contributing (including editing documentation files)
The per-user configuration file lives in `$HOME/.resinrc.yml` or `%UserProfile%\_resinrc.yml`, in Unix based operating systems and Windows respectively.
Please have a look at the [CONTRIBUTING.md](./CONTRIBUTING.md) file for some guidance before
submitting a pull request or updating documentation (because some files are automatically
generated). Thank you for your help and interest!
The Resin CLI also attempts to read a `resinrc.yml` file in the current directory, which takes precedence over the per-user configuration file.
## License
### How do I point the Resin CLI to staging?
The easiest way is to set the `RESINRC_RESIN_URL=resinstaging.io` environment variable.
Alternatively, you can edit your configuration file and set `resinUrl: resinstaging.io` to persist this setting.
### How do I make the Resin CLI persist data in another directory?
The Resin CLI persists your session token, as well as cached images in `$HOME/.resin` or `%UserProfile%\_resin`.
Pointing the Resin CLI to persist data in another location is necessary in certain environments, like a server, where there is no home directory, or a device running resinOS, which erases all data after a restart.
You can accomplish this by setting `RESINRC_DATA_DIRECTORY=/opt/resin` or adding `dataDirectory: /opt/resin` to your configuration file, replacing `/opt/resin` with your desired directory.
Support
-------
If you're having any problems, check our [troubleshooting guide](https://github.com/resin-io/resin-cli/blob/master/TROUBLESHOOTING.md) and if your problem is not addressed there, please [raise an issue](https://github.com/resin-io/resin-cli/issues/new) on GitHub and the resin.io team will be happy to help.
You can also get in touch with us in the resin.io [forums](https://forums.resin.io/).
License
-------
The project is licensed under the Apache 2.0 license.
The project is licensed under the [Apache 2.0 License](https://www.apache.org/licenses/LICENSE-2.0).
A copy is also available in the LICENSE file in this repository.

View File

@ -1,15 +1,41 @@
Troubleshooting
===============
# FAQ & Troubleshooting
This document contains common issues related to the Resin CLI, and how to fix them.
This document contains some common issues, questions and answers related to the balena CLI.
### After burning to an sdcard, my device doesn't boot
## Where is my configuration file?
The per-user configuration file lives in `$HOME/.balenarc.yml` or `%UserProfile%\_balenarc.yml`, in
Unix based operating systems and Windows respectively.
The balena CLI also attempts to read a `balenarc.yml` file in the current directory, which takes
precedence over the per-user configuration file.
## How do I point the balena CLI to staging?
The easiest way is to set the `BALENARC_BALENA_URL=balena-staging.com` environment variable.
Alternatively, you can edit your configuration file and set `balenaUrl: balena-staging.com` to
persist this setting.
## How do I make the balena CLI persist data in another directory?
The balena CLI persists your session token, as well as cached images in `$HOME/.balena` or
`%UserProfile%\_balena`.
Pointing the balena CLI to persist data in another location is necessary in certain environments,
like a server, where there is no home directory, or a device running balenaOS, which erases all
data after a restart.
You can accomplish this by setting `BALENARC_DATA_DIRECTORY=/opt/balena` or adding `dataDirectory:
/opt/balena` to your configuration file, replacing `/opt/balena` with your desired directory.
## After burning to an sdcard, my device doesn't boot
- The downloaded image is not complete (download was interrupted).
Please clean the cache (`%HOME/.resin/cache` or `C:\Users\<user>\_resin\cache`) and run the command again. In the future, the CLI will check that the image is not complete and clean the cache for you.
Please clean the cache (`%HOME/.balena/cache` or `C:\Users\<user>\_balena\cache`) and run the command again. In the future, the CLI will check that the image is not complete and clean the cache for you.
### I get a permission error when burning to an sdcard
## I get a permission error when burning to an sdcard
- The SDCard is locked.
@ -24,36 +50,79 @@ net.js:156
Error: EINVAL, invalid argument
at new Socket (net.js:156:18)
at process.stdin (node.js:664:19)
at Object.Interface.createInterface (C:\cygwin\home\Juan Cruz Viotti\Projects\resin-cli\node_modules\inquirer\node_modules\readline2\index.js:31:43)
at PromptUI.UI (C:\cygwin\home\Juan Cruz Viotti\Projects\resin-cli\node_modules\inquirer\lib\ui\baseUI.js:23:40)
at new PromptUI (C:\cygwin\home\Juan Cruz Viotti\Projects\resin-cli\node_modules\inquirer\lib\ui\prompt.js:26:8)
at Object.promptModule [as prompt] (C:\cygwin\home\Juan Cruz Viotti\Projects\resin-cli\node_modules\inquirer\lib\inquirer.js:27:14)
at Object.Interface.createInterface (C:\cygwin\home\Juan Cruz Viotti\Projects\balena-cli\node_modules\inquirer\node_modules\readline2\index.js:31:43)
at PromptUI.UI (C:\cygwin\home\Juan Cruz Viotti\Projects\balena-cli\node_modules\inquirer\lib\ui\baseUI.js:23:40)
at new PromptUI (C:\cygwin\home\Juan Cruz Viotti\Projects\balena-cli\node_modules\inquirer\lib\ui\prompt.js:26:8)
at Object.promptModule [as prompt] (C:\cygwin\home\Juan Cruz Viotti\Projects\balena-cli\node_modules\inquirer\lib\inquirer.js:27:14)
```
- Some interactive widgets don't work on `Cygwin`. If you're running Windows, it's preferrable that you use `cmd.exe`, as `Cygwin` is [not official supported by Node.js](https://github.com/chjj/blessed/issues/56#issuecomment-42671945).
### I get `Invalid MBR boot signature` when configuring a device
## I get `Invalid MBR boot signature` when configuring a device
This error, accompanied with something like: `Expected 0xAA55, but saw 0x29FE` usually indicates a corrupted device operating system image in the cache, due to bad a internet connection during the download process.
Try clearing the cache with the following command and trying again:
```sh
$ rm -rf $HOME/.resin/cache
$ rm -rf $HOME/.balena/cache
```
Or in Windows:
```sh
> del /s /q %UserProfile%\_resin\cache
> del /s /q %UserProfile%\_balena\cache
```
### I get `EACCES: permission denied` when logging in
## I get `EACCES: permission denied` when logging in
The Resin CLI stores the session token in `$HOME/.resin` or `C:\Users\<user>\_resin` in UNIX based operating systems and Windows respectively. This error usually indicates that the user doesn't have permissions over that directory, which can happen if you ran the Resin CLI as `root`, and thus the directory got owned by him.
The balena CLI stores the session token in `$HOME/.balena` or `C:\Users\<user>\_balena` in UNIX based operating systems and Windows respectively. This error usually indicates that the user doesn't have permissions over that directory, which can happen if you ran the balena CLI as `root`, and thus the directory got owned by him.
Try resetting the ownership by running:
```sh
$ sudo chown -R <user> $HOME/.resin
$ sudo chown -R <user> $HOME/.balena
```
## Broken line wrapping / cursor behavior with `balena ssh`
Users sometimes come across broken line wrapping or cursor behavior in text terminals, for example when long command lines are typed in a `balena ssh` session, or when using text editors like `vim` or `nano`. This is not something specific to the balena CLI, being also a commonly reported issue with standard remote terminal tools like `ssh` or `telnet`. It is often a remote shell configuration issue (files like `/etc/profile`, `~/.bash_profile`, `~/.bash_login`, `~/.profile` and the like), including UTF-8 misconfiguration, the use of unsupported ASCII control characters in shell prompt formatting (e.g. the `$PS1` env var) or the output of tools or log files that use colored text. The issue can sometimes be fixed by resizing the client terminal window, or by running one or more of the following commands on the shell:
```sh
export TERMINAL=linux
stty sane
shopt -s checkwinsize
bind 'set horizontal-scroll-mode off'
```
Terminal multiplexer tools like GNU `screen` or `tmux` are sometimes reported to fix the issues, though at other times they are reported as the _cause_ of the problem. They have their own configuration files to take into account.
Further reference:
* https://stackoverflow.com/questions/1133031/shell-prompt-line-wrapping-issue
* https://superuser.com/questions/46948/any-way-to-fix-screens-mishandling-of-line-wrap-maybe-only-terminal-app
* https://unix.stackexchange.com/questions/105958/terminal-prompt-not-wrapping-correctly
* https://unix.stackexchange.com/questions/529377/terminal-long-line-wrapping
* https://github.com/microsoft/WSL/issues/1436
If nothing seems to help, consider also using a different client-side terminal application:
* Linux: xterm, KDE Konsole, GNOME Terminal
* Mac: Terminal, iTerm2
* Windows: PowerShell, PuTTY, WSL (Windows Subsystem for Linux)
## "Docker seems to be unavailable" error when using Windows Subsystem for Linux (WSL)
When running on WSL, the recommendation is to install a CLI release for Linux, like the standalone
zip package for Linux. However, commands like "balena build" that contact a local Docker daemon,
like the Docker Desktop for Windows, will try to reach Docker at the Unix socket path
`/var/run/docker.sock`, while Docker Desktop for Windows uses a Windows named pipe at
`//./pipe/docker_engine` (which the Linux CLI on WSL cannot use). A solution is:
- Open the Docker Desktop for Windows settings panel and tick the checkbox _"Expose daemon on tcp://localhost:2375 without TLS"._
- On the WSL command line, set an env var:
`export DOCKER_HOST=tcp://localhost:2375`
Alternatively, use the command-line options `-h 127.0.0.1 -p 2375` for commands like `balena build` and `balena deploy`.
Further reference:
- https://techcommunity.microsoft.com/t5/Containers/WSL-Interoperability-with-Docker/ba-p/382405
- https://forums.docker.com/t/wsl-and-docker-for-windows-cannot-connect-to-the-docker-daemon-at-tcp-localhost-2375-is-the-docker-daemon-running/63571/12

43
appveyor.yml Normal file
View File

@ -0,0 +1,43 @@
# appveyor file
# http://www.appveyor.com/docs/appveyor-yml
image: Visual Studio 2017
init:
- git config --global core.autocrlf input
cache:
- C:\Users\appveyor\.node-gyp
- '%AppData%\npm-cache'
matrix:
fast_finish: true
# what combinations to test
environment:
matrix:
- nodejs_version: 10
install:
- ps: Install-Product node $env:nodejs_version x64
- set PATH=%APPDATA%\npm;%PATH%
- npm config set python 'C:\Python27\python.exe'
- npm --version
# - npm install
build: off
test: off
deploy: off
test_script:
- node --version
- npm --version
# - npm test
deploy_script:
- node --version
- npm --version
# - npm run build:standalone
# - npm run build:installer
# - IF "%APPVEYOR_REPO_TAG%" == "true" (npm run release)
# - IF NOT "%APPVEYOR_REPO_TAG%" == "true" (echo 'Not tagged, skipping deploy')

376
automation/build-bin.ts Normal file
View File

@ -0,0 +1,376 @@
/**
* @license
* Copyright 2019 Balena Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { run as oclifRun } from '@oclif/dev-cli';
import * as archiver from 'archiver';
import * as Bluebird from 'bluebird';
import { execFile, spawn } from 'child_process';
import { stripIndent } from 'common-tags';
import * as filehound from 'filehound';
import * as fs from 'fs-extra';
import * as _ from 'lodash';
import * as path from 'path';
import { exec as execPkg } from 'pkg';
import * as rimraf from 'rimraf';
import * as semver from 'semver';
import * as shellEscape from 'shell-escape';
import * as util from 'util';
export const ROOT = path.join(__dirname, '..');
// Note: the following 'tslint disable' line was only required to
// satisfy ts-node under Appveyor's MSYS2 on Windows -- oddly specific.
// Maybe something to do with '/' vs '\' in paths in some tslint file.
// tslint:disable-next-line:no-var-requires
export const packageJSON = require(path.join(ROOT, 'package.json'));
export const version = 'v' + packageJSON.version;
const arch = process.arch;
function dPath(...paths: string[]) {
return path.join(ROOT, 'dist', ...paths);
}
interface PathByPlatform {
[platform: string]: string;
}
const standaloneZips: PathByPlatform = {
linux: dPath(`balena-cli-${version}-linux-${arch}-standalone.zip`),
darwin: dPath(`balena-cli-${version}-macOS-${arch}-standalone.zip`),
win32: dPath(`balena-cli-${version}-windows-${arch}-standalone.zip`),
};
const oclifInstallers: PathByPlatform = {
darwin: dPath('macos', `balena-${version}.pkg`),
win32: dPath('win', `balena-${version}-${arch}.exe`),
};
const renamedOclifInstallers: PathByPlatform = {
darwin: dPath(`balena-cli-${version}-macOS-${arch}-installer.pkg`),
win32: dPath(`balena-cli-${version}-windows-${arch}-installer.exe`),
};
export const finalReleaseAssets: { [platform: string]: string[] } = {
win32: [standaloneZips['win32'], renamedOclifInstallers['win32']],
darwin: [standaloneZips['darwin'], renamedOclifInstallers['darwin']],
linux: [standaloneZips['linux']],
};
const MSYS2_BASH = 'C:\\msys64\\usr\\bin\\bash.exe';
/**
* Run the MSYS2 bash.exe shell in a child process (child_process.spawn()).
* The given argv arguments are escaped using the 'shell-escape' package,
* so that backslashes in Windows paths, and other bash-special characters,
* are preserved. If argv is not provided, defaults to process.argv, to the
* effect that this current (parent) process is re-executed under MSYS2 bash.
* This is useful to change the default shell from cmd.exe to MSYS2 bash on
* Windows.
* @param argv Arguments to be shell-escaped and given to MSYS2 bash.exe.
*/
export async function runUnderMsys(argv?: string[]) {
const newArgv = argv || process.argv;
await new Promise((resolve, reject) => {
const args = ['-lc', shellEscape(newArgv)];
const child = spawn(MSYS2_BASH, args, { stdio: 'inherit' });
child.on('close', code => {
if (code) {
console.log(`runUnderMsys: child process exited with code ${code}`);
reject(code);
} else {
resolve();
}
});
});
}
/**
* Use the 'pkg' module to create a single large executable file with
* the contents of 'node_modules' and the CLI's javascript code.
* Also copy a number of native modules (binary '.node' files) that are
* compiled during 'npm install' to the 'build-bin' folder, alongside
* the single large executable file created by pkg. (This is necessary
* because of a pkg limitation that does not allow binary executables
* to be directly executed from inside another binary executable.)
*/
async function buildPkg() {
const args = [
'--target',
'host',
'--output',
'build-bin/balena',
'package.json',
];
console.log('=======================================================');
console.log(`execPkg ${args.join(' ')}`);
console.log(`cwd="${process.cwd()}" ROOT="${ROOT}"`);
console.log('=======================================================');
await execPkg(args);
const xpaths: Array<[string, string[]]> = [
// [platform, [path, to, file]]
['*', ['opn', 'xdg-open']],
['darwin', ['denymount', 'bin', 'denymount']],
];
await Bluebird.map(xpaths, ([platform, xpath]) => {
if (platform === '*' || platform === process.platform) {
// eg copy from node_modules/opn/xdg-open to build-bin/xdg-open
return fs.copy(
path.join(ROOT, 'node_modules', ...xpath),
path.join(ROOT, 'build-bin', xpath.pop()!),
);
}
});
const nativeExtensionPaths: string[] = await filehound
.create()
.paths(path.join(ROOT, 'node_modules'))
.ext(['node', 'dll'])
.find();
console.log(`\nCopying to build-bin:\n${nativeExtensionPaths.join('\n')}`);
await Bluebird.map(nativeExtensionPaths, extPath =>
fs.copy(
extPath,
extPath.replace(
path.join(ROOT, 'node_modules'),
path.join(ROOT, 'build-bin'),
),
),
);
}
/**
* Run some basic tests on the built pkg executable.
* TODO: test more than just `balena version -j`; integrate with the
* existing mocha/chai CLI command testing.
*/
async function testPkg() {
type JsonVersions = import('../lib/actions-oclif/version').JsonVersions;
const pkgBalenaPath = path.join(
ROOT,
'build-bin',
process.platform === 'win32' ? 'balena.exe' : 'balena',
);
console.log(`Testing standalone package "${pkgBalenaPath}"...`);
// Run `balena version -j`, parse its stdout as JSON, and check that the
// reported Node.js major version matches semver.major(process.version)
const stdout = await getSubprocessStdout(pkgBalenaPath, ['version', '-j']);
let pkgNodeVersion = '';
let pkgNodeMajorVersion = 0;
try {
const balenaVersions: JsonVersions = JSON.parse(stdout);
pkgNodeVersion = balenaVersions['Node.js'];
pkgNodeMajorVersion = semver.major(pkgNodeVersion);
} catch (err) {
throw new Error(stripIndent`
Error parsing JSON output of "balena version -j": ${err}
Original output: "${stdout}"`);
}
if (semver.major(process.version) !== pkgNodeMajorVersion) {
throw new Error(
`Mismatched major version: built-in pkg Node version="${pkgNodeVersion}" vs process.version="${
process.version
}"`,
);
}
console.log('Success! (standalone package test successful)');
}
/**
* Create the zip file for the standalone 'pkg' bundle previously created
* by the buildPkg() function in 'build-bin.ts'.
*/
async function zipPkg() {
const outputFile = standaloneZips[process.platform];
if (!outputFile) {
throw new Error(
`Standalone installer unavailable for platform "${process.platform}"`,
);
}
await fs.mkdirp(path.dirname(outputFile));
await new Promise((resolve, reject) => {
console.log(`Zipping standalone package to "${outputFile}"...`);
const archive = archiver('zip', {
zlib: { level: 7 },
});
archive.directory(path.join(ROOT, 'build-bin'), 'balena-cli');
const outputStream = fs.createWriteStream(outputFile);
outputStream.on('close', resolve);
outputStream.on('error', reject);
archive.on('error', reject);
archive.on('warning', console.warn);
archive.pipe(outputStream);
archive.finalize();
});
}
export async function buildStandaloneZip() {
console.log(`Building standalone zip package for CLI ${version}`);
try {
await buildPkg();
await testPkg();
await zipPkg();
} catch (error) {
console.log(`Error creating or testing standalone zip package:\n ${error}`);
process.exit(1);
}
console.log(`Standalone zip package build completed`);
}
async function renameInstallerFiles() {
if (await fs.pathExists(oclifInstallers[process.platform])) {
await fs.rename(
oclifInstallers[process.platform],
renamedOclifInstallers[process.platform],
);
}
}
/**
* If the CSC_LINK and CSC_KEY_PASSWORD env vars are set, digitally sign the
* executable installer by running the balena-io/scripts/shared/sign-exe.sh
* script (which must be in the PATH) using a MSYS2 bash shell.
*/
async function signWindowsInstaller() {
if (process.env.CSC_LINK && process.env.CSC_KEY_PASSWORD) {
const exeName = renamedOclifInstallers[process.platform];
const execFileAsync = util.promisify<string, string[], void>(execFile);
console.log(`Signing installer "${exeName}"`);
await execFileAsync(MSYS2_BASH, [
'sign-exe.sh',
'-f',
exeName,
'-d',
`balena-cli ${version}`,
]);
} else {
console.log(
'Skipping installer signing step because CSC_* env vars are not set',
);
}
}
/**
* Run the `oclif-dev pack:win` or `pack:macos` command (depending on the value
* of process.platform) to generate the native installers (which end up under
* the 'dist' folder). There are some harcoded options such as selecting only
* 64-bit binaries under Windows.
*/
export async function buildOclifInstaller() {
let packOS = '';
let packOpts = ['-r', ROOT];
if (process.platform === 'darwin') {
packOS = 'macos';
} else if (process.platform === 'win32') {
packOS = 'win';
packOpts = packOpts.concat('-t', 'win32-x64');
}
if (packOS) {
console.log(`Building oclif installer for CLI ${version}`);
const packCmd = `pack:${packOS}`;
const dirs = [path.join(ROOT, 'dist', packOS)];
if (packOS === 'win') {
dirs.push(path.join(ROOT, 'tmp', 'win*'));
}
for (const dir of dirs) {
console.log(`rimraf(${dir})`);
await Bluebird.fromCallback(cb => rimraf(dir, cb));
}
console.log('=======================================================');
console.log(`oclif-dev "${packCmd}" "${packOpts.join('" "')}"`);
console.log(`cwd="${process.cwd()}" ROOT="${ROOT}"`);
console.log('=======================================================');
await oclifRun([packCmd].concat(...packOpts));
await renameInstallerFiles();
// The Windows installer is explicitly signed here (oclif doesn't do it).
// The macOS installer is automatically signed by oclif (which runs the
// `pkgbuild` tool), using the certificate name given in package.json
// (`oclif.macos.sign` section).
if (process.platform === 'win32') {
await signWindowsInstaller();
}
console.log(`oclif installer build completed`);
}
}
/**
* Convert e.g. 'C:\myfolder' -> '/C/myfolder' so that the path can be given
* as argument to "unix tools" like 'tar' under MSYS or MSYS2 on Windows.
*/
export function fixPathForMsys(p: string): string {
return p.replace(/\\/g, '/').replace(/^([a-zA-Z]):/, '/$1');
}
/**
* Run the executable at execPath as a child process, and resolve a promise
* to the executable's stdout output as a string. Reject the promise if
* anything is printed to stderr, or if the child process exits with a
* non-zero exit code.
* @param execPath Executable path
* @param args Command-line argument for the executable
*/
async function getSubprocessStdout(
execPath: string,
args: string[],
): Promise<string> {
const child = spawn(execPath, args);
return new Promise((resolve, reject) => {
let stdout = '';
child.stdout.on('error', reject);
child.stderr.on('error', reject);
child.stdout.on('data', (data: Buffer) => {
try {
stdout = data.toString();
} catch (err) {
reject(err);
}
});
child.stderr.on('data', (data: Buffer) => {
try {
const stderr = data.toString();
// ignore any debug lines, but ensure that we parse
// every line provided to the stderr stream
const lines = _.filter(
stderr.trim().split(/\r?\n/),
line => !line.startsWith('[debug]'),
);
if (lines.length > 0) {
reject(
new Error(`"${execPath}": non-empty stderr "${lines.join('\n')}"`),
);
}
} catch (err) {
reject(err);
}
});
child.on('exit', (code: number) => {
if (code) {
reject(new Error(`"${execPath}": non-zero exit code "${code}"`));
} else {
resolve(stdout);
}
});
});
}

View File

@ -0,0 +1,152 @@
/**
* @license
* Copyright 2019 Balena Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as path from 'path';
import { MarkdownFileParser } from './utils';
/**
* This is the skeleton of CLI documentation/reference web page at:
* https://www.balena.io/docs/reference/cli/
*
* The `getCapitanoDoc` function in this module parses README.md and adds
* some content to this object.
*/
const capitanoDoc = {
title: 'Balena CLI Documentation',
introduction: '',
categories: [
{
title: 'API keys',
files: ['build/actions/api-key.js'],
},
{
title: 'Application',
files: ['build/actions/app.js'],
},
{
title: 'Authentication',
files: ['build/actions/auth.js'],
},
{
title: 'Device',
files: ['build/actions/device.js'],
},
{
title: 'Environment Variables',
files: [
'build/actions-oclif/envs.js',
'build/actions-oclif/env/add.js',
'build/actions-oclif/env/rename.js',
'build/actions-oclif/env/rm.js',
],
},
{
title: 'Tags',
files: ['build/actions/tags.js'],
},
{
title: 'Help and Version',
files: ['build/actions/help.js', 'build/actions-oclif/version.js'],
},
{
title: 'Keys',
files: ['build/actions/keys.js'],
},
{
title: 'Logs',
files: ['build/actions/logs.js'],
},
{
title: 'Network',
files: [
'build/actions/scan.js',
'build/actions/ssh.js',
'build/actions/tunnel.js',
],
},
{
title: 'Notes',
files: ['build/actions/notes.js'],
},
{
title: 'OS',
files: ['build/actions/os.js', 'build/actions-oclif/os/configure.js'],
},
{
title: 'Config',
files: ['build/actions/config.js'],
},
{
title: 'Preload',
files: ['build/actions/preload.js'],
},
{
title: 'Push',
files: ['build/actions/push.js'],
},
{
title: 'Settings',
files: ['build/actions/settings.js'],
},
{
title: 'Local',
files: ['build/actions/local/index.js'],
},
{
title: 'Deploy',
files: ['build/actions/build.js', 'build/actions/deploy.js'],
},
{
title: 'Platform',
files: ['build/actions/join.js', 'build/actions/leave.js'],
},
{
title: 'Utilities',
files: ['build/actions/util.js'],
},
],
};
/**
* Modify and return the `capitanoDoc` object above in order to render the
* CLI documentation/reference web page at:
* https://www.balena.io/docs/reference/cli/
*
* This function parses the README.md file to extract relevant sections
* for the documentation web page.
*/
export async function getCapitanoDoc(): Promise<typeof capitanoDoc> {
const readmePath = path.join(__dirname, '..', '..', 'README.md');
const mdParser = new MarkdownFileParser(readmePath);
const sections: string[] = await Promise.all([
mdParser.getSectionOfTitle('About').then((sectionLines: string) => {
// delete the title of the 'About' section for the web page
const match = /^(#+)\s+.+?\n\s*([^]*)/.exec(sectionLines);
if (!match || match.length < 3) {
throw new Error(`Error parsing section title`);
}
// match[1] has the title, match[2] has the rest
return match && match[2];
}),
mdParser.getSectionOfTitle('Installation'),
mdParser.getSectionOfTitle('Getting Started'),
mdParser.getSectionOfTitle('Support, FAQ and troubleshooting'),
]);
capitanoDoc.introduction = sections.join('\n');
return capitanoDoc;
}

33
automation/capitanodoc/doc-types.d.ts vendored Normal file
View File

@ -0,0 +1,33 @@
/**
* @license
* Copyright 2019 Balena Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Command as OclifCommandClass } from '@oclif/command';
import { CommandDefinition as CapitanoCommand } from 'capitano';
type OclifCommand = typeof OclifCommandClass;
export interface Document {
title: string;
introduction: string;
categories: Category[];
}
export interface Category {
title: string;
commands: Array<CapitanoCommand | OclifCommand>;
}
export { CapitanoCommand, OclifCommand };

View File

@ -0,0 +1,89 @@
/**
* @license
* Copyright 2019 Balena Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as _ from 'lodash';
import * as path from 'path';
import { getCapitanoDoc } from './capitanodoc';
import { CapitanoCommand, Category, Document, OclifCommand } from './doc-types';
import * as markdown from './markdown';
/**
* Generates the markdown document (as a string) for the CLI documentation
* page on the web: https://www.balena.io/docs/reference/cli/
*/
export async function renderMarkdown(): Promise<string> {
const capitanodoc = await getCapitanoDoc();
const result: Document = {
title: capitanodoc.title,
introduction: capitanodoc.introduction,
categories: [],
};
for (const commandCategory of capitanodoc.categories) {
const category: Category = {
title: commandCategory.title,
commands: [],
};
for (const jsFilename of commandCategory.files) {
category.commands.push(
...(jsFilename.includes('actions-oclif')
? importOclifCommands(jsFilename)
: importCapitanoCommands(jsFilename)),
);
}
result.categories.push(category);
}
return markdown.render(result);
}
function importCapitanoCommands(jsFilename: string): CapitanoCommand[] {
const actions = require(path.join(process.cwd(), jsFilename));
const commands: CapitanoCommand[] = [];
if (actions.signature) {
commands.push(_.omit(actions, 'action'));
} else {
for (const actionName of Object.keys(actions)) {
const actionCommand = actions[actionName];
commands.push(_.omit(actionCommand, 'action'));
}
}
return commands;
}
function importOclifCommands(jsFilename: string): OclifCommand[] {
const command: OclifCommand = require(path.join(process.cwd(), jsFilename))
.default as OclifCommand;
return [command];
}
/**
* Print the CLI docs markdown to stdout.
* See package.json for how the output is redirected to a file.
*/
async function printMarkdown() {
try {
console.log(await renderMarkdown());
} catch (error) {
console.error(error);
process.exit(1);
}
}
printMarkdown();

View File

@ -0,0 +1,160 @@
/**
* @license
* Copyright 2019 Balena Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { flagUsages } from '@oclif/parser';
import * as ent from 'ent';
import * as _ from 'lodash';
import { getManualSortCompareFunction } from '../../lib/utils/helpers';
import { capitanoizeOclifUsage } from '../../lib/utils/oclif-utils';
import { CapitanoCommand, Category, Document, OclifCommand } from './doc-types';
import * as utils from './utils';
function renderCapitanoCommand(command: CapitanoCommand): string[] {
const result = [`## ${ent.encode(command.signature)}`, command.help];
if (!_.isEmpty(command.options)) {
result.push('### Options');
for (const option of command.options!) {
if (option == null) {
throw new Error(`Undefined option in markdown generation!`);
}
result.push(
`#### ${utils.parseCapitanoOption(option)}`,
option.description,
);
}
}
return result;
}
function renderOclifCommand(command: OclifCommand): string[] {
const result = [`## ${ent.encode(command.usage)}`];
const description = (command.description || '')
.split('\n')
.slice(1) // remove the first line, which oclif uses as help header
.join('\n')
.trim();
result.push(description);
if (!_.isEmpty(command.examples)) {
result.push('Examples:', command.examples!.map(v => `\t${v}`).join('\n'));
}
if (!_.isEmpty(command.args)) {
result.push('### Arguments');
for (const arg of command.args!) {
result.push(`#### ${arg.name.toUpperCase()}`, arg.description || '');
}
}
if (!_.isEmpty(command.flags)) {
result.push('### Options');
for (const [name, flag] of Object.entries(command.flags!)) {
if (name === 'help') {
continue;
}
flag.name = name;
const flagUsage = flagUsages([flag])
.map(([usage, _description]) => usage)
.join()
.trim();
result.push(`#### ${flagUsage}`);
result.push(flag.description || '');
}
}
return result;
}
function renderCategory(category: Category): string[] {
const result = [`# ${category.title}`];
for (const command of category.commands) {
result.push(
...(typeof command === 'object'
? renderCapitanoCommand(command)
: renderOclifCommand(command)),
);
}
return result;
}
function getAnchor(cmdSignature: string): string {
return `#${_.trim(cmdSignature.replace(/\W+/g, '-'), '-').toLowerCase()}`;
}
function renderToc(categories: Category[]): string[] {
const result = [`# CLI Command Reference`];
for (const category of categories) {
result.push(`- ${category.title}`);
result.push(
category.commands
.map(command => {
const signature =
typeof command === 'object'
? command.signature // Capitano
: capitanoizeOclifUsage(command.usage); // oclif
return `\t- [${ent.encode(signature)}](${getAnchor(signature)})`;
})
.join('\n'),
);
}
return result;
}
const manualCategorySorting: { [category: string]: string[] } = {
'Environment Variables': ['envs', 'env rm', 'env add', 'env rename'],
OS: [
'os versions',
'os download',
'os build config',
'os configure',
'os initialize',
],
};
function sortCommands(doc: Document): void {
for (const category of doc.categories) {
if (category.title in manualCategorySorting) {
category.commands = category.commands.sort(
getManualSortCompareFunction<CapitanoCommand | OclifCommand, string>(
manualCategorySorting[category.title],
(cmd: CapitanoCommand | OclifCommand, x: string) =>
typeof cmd === 'object' // Capitano vs oclif command
? cmd.signature.replace(/\W+/g, ' ').includes(x)
: (cmd.usage || '')
.toString()
.replace(/\W+/g, ' ')
.includes(x),
),
);
}
}
}
export function render(doc: Document) {
sortCommands(doc);
const result = [
`# ${doc.title}`,
doc.introduction,
...renderToc(doc.categories),
];
for (const category of doc.categories) {
result.push(...renderCategory(category));
}
return result.join('\n\n');
}

View File

@ -0,0 +1,139 @@
/**
* @license
* Copyright 2019 Balena Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { OptionDefinition } from 'capitano';
import * as ent from 'ent';
import * as fs from 'fs';
import * as _ from 'lodash';
import * as readline from 'readline';
export function getOptionPrefix(signature: string) {
if (signature.length > 1) {
return '--';
} else {
return '-';
}
}
export function getOptionSignature(signature: string) {
return `${getOptionPrefix(signature)}${signature}`;
}
export function parseCapitanoOption(option: OptionDefinition): string {
let result = getOptionSignature(option.signature);
if (_.isArray(option.alias)) {
for (const alias of option.alias) {
result += `, ${getOptionSignature(alias)}`;
}
} else if (_.isString(option.alias)) {
result += `, ${getOptionSignature(option.alias)}`;
}
if (option.parameter) {
result += ` <${option.parameter}>`;
}
return ent.encode(result);
}
export class MarkdownFileParser {
constructor(public mdFilePath: string) {}
/**
* Extract the lines of a markdown document section with the given title.
* For example, consider this sample markdown document:
* ```
* # balena CLI
*
* ## Introduction
* Lorem ipsum dolor sit amet, consectetur adipiscing elit,
*
* ## Getting Started
* sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
*
* ### Prerequisites
* - Foo
* - Bar
*
* ## Support
* Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.
* ```
*
* Calling getSectionOfTitle('Getting Started') for the markdown doc above
* returns everything from line '## Getting Started' (included) to line
* '## Support' (excluded). This method counts the number of '#' characters
* to determine that subsections should be included as part of the parent
* section.
*
* @param title The section title without '#' chars, eg. 'Getting Started'
*/
public async getSectionOfTitle(
title: string,
includeSubsections = true,
): Promise<string> {
let foundSectionLines: string[];
let foundSectionLevel = 0;
const rl = readline.createInterface({
input: fs.createReadStream(this.mdFilePath),
crlfDelay: Infinity,
});
rl.on('line', line => {
// try to match a line like "## Getting Started", where the number
// of '#' characters is the sectionLevel ('##' -> 2), and the
// sectionTitle is "Getting Started"
const match = /^(#+)\s+(.+)/.exec(line);
if (match) {
const sectionLevel = match[1].length;
const sectionTitle = match[2];
// If the target section had already been found: append a line, or end it
if (foundSectionLines) {
if (!includeSubsections || sectionLevel <= foundSectionLevel) {
// end previously found section
rl.close();
}
} else if (sectionTitle === title) {
// found the target section
foundSectionLevel = sectionLevel;
foundSectionLines = [];
}
}
if (foundSectionLines) {
foundSectionLines.push(line);
}
});
return await new Promise((resolve, reject) => {
rl.on('close', () => {
if (foundSectionLines) {
resolve(foundSectionLines.join('\n'));
} else {
reject(
new Error(
`Markdown section not found: title="${title}" file="${
this.mdFilePath
}"`,
),
);
}
});
});
}
}

View File

@ -0,0 +1,75 @@
#!/usr/bin/env node
'use strict';
/**
* Check that semver v1 is greater than or equal to semver v2.
*
* We don't `require('semver')` to allow this script to be run as a npm
* 'preinstall' hook, at which point no dependencies have been installed.
*/
function semverGte(v1, v2) {
let v1Array, v2Array; // number[]
try {
const [, major1, minor1, patch1] = /v?(\d+)\.(\d+).(\d+)/.exec(v1);
const [, major2, minor2, patch2] = /v?(\d+)\.(\d+).(\d+)/.exec(v2);
v1Array = [parseInt(major1), parseInt(minor1), parseInt(patch1)];
v2Array = [parseInt(major2), parseInt(minor2), parseInt(patch2)];
} catch (err) {
throw new Error(`Invalid semver versions: '${v1}' or '${v2}'`);
}
for (let i = 0; i < 3; i++) {
if (v1Array[i] < v2Array[i]) {
return false;
} else if (v1Array[i] > v2Array[i]) {
return true;
}
}
return true;
}
function _testSemverGet() {
const assert = require('assert').strict;
assert(semverGte('6.4.1', '6.4.1'));
assert(semverGte('6.4.1', 'v6.4.1'));
assert(semverGte('v6.4.1', '6.4.1'));
assert(semverGte('v6.4.1', 'v6.4.1'));
assert(semverGte('6.4.1', '6.4.0'));
assert(semverGte('6.4.1', '6.3.1'));
assert(semverGte('6.4.1', '5.4.1'));
assert(!semverGte('6.4.1', '6.4.2'));
assert(!semverGte('6.4.1', '6.5.1'));
assert(!semverGte('6.4.1', '7.4.1'));
assert(semverGte('v6.4.1', 'v4.0.0'));
assert(!semverGte('v6.4.1', 'v6.9.0'));
assert(!semverGte('v6.4.1', 'v7.0.0'));
}
function checkNpmVersion() {
const execSync = require('child_process').execSync;
const npmVersion = execSync('npm --version')
.toString()
.trim();
const requiredVersion = '6.9.0';
if (!semverGte(npmVersion, requiredVersion)) {
// In case you take issue with the error message below:
// "At this point, however, your 'npm-shrinkwrap.json' file has
// already been damaged"
// ... and think: "why not add the check to the 'preinstall' hook?",
// the reason is that it would unnecessarily prevent end users from
// using npm v6.4.1 that ships with Node 8. (It is OK for the
// shrinkwrap file to get damaged if it is not going to be reused.)
console.error(`\
-------------------------------------------------------------------------------
Error: npm version '${npmVersion}' detected. Please upgrade to npm v${requiredVersion} or later
because of a bug that causes the 'npm-shrinkwrap.json' file to be damaged.
At this point, however, your 'npm-shrinkwrap.json' file has already been
damaged. Please revert it to the master branch state with a command such as:
"git checkout master -- npm-shrinkwrap.json"
Then re-run "npm install" using npm version ${requiredVersion} or later.
-------------------------------------------------------------------------------`);
process.exit(1);
}
}
checkNpmVersion();

258
automation/deploy-bin.ts Normal file
View File

@ -0,0 +1,258 @@
/**
* @license
* Copyright 2019 Balena Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as Bluebird from 'bluebird';
import * as _ from 'lodash';
import * as semver from 'semver';
import { finalReleaseAssets, version } from './build-bin';
const { GITHUB_TOKEN } = process.env;
/**
* Create or update a release in GitHub's releases page, uploading the
* installer files (standalone zip + native oclif installers).
*/
export async function createGitHubRelease() {
console.log(`Publishing release ${version} to GitHub`);
const publishRelease = await import('publish-release');
const ghRelease = await Bluebird.fromCallback(
publishRelease.bind(null, {
token: GITHUB_TOKEN || '',
owner: 'balena-io',
repo: 'balena-cli',
tag: version,
name: `balena-CLI ${version}`,
reuseRelease: true,
assets: finalReleaseAssets[process.platform],
}),
);
console.log(`Release ${version} successful: ${ghRelease.html_url}`);
}
/**
* Top-level function to create a CLI release in GitHub's releases page:
* call zipStandaloneInstaller(), rename the files as we'd like them to
* display on the releases page, and call createGitHubRelease() to upload
* the files.
*/
export async function release() {
try {
await createGitHubRelease();
} catch (err) {
console.error('Release failed');
console.error(err);
process.exit(1);
}
}
let cachedOctokit: any;
/** Return a cached Octokit instance, creating a new one as needed. */
function getOctokit(): any {
if (cachedOctokit) {
return cachedOctokit;
}
const Octokit = require('@octokit/rest').plugin(
require('@octokit/plugin-throttling'),
);
return (cachedOctokit = new Octokit({
auth: GITHUB_TOKEN,
throttle: {
onRateLimit: (retryAfter: number, options: any) => {
console.warn(
`Request quota exhausted for request ${options.method} ${
options.url
}`,
);
// retries 3 times
if (options.request.retryCount < 3) {
console.log(`Retrying after ${retryAfter} seconds!`);
return true;
}
},
onAbuseLimit: (_retryAfter: number, options: any) => {
// does not retry, only logs a warning
console.warn(
`Abuse detected for request ${options.method} ${options.url}`,
);
},
},
}));
}
/**
* Extract pagination information (current page, total pages, ordinal number)
* from the 'link' response header (example below), using the parse-link-header
* npm package:
* "link": "<https://api.github.com/repositories/187370853/releases?per_page=2&page=2>; rel=\"next\",
* <https://api.github.com/repositories/187370853/releases?per_page=2&page=3>; rel=\"last\""
*
* @param response Octokit response object (including response.headers.link)
* @param perPageDefault Default per_page pagination value if missing in URL
* @return Object where 'page' is the current page number (1-based),
* 'pages' is the total number of pages, and 'ordinal' is the ordinal number
* (3rd, 4th, 5th...) of the first item in the current page.
*/
function getPageNumbers(
response: any,
perPageDefault: number,
): { page: number; pages: number; ordinal: number } {
const res = { page: 1, pages: 1, ordinal: 1 };
if (!response.headers.link) {
return res;
}
const parse = require('parse-link-header');
const parsed = parse(response.headers.link);
let perPage = perPageDefault;
if (parsed.next) {
if (parsed.next.per_page) {
perPage = parseInt(parsed.next.per_page, 10);
}
res.page = parseInt(parsed.next.page, 10) - 1;
res.pages = parseInt(parsed.last.page, 10);
} else {
if (parsed.prev.per_page) {
perPage = parseInt(parsed.prev.per_page, 10);
}
res.page = res.pages = parseInt(parsed.prev.page, 10) + 1;
}
res.ordinal = (res.page - 1) * perPage + 1;
return res;
}
/**
* Iterate over every GitHub release in the given owner/repo, check whether
* its tag_name matches against the affectedVersions semver spec, and if so
* replace its release description (body) with the given newDescription value.
* @param owner GitHub repo owner, e.g. 'balena-io' or 'pdcastro'
* @param repo GitHub repo, e.g. 'balena-cli'
* @param affectedVersions Semver spec, e.g. '2.6.1 - 7.10.9 || 8.0.0'
* @param newDescription New release description (body)
* @param editID Short string present in newDescription, e.g. '[AA101]', that
* can be searched to determine whether that release has already been updated.
*/
async function updateGitHubReleaseDescriptions(
owner: string,
repo: string,
affectedVersions: string,
newDescription: string,
editID: string,
) {
const perPage = 30;
const octokit = getOctokit();
const options = await octokit.repos.listReleases.endpoint.merge({
owner,
repo,
per_page: perPage,
});
let errCount = 0;
for await (const response of octokit.paginate.iterator(options)) {
const { page: thisPage, pages: totalPages, ordinal } = getPageNumbers(
response,
perPage,
);
let i = 0;
for (const cliRelease of response.data) {
const prefix = `[#${ordinal + i++} pg ${thisPage}/${totalPages}]`;
if (!cliRelease.id) {
console.error(
`${prefix} Error: missing release ID (errCount=${++errCount})`,
);
continue;
}
const skipMsg = `${prefix} skipping release "${cliRelease.tag_name}" (${
cliRelease.id
})`;
if (cliRelease.draft === true) {
console.info(`${skipMsg}: draft release`);
continue;
} else if (cliRelease.body && cliRelease.body.includes(editID)) {
console.info(`${skipMsg}: already updated`);
continue;
} else if (!semver.satisfies(cliRelease.tag_name, affectedVersions)) {
console.info(`${skipMsg}: outside version range`);
continue;
} else {
const updatedRelease = {
owner,
repo,
release_id: cliRelease.id,
body: newDescription,
};
let oldBodyPreview = cliRelease.body;
if (oldBodyPreview) {
oldBodyPreview = oldBodyPreview.replace(/\s+/g, ' ').trim();
if (oldBodyPreview.length > 12) {
oldBodyPreview = oldBodyPreview.substring(0, 9) + '...';
}
}
console.info(
`${prefix} updating release "${cliRelease.tag_name}" (${
cliRelease.id
}) old body="${oldBodyPreview}"`,
);
try {
await octokit.repos.updateRelease(updatedRelease);
} catch (err) {
console.error(
`${skipMsg}: Error: ${err.message} (count=${++errCount})`,
);
continue;
}
}
}
}
}
/**
* Add a warning description to CLI releases affected by a mixpanel tracking
* security issue (#1359). This function can be executed "manually" with the
* following command line:
*
* npx ts-node --type-check -P automation/tsconfig.json automation/run.ts fix1359
*/
export async function updateDescriptionOfReleasesAffectedByIssue1359() {
// Run only on Linux/Node10, instead of all platform/Node combinations.
// (It could have been any other platform, as long as it only runs once.)
if (process.platform !== 'linux' || semver.major(process.version) !== 10) {
return;
}
const owner = 'balena-io';
const repo = 'balena-cli';
const affectedVersions =
'2.6.1 - 7.10.9 || 8.0.0 - 8.1.0 || 9.0.0 - 9.15.6 || 10.0.0 - 10.17.5 || 11.0.0 - 11.7.2';
const editID = '[AA100]';
let newDescription = `
Please note: the "login" command in this release is affected by a
security issue fixed in versions
[7.10.10](https://github.com/balena-io/balena-cli/releases/tag/v7.10.10),
[8.1.1](https://github.com/balena-io/balena-cli/releases/tag/v8.1.1),
[9.15.7](https://github.com/balena-io/balena-cli/releases/tag/v9.15.7),
[10.17.6](https://github.com/balena-io/balena-cli/releases/tag/v10.17.6),
[11.7.3](https://github.com/balena-io/balena-cli/releases/tag/v11.7.3)
and later. If you need to use this version, avoid passing your password,
keys or tokens as command-line arguments. ${editID}`;
// remove line breaks and collapse white space
newDescription = newDescription.replace(/\s+/g, ' ').trim();
await updateGitHubReleaseDescriptions(
owner,
repo,
affectedVersions,
newDescription,
editID,
);
}

115
automation/run.ts Normal file
View File

@ -0,0 +1,115 @@
/**
* @license
* Copyright 2019 Balena Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as _ from 'lodash';
import {
buildOclifInstaller,
buildStandaloneZip,
fixPathForMsys,
ROOT,
runUnderMsys,
} from './build-bin';
import {
release,
updateDescriptionOfReleasesAffectedByIssue1359,
} from './deploy-bin';
function exitWithError(error: Error | string): never {
console.error(`Error: ${error}`);
process.exit(1);
throw error; // to please the Typescript compiler
}
/**
* Trivial command-line parser. Check whether the command-line argument is one
* of the following strings, then call the appropriate functions:
* 'build:installer' (to build a native oclif installer)
* 'build:standalone' (to build a standalone pkg package)
* 'release' (to create/update a GitHub release)
*
* In the case of 'build:installer', also call runUnderMsys() to switch the
* shell from cmd.exe to MSYS2 bash.exe.
*
* @param args Arguments to parse (default is process.argv.slice(2))
*/
export async function run(args?: string[]) {
args = args || process.argv.slice(2);
console.log(`automation/run.ts process.argv=[${process.argv}]\n`);
console.log(`automation/run.ts args=[${args}]`);
if (_.isEmpty(args)) {
return exitWithError('missing command-line arguments');
}
const commands: { [cmd: string]: () => void } = {
'build:installer': buildOclifInstaller,
'build:standalone': buildStandaloneZip,
fix1359: updateDescriptionOfReleasesAffectedByIssue1359,
release,
};
for (const arg of args) {
if (!commands.hasOwnProperty(arg)) {
return exitWithError(`command unknown: ${arg}`);
}
}
// If runUnderMsys() is called to re-execute this script under MSYS2,
// the current working dir becomes the MSYS2 homedir, so we change back.
process.chdir(ROOT);
// The BUILD_TMP env var is used as an alternative location for oclif
// (patched) to copy/extract the CLI files, run npm install and then
// create the NSIS executable installer for Windows. This was necessary
// to avoid issues with a 260-char limit on Windows paths (possibly a
// limitation of some library used by NSIS), as the "current working dir"
// provided by balena CI is a rather long path to start with.
if (process.platform === 'win32' && !process.env.BUILD_TMP) {
const randID = require('crypto')
.randomBytes(6)
.toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_'); // base64url (RFC 4648)
process.env.BUILD_TMP = `C:\\tmp\\${randID}`;
}
for (const arg of args) {
try {
if (arg === 'build:installer' && process.platform === 'win32') {
// ensure running under MSYS2
if (!process.env.MSYSTEM) {
process.env.MSYS2_PATH_TYPE = 'inherit';
await runUnderMsys([
fixPathForMsys(process.argv[0]),
fixPathForMsys(process.argv[1]),
arg,
]);
continue;
}
if (process.env.MSYS2_PATH_TYPE !== 'inherit') {
throw new Error(
'the MSYS2_PATH_TYPE env var must be set to "inherit"',
);
}
}
const cmdFunc = commands[arg];
await cmdFunc();
} catch (err) {
return exitWithError(`"${arg}": ${err}`);
}
}
}
run();

19
automation/tsconfig.json Normal file
View File

@ -0,0 +1,19 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es2017",
"strict": true,
"strictPropertyInitialization": false,
"noUnusedLocals": true,
"noUnusedParameters": true,
"preserveConstEnums": true,
"removeComments": true,
"resolveJsonModule": true,
"sourceMap": true,
"skipLibCheck": true,
"typeRoots" : [
"../node_modules/@types",
"../typings"
]
}
}

73
balena-completion.bash Normal file
View File

@ -0,0 +1,73 @@
#!/bin/bash
_balena_complete()
{
local cur prev
# Valid top-level completions
commands="app apps build config deploy device devices env envs help key \
keys local login logout logs note os preload quickstart settings \
scan ssh util version whoami"
# Sub-completions
app_cmds="create restart rm"
config_cmds="generate inject read reconfigure write"
device_cmds="identify init move public-url reboot register rename rm \
shutdown"
device_public_url_cmds="disable enable status"
env_cmds="add rename rm"
key_cmds="add rm"
local_cmds="configure flash"
os_cmds="build-config configure download initialize versions"
util_cmds="available-drives"
COMPREPLY=()
cur=${COMP_WORDS[COMP_CWORD]}
prev=${COMP_WORDS[COMP_CWORD-1]}
if [ $COMP_CWORD -eq 1 ]
then
COMPREPLY=( $(compgen -W "${commands}" -- $cur) )
elif [ $COMP_CWORD -eq 2 ]
then
case "$prev" in
"app")
COMPREPLY=( $(compgen -W "$app_cmds" -- $cur) )
;;
"config")
COMPREPLY=( $(compgen -W "$config_cmds" -- $cur) )
;;
"device")
COMPREPLY=( $(compgen -W "$device_cmds" -- $cur) )
;;
"env")
COMPREPLY=( $(compgen -W "$env_cmds" -- $cur) )
;;
"key")
COMPREPLY=( $(compgen -W "$key_cmds" -- $cur) )
;;
"local")
COMPREPLY=( $(compgen -W "$local_cmds" -- $cur) )
;;
"os")
COMPREPLY=( $(compgen -W "$os_cmds" -- $cur) )
;;
"util")
COMPREPLY=( $(compgen -W "$util_cmds" -- $cur) )
;;
"*")
;;
esac
elif [ $COMP_CWORD -eq 3 ]
then
case "$prev" in
"public-url")
COMPREPLY=( $(compgen -W "$device_public_url_cmds" -- $cur) )
;;
"*")
;;
esac
fi
}
complete -F _balena_complete balena

12
bin/balena Executable file
View File

@ -0,0 +1,12 @@
#!/usr/bin/env node
// We boost the threadpool size as ext2fs can deadlock with some
// operations otherwise, if the pool runs out.
process.env.UV_THREADPOOL_SIZE = '64';
// Use fast-boot to cache require lookups, speeding up startup
require('fast-boot2').start({
cacheFile: __dirname + '/.fast-boot.json'
})
// Run the CLI
require('../build/app').run();

29
bin/balena-dev Executable file
View File

@ -0,0 +1,29 @@
#!/usr/bin/env node
// ****************************************************************************
// THIS IS FOR DEV PERROSES ONLY AND WILL NOT BE PART OF THE PUBLISHED PACKAGE
// Before opening a PR you should build and test your changes using bin/balena
// ****************************************************************************
// We boost the threadpool size as ext2fs can deadlock with some
// operations otherwise, if the pool runs out.
process.env.UV_THREADPOOL_SIZE = '64';
// Use fast-boot to cache require lookups, speeding up startup
require('fast-boot2').start({
cacheFile: '.fast-boot.json',
});
require('coffeescript/register');
const path = require('path');
const rootDir = path.join(__dirname, '..');
// Note: before ts-node v6.0.0, 'transpile-only' (no type checking) was the
// default option. We upgraded ts-node and found that adding 'transpile-only'
// was necessary to avoid a mysterious 'null' error message. On the plus side,
// it is supposed to run faster. We still benefit from type checking when
// running 'npm run build'.
require('ts-node').register({
project: path.join(rootDir, 'tsconfig.json'),
transpileOnly: true,
});
require('../lib/app').run();

View File

@ -1,2 +0,0 @@
#!/usr/bin/env node
require('../build/app');

View File

@ -1,115 +0,0 @@
# coffeelint: disable=max_line_length
module.exports =
title: 'Resin CLI Documentation'
introduction: '''
This tool allows you to interact with the resin.io api from the comfort of your command line.
Please make sure your system meets the requirements as specified in the [README](https://github.com/resin-io/resin-cli).
To get started download the CLI from npm.
$ npm install resin-cli -g
Then authenticate yourself:
$ resin login
Now you have access to all the commands referenced below.
## Proxy support
The CLI does support HTTP(S) proxies.
You can configure the proxy using several methods (in order of their precedence):
* set the `RESINRC_PROXY` environment variable in the URL format (with protocol, host, port, and optionally the basic auth),
* use the [resin config file](https://www.npmjs.com/package/resin-settings-client#documentation) (project-specific or user-level)
and set the `proxy` setting. This can be:
* a string in the URL format,
* or an object following [this format](https://www.npmjs.com/package/global-tunnel-ng#options), which allows more control,
* or set the conventional `https_proxy` / `HTTPS_PROXY` / `http_proxy` / `HTTP_PROXY`
environment variable (in the same standard URL format).
'''
categories: [
{
title: 'Application'
files: [ 'lib/actions/app.coffee' ]
},
{
title: 'Authentication',
files: [ 'lib/actions/auth.coffee' ]
},
{
title: 'Device',
files: [ 'lib/actions/device.coffee' ]
},
{
title: 'Environment Variables',
files: [ 'lib/actions/environment-variables.coffee' ]
},
{
title: 'Help',
files: [ 'lib/actions/help.coffee' ]
},
{
title: 'Information',
files: [ 'lib/actions/info.coffee' ]
},
{
title: 'Keys',
files: [ 'lib/actions/keys.coffee' ]
},
{
title: 'Logs',
files: [ 'lib/actions/logs.coffee' ]
},
{
title: 'Sync',
files: [ 'lib/actions/sync.coffee' ]
},
{
title: 'SSH',
files: [ 'lib/actions/ssh.coffee' ]
},
{
title: 'Notes',
files: [ 'lib/actions/notes.coffee' ]
},
{
title: 'OS',
files: [ 'lib/actions/os.coffee' ]
},
{
title: 'Config',
files: [ 'lib/actions/config.coffee' ]
},
{
title: 'Preload',
files: [ 'lib/actions/preload.coffee' ]
},
{
title: 'Settings',
files: [ 'lib/actions/settings.coffee' ]
},
{
title: 'Wizard',
files: [ 'lib/actions/wizard.coffee' ]
},
{
title: 'Local',
files: [ 'lib/actions/local/index.coffee' ]
},
{
title: 'Deploy',
files: [
'lib/actions/build.coffee'
'lib/actions/deploy.coffee'
]
},
{
title: 'Utilities',
files: [ 'lib/actions/util.coffee' ]
},
]

View File

@ -1,4 +1,4 @@
# Provisioning Resin.io devices in automated (non-interactive) mode
# Provisioning balena devices in automated (non-interactive) mode
This document describes how to run the `device init` command in non-interactive mode.
@ -7,7 +7,7 @@ It requires collecting some preliminary information _once_.
The final command to provision the device looks like this:
```bash
resin device init --app APP_ID --os-version OS_VERSION --drive DRIVE --config CONFIG_FILE --yes
balena device init --app APP_ID --os-version OS_VERSION --drive DRIVE --config CONFIG_FILE --yes
```
@ -20,15 +20,15 @@ But before you can run it you need to collect the parameters and build the confi
1. `DEVICE_TYPE`. Run
```bash
resin devices supported
balena devices supported
```
and find the _slug_ for your target device type, like _raspberrypi3_.
1. `APP_ID`. Create an application (`resin app create APP_NAME --type DEVICE_TYPE`) or find an existing one (`resin apps`) and notice its ID.
1. `APP_ID`. Create an application (`balena app create APP_NAME --type DEVICE_TYPE`) or find an existing one (`balena apps`) and notice its ID.
1. `OS_VERSION`. Run
```bash
resin os versions DEVICE_TYPE
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
@ -36,10 +36,10 @@ But before you can run it you need to collect the parameters and build the confi
1. `DRIVE`. Plug in your target medium (SD card or the USB stick, depending on your device type) and run
```bash
resin util available-drives
balena util available-drives
```
and get the drive name, like _/dev/sdb_ or _/dev/mmcblk0_.
The resin CLI will not display the system drives to protect you,
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.
@ -50,21 +50,21 @@ Interactive device provisioning process often includes collecting some extra dev
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 _./resin-os/raspberrypi3-config.json_.
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 _./resin-os/raspberrypi3-v2.0.6+rev1.prod.img_.
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
resin os download DEVICE_TYPE --output OS_IMAGE_PATH --version OS_VERSION
balena os download DEVICE_TYPE --output OS_IMAGE_PATH --version OS_VERSION
```
1. Now we're ready to build the config:
```bash
resin os build-config OS_IMAGE_PATH DEVICE_TYPE --output CONFIG_FILE
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:
@ -97,11 +97,11 @@ There are several ways to eliminate it and make the process fully non-interactiv
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 resin provisioning this can be fine, and also the simplest thing to do.
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 `resin` 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.
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
@ -109,4 +109,4 @@ As of June 2017 all the supported devices should not require any other interacti
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 resin CLI repository and the maintainers will add the necessary options to build the similar JSON config for this step.
If that is the case please raise the issue in the balena CLI repository and the maintainers will add the necessary options to build the similar JSON config for this step.

File diff suppressed because it is too large Load Diff

View File

@ -1,46 +0,0 @@
_ = require('lodash')
path = require('path')
capitanodoc = require('../../capitanodoc')
markdown = require('./markdown')
result = {}
result.title = capitanodoc.title
result.introduction = capitanodoc.introduction
result.categories = []
for commandCategory in capitanodoc.categories
category = {}
category.title = commandCategory.title
category.commands = []
for file in commandCategory.files
actions = require(path.join(process.cwd(), file))
if actions.signature?
category.commands.push(_.omit(actions, 'action'))
else
for actionName, actionCommand of actions
category.commands.push(_.omit(actionCommand, 'action'))
result.categories.push(category)
result.toc = _.cloneDeep(result.categories)
result.toc = _.map result.toc, (category) ->
category.commands = _.map category.commands, (command) ->
return {
signature: command.signature
anchor: '#' + command.signature
.replace(/\s/g,'-')
.replace(/</g, '60-')
.replace(/>/g, '-62-')
.replace(/\[/g, '')
.replace(/\]/g, '-')
.replace(/--/g, '-')
.replace(/\.\.\./g, '')
.replace(/\|/g, '')
.toLowerCase()
}
return category
console.log(markdown.display(result))

View File

@ -1,66 +0,0 @@
_ = require('lodash')
ent = require('ent')
utils = require('./utils')
exports.command = (command) ->
result = """
## #{ent.encode(command.signature)}
#{command.help}\n
"""
if not _.isEmpty(command.options)
result += '\n### Options'
for option in command.options
result += """
\n\n#### #{utils.parseSignature(option)}
#{option.description}
"""
result += '\n'
return result
exports.category = (category) ->
result = """
# #{category.title}\n
"""
for command in category.commands
result += '\n' + exports.command(command)
return result
exports.toc = (toc) ->
result = '''
# Table of contents\n
'''
for category in toc
result += """
\n- #{category.title}\n\n
"""
for command in category.commands
result += """
\t- [#{ent.encode(command.signature)}](#{command.anchor})\n
"""
return result
exports.display = (doc) ->
result = """
# #{doc.title}
#{doc.introduction}
#{exports.toc(doc.toc)}
"""
for category in doc.categories
result += '\n' + exports.category(category)
return result

View File

@ -1,26 +0,0 @@
_ = require('lodash')
ent = require('ent')
exports.getOptionPrefix = (signature) ->
if signature.length > 1
return '--'
else
return '-'
exports.getOptionSignature = (signature) ->
return "#{exports.getOptionPrefix(signature)}#{signature}"
exports.parseSignature = (option) ->
result = exports.getOptionSignature(option.signature)
if not _.isEmpty(option.alias)
if _.isString(option.alias)
result += ", #{exports.getOptionSignature(option.alias)}"
else
for alias in option.alias
result += ", #{exports.getOptionSignature(option.alias)}"
if option.parameter?
result += " <#{option.parameter}>"
return ent.encode(result)

View File

@ -1,19 +1,15 @@
path = require('path')
gulp = require('gulp')
coffee = require('gulp-coffee')
coffeelint = require('gulp-coffeelint')
inlinesource = require('gulp-inline-source')
mocha = require('gulp-mocha')
shell = require('gulp-shell')
packageJSON = require('./package.json')
OPTIONS =
config:
coffeelint: path.join(__dirname, 'coffeelint.json')
files:
coffee: [ 'lib/**/*.coffee', 'gulpfile.coffee' ]
app: 'lib/**/*.coffee'
tests: 'tests/**/*.spec.coffee'
tests: 'tests/**/*.spec.js'
pages: 'lib/auth/pages/*.ejs'
directories:
build: 'build/'
@ -23,28 +19,15 @@ gulp.task 'pages', ->
.pipe(inlinesource())
.pipe(gulp.dest('build/auth/pages'))
gulp.task 'coffee', [ 'lint' ], ->
gulp.task 'coffee', ->
gulp.src(OPTIONS.files.app)
.pipe(coffee(bare: true, header: true))
.pipe(gulp.dest(OPTIONS.directories.build))
gulp.task 'lint', ->
gulp.src(OPTIONS.files.coffee)
.pipe(coffeelint({
optFile: OPTIONS.config.coffeelint
}))
.pipe(coffeelint.reporter())
gulp.task 'test', ->
gulp.src(OPTIONS.files.tests, read: false)
.pipe(mocha({
reporter: 'min'
}))
gulp.task 'build', [
gulp.task 'build', gulp.series [
'coffee',
'pages'
]
gulp.task 'watch', [ 'build' ], ->
gulp.task 'watch', gulp.series [ 'build' ], ->
gulp.watch([ OPTIONS.files.coffee ], [ 'build' ])

138
lib/actions-oclif/env/add.ts vendored Normal file
View File

@ -0,0 +1,138 @@
/**
* @license
* Copyright 2019 Balena Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Command, flags } from '@oclif/command';
import { stripIndent } from 'common-tags';
import * as _ from 'lodash';
import * as cf from '../../utils/common-flags';
import { CommandHelp } from '../../utils/oclif-utils';
interface FlagsDef {
application?: string;
device?: string;
help: void;
quiet: boolean;
}
interface ArgsDef {
name: string;
value?: string;
}
export default class EnvAddCmd extends Command {
public static description = stripIndent`
Add an environment or config variable to an application or device.
Add an environment or config variable to an application or device, as selected
by the respective command-line options.
If VALUE is omitted, the CLI will attempt to use the value of the environment
variable of same name in the CLI process' environment. In this case, a warning
message will be printed. Use \`--quiet\` to suppress it.
Service-specific variables are not currently supported. The given command line
examples variables that apply to all services in an app or device.
`;
public static examples = [
'$ balena env add TERM --application MyApp',
'$ balena env add EDITOR vim --application MyApp',
'$ balena env add EDITOR vim --device 7cf02a6',
];
public static args = [
{
name: 'name',
required: true,
description: 'environment or config variable name',
},
{
name: 'value',
required: false,
description:
"variable value; if omitted, use value from CLI's environment",
},
];
// hardcoded 'env add' to avoid oclif's 'env:add' topic syntax
public static usage =
'env add ' + new CommandHelp({ args: EnvAddCmd.args }).defaultUsage();
public static flags: flags.Input<FlagsDef> = {
application: _.assign({ exclusive: ['device'] }, cf.application),
device: _.assign({ exclusive: ['application'] }, cf.device),
help: cf.help,
quiet: cf.quiet,
};
public async run() {
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
EnvAddCmd,
);
const cmd = this;
const balena = (await import('balena-sdk')).fromSharedOptions();
const { exitWithExpectedError } = await import('../../utils/patterns');
if (params.value == null) {
params.value = process.env[params.name];
if (params.value == null) {
throw new Error(
`Environment value not found for variable: ${params.name}`,
);
} else if (!options.quiet) {
cmd.warn(
`Using ${params.name}=${params.value} from CLI process environment`,
);
}
}
const reservedPrefixes = await getReservedPrefixes();
const isConfigVar = _.some(reservedPrefixes, prefix =>
_.startsWith(params.name, prefix),
);
const varType = isConfigVar ? 'configVar' : 'envVar';
if (options.application) {
await balena.models.application[varType].set(
options.application,
params.name,
params.value,
);
} else if (options.device) {
await balena.models.device[varType].set(
options.device,
params.name,
params.value,
);
} else {
exitWithExpectedError('You must specify an application or device');
}
}
}
async function getReservedPrefixes(): Promise<string[]> {
const balena = (await import('balena-sdk')).fromSharedOptions();
const settings = await balena.settings.getAll();
const response = await balena.request.send({
baseUrl: settings.apiUrl,
url: '/config/vars',
});
return response.body.reservedNamespaces;
}

96
lib/actions-oclif/env/rename.ts vendored Normal file
View File

@ -0,0 +1,96 @@
/**
* @license
* Copyright 2016-2019 Balena Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Command, flags } from '@oclif/command';
import { stripIndent } from 'common-tags';
import * as cf from '../../utils/common-flags';
import { CommandHelp } from '../../utils/oclif-utils';
type IArg<T> = import('@oclif/parser').args.IArg<T>;
interface FlagsDef {
device: boolean;
help: void;
}
interface ArgsDef {
id: number;
value: string;
}
export default class EnvRenameCmd extends Command {
public static description = stripIndent`
Change the value of an environment variable for an app or device.
Change the value of an environment variable for an application or device,
as selected by the '--device' option. The variable is identified by its
database ID, rather than its name. The 'balena envs' command can be used
to list the variable's ID.
Service-specific variables are not currently supported. The following
examples modify variables that apply to all services in an app or device.
`;
public static examples = [
'$ balena env rename 376 emacs',
'$ balena env rename 376 emacs --device',
];
public static args: Array<IArg<any>> = [
{
name: 'id',
required: true,
description: 'environment variable numeric database ID',
parse: input => parseInt(input, 10),
},
{
name: 'value',
required: true,
description:
"variable value; if omitted, use value from CLI's environment",
},
];
// hardcoded 'env add' to avoid oclif's 'env:add' topic syntax
public static usage =
'env rename ' + new CommandHelp({ args: EnvRenameCmd.args }).defaultUsage();
public static flags: flags.Input<FlagsDef> = {
device: flags.boolean({
char: 'd',
description:
'select a device variable instead of an application variable',
}),
help: cf.help,
};
public async run() {
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
EnvRenameCmd,
);
const balena = (await import('balena-sdk')).fromSharedOptions();
await balena.pine.patch({
resource: options.device
? 'device_environment_variable'
: 'application_environment_variable',
id: params.id,
body: {
value: params.value,
},
});
}
}

120
lib/actions-oclif/env/rm.ts vendored Normal file
View File

@ -0,0 +1,120 @@
/**
* @license
* Copyright 2019 Balena Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Command, flags } from '@oclif/command';
import { stripIndent } from 'common-tags';
import { CommandHelp } from '../../utils/oclif-utils';
interface FlagsDef {
config: boolean;
device: boolean;
yes: boolean;
}
interface ArgsDef {
id: number;
}
export default class EnvRmCmd extends Command {
public static description = stripIndent`
Remove an environment variable from an application or device.
Remove a configuration or environment variable from an application or device,
as selected by command-line options.
Note that this command asks for confirmation interactively.
You can avoid this by passing the \`--yes\` boolean option.
The --device option selects a device instead of an application.
The --config option selects a config var instead of an env var.
Service-specific variables are not currently supported. The following
examples remove variables that apply to all services in an app or device.
`;
public static examples = [
'$ balena env rm 215',
'$ balena env rm 215 --yes',
'$ balena env rm 215 --config',
'$ balena env rm 215 --device',
'$ balena env rm 215 --device --config',
];
public static args = [
{
name: 'id',
required: true,
description: 'environment variable numeric database ID',
},
];
// hardcoded 'env add' to avoid oclif's 'env:add' topic syntax
public static usage =
'env rm ' + new CommandHelp({ args: EnvRmCmd.args }).defaultUsage();
public static flags: flags.Input<FlagsDef> = {
device: flags.boolean({
char: 'd',
description:
'Selects a device environment variable instead of an application environment variable',
default: false,
}),
config: flags.boolean({
char: 'c',
description:
'Selects a configuration variable instead of an environment variable',
default: false,
}),
yes: flags.boolean({
char: 'y',
description: 'Run in non-interactive mode',
default: false,
}),
};
public async run() {
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
EnvRmCmd,
);
const balena = (await import('balena-sdk')).fromSharedOptions();
const patterns = await import('../../utils/patterns');
if (isNaN(params.id) || !Number.isInteger(Number(params.id))) {
patterns.exitWithExpectedError(
'The environment variable id must be an integer',
);
}
await patterns.confirm(
options.yes || false,
'Are you sure you want to delete the environment variable?',
undefined,
true,
);
await balena.pine.delete({
resource: options.device
? options.config
? 'device_config_variable'
: 'device_environment_variable'
: options.config
? 'application_config_variable'
: 'application_environment_variable',
id: params.id,
});
}
}

95
lib/actions-oclif/envs.ts Normal file
View File

@ -0,0 +1,95 @@
/**
* @license
* Copyright 2016-2019 Balena Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Command, flags } from '@oclif/command';
import { ApplicationVariable, DeviceVariable } from 'balena-sdk';
import { stripIndent } from 'common-tags';
import * as _ from 'lodash';
import * as cf from '../utils/common-flags';
import { CommandHelp } from '../utils/oclif-utils';
interface FlagsDef {
application?: string;
config: boolean;
device?: string;
help: void;
verbose: boolean;
}
export default class EnvsCmd extends Command {
public static description = stripIndent`
List the environment or config variables of an app or device.
List the environment or config variables of an application or device,
as selected by the respective command-line options.
The --config option is used to list "configuration variables" that
control balena features.
Service-specific variables are not currently supported. The following
examples list variables that apply to all services in an app or device.
`;
public static examples = [
'$ balena envs --application MyApp',
'$ balena envs --application MyApp --config',
'$ balena envs --device 7cf02a6',
];
public static usage = (
'envs ' + new CommandHelp({ args: EnvsCmd.args }).defaultUsage()
).trim();
public static flags: flags.Input<FlagsDef> = {
application: _.assign({ exclusive: ['device'] }, cf.application),
config: flags.boolean({
char: 'c',
description: 'show config variables',
}),
device: _.assign({ exclusive: ['application'] }, cf.device),
help: cf.help,
verbose: cf.verbose,
};
public async run() {
const { flags: options } = this.parse<FlagsDef, {}>(EnvsCmd);
const balena = (await import('balena-sdk')).fromSharedOptions();
const visuals = await import('resin-cli-visuals');
const { exitWithExpectedError } = await import('../utils/patterns');
const cmd = this;
let environmentVariables: ApplicationVariable[] | DeviceVariable[];
if (options.application) {
environmentVariables = await balena.models.application[
options.config ? 'configVar' : 'envVar'
].getAllByApplication(options.application);
} else if (options.device) {
environmentVariables = await balena.models.device[
options.config ? 'configVar' : 'envVar'
].getAllByDevice(options.device);
} else {
return exitWithExpectedError('You must specify an application or device');
}
if (_.isEmpty(environmentVariables)) {
return exitWithExpectedError('No environment variables found');
}
cmd.log(
visuals.table.horizontal(environmentVariables, ['id', 'name', 'value']),
);
}
}

View File

@ -0,0 +1,431 @@
/**
* @license
* Copyright 2019 Balena Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Command, flags } from '@oclif/command';
import BalenaSdk = require('balena-sdk');
import { stripIndent } from 'common-tags';
import * as _ from 'lodash';
import { ExpectedError } from '../../errors';
import * as cf from '../../utils/common-flags';
import { CommandHelp } from '../../utils/oclif-utils';
interface FlagsDef {
advanced?: boolean;
app?: string;
application?: string;
config?: string;
'config-app-update-poll-interval'?: number;
'config-network'?: string;
'config-wifi-key'?: string;
'config-wifi-ssid'?: string;
device?: string; // device UUID
'device-api-key'?: string;
'device-type'?: string;
help?: void;
version?: string;
}
interface ArgsDef {
image: string;
}
interface DeferredDevice extends BalenaSdk.Device {
belongs_to__application: BalenaSdk.PineDeferred;
}
interface Answers {
appUpdatePollInterval: number; // in minutes
deviceType: string; // e.g. "raspberrypi3"
network: 'ethernet' | 'wifi';
version: string; // e.g. "2.32.0+rev1"
wifiSsid?: string;
wifiKey?: string;
}
const deviceApiKeyDeprecationMsg = stripIndent`
The --device-api-key option is deprecated and will be removed in a future release.
A suitable key is automatically generated or fetched if this option is omitted.`;
export default class OsConfigureCmd extends Command {
public static description = stripIndent`
Configure a previously downloaded balenaOS image.
Configure a previously downloaded balenaOS image for a specific device type or
balena application.
Configuration settings such as WiFi authentication will be taken from the
following sources, in precedence order:
1. Command-line options like \`--config-wifi-ssid\`
2. A given \`config.json\` file specified with the \`--config\` option.
3. User input through interactive prompts (text menus).
The --device-type option may be used to override the application's default
device type, in case of an application with mixed device types.
${deviceApiKeyDeprecationMsg.split('\n').join('\n\t\t')}
`;
public static examples = [
'$ balena os configure ../path/rpi3.img --device 7cf02a6',
'$ balena os configure ../path/rpi3.img --device 7cf02a6 --device-api-key <existingDeviceKey>',
'$ balena os configure ../path/rpi3.img --app MyApp',
'$ balena os configure ../path/rpi3.img --app MyApp --version 2.12.7',
'$ balena os configure ../path/rpi3.img --app MyFinApp --device-type raspberrypi3',
'$ balena os configure ../path/rpi3.img --app MyFinApp --device-type raspberrypi3 --config myWifiConfig.json',
];
public static args = [
{
name: 'image',
required: true,
description: 'path to a balenaOS image file, e.g. "rpi3.img"',
},
];
// hardcoded 'os configure' to avoid oclif's 'os:configure' topic syntax
public static usage =
'os configure ' +
new CommandHelp({ args: OsConfigureCmd.args }).defaultUsage();
public static flags: flags.Input<FlagsDef> = {
advanced: flags.boolean({
char: 'v',
description:
'ask advanced configuration questions (when in interactive mode)',
}),
app: flags.string({
description: "same as '--application'",
exclusive: ['application', 'device'],
}),
application: _.assign({ exclusive: ['app', 'device'] }, cf.application),
config: flags.string({
description:
'path to a pre-generated config.json file to be injected in the OS image',
}),
'config-app-update-poll-interval': flags.integer({
description:
'interval (in minutes) for the on-device balena supervisor periodic app update check',
}),
'config-network': flags.string({
description: 'device network type (non-interactive configuration)',
options: ['ethernet', 'wifi'],
}),
'config-wifi-key': flags.string({
description: 'WiFi key (password) (non-interactive configuration)',
}),
'config-wifi-ssid': flags.string({
description: 'WiFi SSID (network name) (non-interactive configuration)',
}),
device: _.assign({ exclusive: ['app', 'application'] }, cf.device),
'device-api-key': flags.string({
char: 'k',
description:
'custom device API key (DEPRECATED and only supported with balenaOS 2.0.3+)',
}),
'device-type': flags.string({
description:
'device type slug (e.g. "raspberrypi3") to override the application device type',
}),
help: cf.help,
version: flags.string({
description: 'balenaOS version, for example "2.32.0" or "2.44.0+rev1"',
}),
};
public async run() {
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
OsConfigureCmd,
);
// Prefer options.application over options.app
options.application = options.application || options.app;
options.app = undefined;
await validateOptions(options);
const devInit = await import('balena-device-init');
const balena = (await import('balena-sdk')).fromSharedOptions();
const fs = await import('mz/fs');
const { generateDeviceConfig, generateApplicationConfig } = await import(
'../../utils/config'
);
const helpers = await import('../../utils/helpers');
let app: BalenaSdk.Application | undefined;
let device: BalenaSdk.Device | undefined;
let deviceTypeSlug: string;
if (options.device) {
device = await balena.models['device'].get(options.device);
deviceTypeSlug = device.device_type;
} else {
app = await balena.models['application'].get(options.application!);
await checkDeviceTypeCompatibility(balena, options, app);
deviceTypeSlug = options['device-type'] || app.device_type;
}
const deviceTypeManifest = await helpers.getManifest(
params.image,
deviceTypeSlug,
);
let configJson: import('../../utils/config').ImgConfig | undefined;
if (options.config) {
const rawConfig = await fs.readFile(options.config, 'utf8');
configJson = JSON.parse(rawConfig);
}
const answers: Answers = await askQuestionsForDeviceType(
deviceTypeManifest,
options,
configJson,
);
if (options.application) {
answers.deviceType = deviceTypeSlug;
}
answers.version =
options.version ||
(await getOsVersionFromImage(params.image, deviceTypeManifest, devInit));
if (_.isEmpty(configJson)) {
if (device) {
configJson = await generateDeviceConfig(
device as DeferredDevice,
options['device-api-key'],
answers,
);
} else {
configJson = await generateApplicationConfig(app!, answers);
}
}
console.info('Configuring operating system image');
await helpers.osProgressHandler(
await devInit.configure(
params.image,
deviceTypeManifest,
configJson || {},
answers,
),
);
}
}
async function validateOptions(options: FlagsDef) {
if (process.platform === 'win32') {
throw new ExpectedError(stripIndent`
Unsupported platform error: the 'balena os configure' command currently requires
the Windows Subsystem for Linux in order to run on Windows. It was tested with
the Ubuntu 18.04 distribution from the Microsoft Store. With WSL, a balena CLI
release for Linux (rather than Windows) should be installed: for example, the
standalone zip package for Linux. (It is possible to have both a Windows CLI
release and a Linux CLI release installed simultaneously.) For more information
on WSL and the balena CLI installation options, please check:
- https://docs.microsoft.com/en-us/windows/wsl/about
- https://github.com/balena-io/balena-cli/blob/master/INSTALL.md
`);
}
// The 'device' and 'application' options are declared "exclusive" in the oclif
// flag definitions above, so oclif will enforce that they are not both used together.
if (!options.device && !options.application) {
throw new ExpectedError(
"Either the '--device' or the '--application' option must be provided",
);
}
if (!options.application && options['device-type']) {
throw new ExpectedError(
"The '--device-type' option can only be used in conjunction with the '--application' option",
);
}
if (options['device-api-key']) {
console.error(stripIndent`
-------------------------------------------------------------------------------------------
Warning: ${deviceApiKeyDeprecationMsg.split('\n').join('\n\t\t\t')}
-------------------------------------------------------------------------------------------
`);
}
const { checkLoggedIn } = await import('../../utils/patterns');
await checkLoggedIn();
}
/**
* Wrapper around balena-device-init.getImageOsVersion(). Throws ExpectedError
* if the OS image could not be read or the OS version could not be extracted
* from it.
* @param imagePath Local filesystem path to a balenaOS image file
* @param deviceTypeManifest Device type manifest object
*/
async function getOsVersionFromImage(
imagePath: string,
deviceTypeManifest: BalenaSdk.DeviceType,
devInit: typeof import('balena-device-init'),
): Promise<string> {
const osVersion = await devInit.getImageOsVersion(
imagePath,
deviceTypeManifest,
);
if (!osVersion) {
throw new ExpectedError(stripIndent`
Could not read OS version from the image. Please specify the balenaOS
version manually with the --version command-line option.`);
}
return osVersion;
}
/**
* Check that options['device-type'], e.g. 'raspberrypi3', is compatible with
* app.device_type, e.g. 'raspberry-pi2'. Throws ExpectedError if they are not
* compatible.
* @param sdk Balena Node SDK instance
* @param options oclif command-line options object
* @param app Balena SDK Application model object
*/
async function checkDeviceTypeCompatibility(
sdk: BalenaSdk.BalenaSDK,
options: FlagsDef,
app: BalenaSdk.Application,
) {
if (options['device-type']) {
const [appDeviceType, optionDeviceType] = await Promise.all([
sdk.models.device.getManifestBySlug(app.device_type),
sdk.models.device.getManifestBySlug(options['device-type']),
]);
const helpers = await import('../../utils/helpers');
if (!helpers.areDeviceTypesCompatible(appDeviceType, optionDeviceType)) {
throw new ExpectedError(
`Device type ${
options['device-type']
} is incompatible with application ${options.application}`,
);
}
}
}
/**
* Check if the given options or configJson objects (in this order) contain
* the answers to some configuration questions, and interactively ask the
* user the questions for which answers are missing. Questions such as:
*
* ? Network Connection (Use arrow keys)
* ethernet
* wifi
* ? Network Connection wifi
* ? Wifi SSID i-ssid
* ? Wifi Passphrase [input is hidden]
*
* The questions are extracted from the given deviceType "manifest".
*/
async function askQuestionsForDeviceType(
deviceType: BalenaSdk.DeviceType,
options: FlagsDef,
configJson?: import('../../utils/config').ImgConfig,
): Promise<Answers> {
const form = await import('resin-cli-form');
const helpers = await import('../../utils/helpers');
const answerSources: any[] = [camelifyConfigOptions(options)];
const defaultAnswers: Partial<Answers> = {};
const questions: any = deviceType.options;
let extraOpts: { override: object } | undefined;
if (!_.isEmpty(configJson)) {
answerSources.push(configJson);
}
if (!options.advanced) {
const advancedGroup: any = _.find(questions, {
name: 'advanced',
isGroup: true,
});
if (!_.isEmpty(advancedGroup)) {
answerSources.push(helpers.getGroupDefaults(advancedGroup));
}
}
for (const questionName of getQuestionNames(deviceType)) {
for (const answerSource of answerSources) {
if (answerSource[questionName] != null) {
defaultAnswers[questionName] = answerSource[questionName];
break;
}
}
}
if (
!defaultAnswers.network &&
(defaultAnswers.wifiSsid || defaultAnswers.wifiKey)
) {
defaultAnswers.network = 'wifi';
}
if (!_.isEmpty(defaultAnswers)) {
extraOpts = { override: defaultAnswers };
}
return form.run(questions, extraOpts);
}
/**
* Given a deviceType "manifest" containing "options" properties, return an
* array of "question names" as in the following example.
*
* @param deviceType Device type "manifest", for example:
* { "slug": "raspberrypi3",
* "options": [{
* "options": [ {
* "name": "network",
* "choices": ["ethernet", "wifi"],
* ... }, {
* "name": "wifiSsid",
* "type": "text",
* ... }, {
* "options": [ {
* "name": "appUpdatePollInterval",
* "default": 10,
* ...
* @return Array of question names, for example:
* [ 'network', 'wifiSsid', 'wifiKey', 'appUpdatePollInterval' ]
*/
function getQuestionNames(
deviceType: BalenaSdk.DeviceType,
): Array<keyof Answers> {
const questionNames: string[] = _.chain(deviceType.options)
.flatMap(
(group: BalenaSdk.DeviceTypeOptions) =>
(group.isGroup && group.options) || [],
)
.map((groupOption: BalenaSdk.DeviceTypeOptionsGroup) => groupOption.name)
.filter()
.value();
return questionNames as Array<keyof Answers>;
}
/**
* Create and return a new object with the key-value pairs from the input object,
* renaming keys that start with the 'config-' prefix as follows:
* Sample input:
* { app: 'foo', 'config-wifi-key': 'mykey', 'config-wifi-ssid': 'myssid' }
* Output:
* { app: 'foo', wifiKey: 'mykey', wifiSsid: 'myssid' }
*/
function camelifyConfigOptions(options: FlagsDef): { [key: string]: any } {
return _.mapKeys(options, (_value, key) => {
if (key.startsWith('config-')) {
return key
.substring('config-'.length)
.replace(/-[a-z]/g, match => match.substring(1).toUpperCase());
}
return key;
});
}

View File

@ -0,0 +1,85 @@
/**
* @license
* Copyright 2019 Balena Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Command, flags } from '@oclif/command';
import { stripIndent } from 'common-tags';
interface FlagsDef {
all?: boolean;
json?: boolean;
help: void;
}
export interface JsonVersions {
'balena-cli': string;
'Node.js': string;
}
export default class VersionCmd extends Command {
public static description = stripIndent`
Display version information for the balena CLI and/or Node.js.
Display version information for the balena CLI and/or Node.js.
If you intend to parse the output, please use the -j option for
JSON output, as its format is more stable.
`;
public static examples = [
'$ balena version',
'$ balena version -a',
'$ balena version -j',
];
public static usage = 'version';
public static flags: flags.Input<FlagsDef> = {
all: flags.boolean({
char: 'a',
default: false,
description:
'include version information for additional components (Node.js)',
}),
json: flags.boolean({
char: 'j',
default: false,
description:
'output version information in JSON format for programmatic use',
}),
help: flags.help({ char: 'h' }),
};
public async run() {
const { flags: options } = this.parse<FlagsDef, {}>(VersionCmd);
const versions: JsonVersions = {
'balena-cli': (await import('../../package.json')).version,
'Node.js':
process.version && process.version.startsWith('v')
? process.version.slice(1)
: process.version,
};
if (options.json) {
console.log(JSON.stringify(versions, null, 4));
} else {
if (options.all) {
console.log(`balena-cli version "${versions['balena-cli']}"`);
console.log(`Node.js version "${versions['Node.js']}"`);
} else {
// backwards compatibility
console.log(versions['balena-cli']);
}
}
}
}

36
lib/actions/api-key.ts Normal file
View File

@ -0,0 +1,36 @@
import { CommandDefinition } from 'capitano';
import { stripIndent } from 'common-tags';
export const generate: CommandDefinition<{
name: string;
}> = {
signature: 'api-key generate <name>',
description: 'Generate a new API key with the given name',
help: stripIndent`
This command generates a new API key for the current user, with the given
name. The key will be logged to the console.
This key can be used to log into the CLI using 'balena login --token <key>',
or to authenticate requests to the API with an 'Authorization: Bearer <key>' header.
Examples:
$ balena api-key generate "Jenkins Key"
`,
async action(params, _options, done) {
const balena = (await import('balena-sdk')).fromSharedOptions();
balena.models.apiKey
.create(params.name)
.then(key => {
console.log(stripIndent`
Registered api key '${params.name}':
${key}
This key will not be shown again, so please save it now.
`);
})
.finally(done);
},
};

View File

@ -1,5 +1,5 @@
###
Copyright 2016-2017 Resin.io
Copyright 2016-2017 Balena
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -20,44 +20,48 @@ exports.create =
signature: 'app create <name>'
description: 'create an application'
help: '''
Use this command to create a new resin.io application.
Use this command to create a new balena application.
You can specify the application device type with the `--type` option.
Otherwise, an interactive dropdown will be shown for you to select from.
You can see a list of supported device types with
$ resin devices supported
$ balena devices supported
Examples:
$ resin app create MyApp
$ resin app create MyApp --type raspberry-pi
$ balena app create MyApp
$ balena app create MyApp --type raspberry-pi
'''
options: [
{
signature: 'type'
parameter: 'type'
description: 'application device type (Check available types with `resin devices supported`)'
description: 'application device type (Check available types with `balena devices supported`)'
alias: 't'
}
]
permission: 'user'
action: (params, options, done) ->
resin = require('resin-sdk-preconfigured')
balena = require('balena-sdk').fromSharedOptions()
patterns = require('../utils/patterns')
# Validate the the application name is available
# before asking the device type.
# https://github.com/resin-io/resin-cli/issues/30
resin.models.application.has(params.name).then (hasApplication) ->
# https://github.com/balena-io/balena-cli/issues/30
balena.models.application.has(params.name).then (hasApplication) ->
if hasApplication
throw new Error('You already have an application with that name!')
patterns.exitWithExpectedError('You already have an application with that name!')
.then ->
return options.type or patterns.selectDeviceType()
.then (deviceType) ->
return resin.models.application.create(params.name, deviceType)
return balena.models.application.create({
name: params.name
deviceType
})
.then (application) ->
console.info("Application created: #{application.app_name} (#{application.device_type}, id #{application.id})")
.nodeify(done)
@ -69,25 +73,37 @@ exports.list =
Use this command to list all your applications.
Notice this command only shows the most important bits of information for each app.
If you want detailed information, use resin app <name> instead.
If you want detailed information, use balena app <name> instead.
Examples:
$ resin apps
$ balena apps
'''
permission: 'user'
primary: true
action: (params, options, done) ->
resin = require('resin-sdk-preconfigured')
_ = require('lodash')
balena = require('balena-sdk').fromSharedOptions()
visuals = require('resin-cli-visuals')
resin.models.application.getAll().then (applications) ->
balena.models.application.getAll
$select: [
'id'
'app_name'
'device_type'
]
$expand: owns__device: $select: 'is_online'
.then (applications) ->
applications.forEach (application) ->
application.device_count = _.size(application.owns__device)
application.online_devices = _.filter(application.owns__device, (d) -> d.is_online == true).length
console.log visuals.table.horizontal applications, [
'id'
'app_name'
'device_type'
'online_devices'
'devices_length'
'device_count'
]
.nodeify(done)
@ -99,15 +115,15 @@ exports.info =
Examples:
$ resin app MyApp
$ balena app MyApp
'''
permission: 'user'
primary: true
action: (params, options, done) ->
resin = require('resin-sdk-preconfigured')
balena = require('balena-sdk').fromSharedOptions()
visuals = require('resin-cli-visuals')
resin.models.application.get(params.name).then (application) ->
balena.models.application.get(params.name).then (application) ->
console.log visuals.table.vertical application, [
"$#{application.app_name}$"
'id'
@ -125,33 +141,33 @@ exports.restart =
Examples:
$ resin app restart MyApp
$ balena app restart MyApp
'''
permission: 'user'
action: (params, options, done) ->
resin = require('resin-sdk-preconfigured')
resin.models.application.restart(params.name).nodeify(done)
balena = require('balena-sdk').fromSharedOptions()
balena.models.application.restart(params.name).nodeify(done)
exports.remove =
signature: 'app rm <name>'
description: 'remove an application'
help: '''
Use this command to remove a resin.io application.
Use this command to remove a balena application.
Notice this command asks for confirmation interactively.
You can avoid this by passing the `--yes` boolean option.
Examples:
$ resin app rm MyApp
$ resin app rm MyApp --yes
$ balena app rm MyApp
$ balena app rm MyApp --yes
'''
options: [ commandOptions.yes ]
permission: 'user'
action: (params, options, done) ->
resin = require('resin-sdk-preconfigured')
balena = require('balena-sdk').fromSharedOptions()
patterns = require('../utils/patterns')
patterns.confirm(options.yes, 'Are you sure you want to delete the application?').then ->
resin.models.application.remove(params.name)
balena.models.application.remove(params.name)
.nodeify(done)

View File

@ -1,5 +1,5 @@
###
Copyright 2016-2017 Resin.io
Copyright 2016-2017 Balena
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -16,9 +16,9 @@ limitations under the License.
exports.login =
signature: 'login'
description: 'login to resin.io'
description: 'login to balena'
help: '''
Use this command to login to your resin.io account.
Use this command to login to your balena account.
This command will prompt you to login using the following login types:
@ -27,20 +27,20 @@ exports.login =
- Credentials: using email/password and 2FA.
- Token: using the authentication token from the preferences page.
- Token: using a session token or API key from the preferences page.
Examples:
$ resin login
$ resin login --web
$ resin login --token "..."
$ resin login --credentials
$ resin login --credentials --email johndoe@gmail.com --password secret
$ balena login
$ balena login --web
$ balena login --token "..."
$ balena login --credentials
$ balena login --credentials --email johndoe@gmail.com --password secret
'''
options: [
{
signature: 'token'
description: 'auth token'
description: 'session token or API key'
parameter: 'token'
alias: 't'
}
@ -57,23 +57,23 @@ exports.login =
alias: 'c'
}
{
signature: 'email'
parameter: 'email'
description: 'email'
alias: [ 'e', 'u' ]
}
{
signature: 'password'
parameter: 'password'
description: 'password'
alias: 'p'
}
signature: 'email'
parameter: 'email'
description: 'email'
alias: [ 'e', 'u' ]
}
{
signature: 'password'
parameter: 'password'
description: 'password'
alias: 'p'
}
]
primary: true
action: (params, options, done) ->
_ = require('lodash')
Promise = require('bluebird')
resin = require('resin-sdk-preconfigured')
balena = require('balena-sdk').fromSharedOptions()
auth = require('../auth')
form = require('resin-cli-form')
patterns = require('../utils/patterns')
@ -84,10 +84,15 @@ exports.login =
return Promise.try ->
return options.token if _.isString(options.token)
return form.ask
message: 'Token (from the preferences page)'
message: 'Session token or API key from the preferences page'
name: 'token'
type: 'input'
.then(resin.auth.loginWithToken)
.then(balena.auth.loginWithToken)
.tap ->
balena.auth.whoami()
.then (username) ->
if !username
patterns.exitWithExpectedError('Token authentication failed')
else if options.credentials
return patterns.authenticate(options)
else if options.web
@ -97,24 +102,25 @@ exports.login =
return patterns.askLoginType().then (loginType) ->
if loginType is 'register'
capitanoRunAsync = Promise.promisify(require('capitano').run)
return capitanoRunAsync('signup')
signupUrl = 'https://dashboard.balena-cloud.com/signup'
require('opn')(signupUrl, { wait: false })
patterns.exitWithExpectedError("Please sign up at #{signupUrl}")
options[loginType] = true
return login(options)
resin.settings.get('resinUrl').then (resinUrl) ->
console.log(messages.resinAsciiArt)
console.log("\nLogging in to #{resinUrl}")
balena.settings.get('balenaUrl').then (balenaUrl) ->
console.log(messages.balenaAsciiArt)
console.log("\nLogging in to #{balenaUrl}")
return login(options)
.then(resin.auth.whoami)
.then(balena.auth.whoami)
.tap (username) ->
console.info("Successfully logged in as: #{username}")
console.info """
Find out about the available commands by running:
$ resin help
$ balena help
#{messages.reachingOut}
"""
@ -122,59 +128,17 @@ exports.login =
exports.logout =
signature: 'logout'
description: 'logout from resin.io'
description: 'logout from balena'
help: '''
Use this command to logout from your resin.io account.o
Use this command to logout from your balena account.
Examples:
$ resin logout
'''
permission: 'user'
action: (params, options, done) ->
resin = require('resin-sdk-preconfigured')
resin.auth.logout().nodeify(done)
exports.signup =
signature: 'signup'
description: 'signup to resin.io'
help: '''
Use this command to signup for a resin.io account.
If signup is successful, you'll be logged in to your new user automatically.
Examples:
$ resin signup
Email: johndoe@acme.com
Password: ***********
$ resin whoami
johndoe
$ balena logout
'''
action: (params, options, done) ->
resin = require('resin-sdk-preconfigured')
form = require('resin-cli-form')
validation = require('../utils/validation')
resin.settings.get('resinUrl').then (resinUrl) ->
console.log("\nRegistering to #{resinUrl}")
form.run [
message: 'Email:'
name: 'email'
type: 'input'
validate: validation.validateEmail
,
message: 'Password:'
name: 'password'
type: 'password',
validate: validation.validatePassword
]
.then(resin.auth.register)
.then(resin.auth.loginWithToken)
.nodeify(done)
balena = require('balena-sdk').fromSharedOptions()
balena.auth.logout().nodeify(done)
exports.whoami =
signature: 'whoami'
@ -184,18 +148,18 @@ exports.whoami =
Examples:
$ resin whoami
$ balena whoami
'''
permission: 'user'
action: (params, options, done) ->
Promise = require('bluebird')
resin = require('resin-sdk-preconfigured')
balena = require('balena-sdk').fromSharedOptions()
visuals = require('resin-cli-visuals')
Promise.props
username: resin.auth.whoami()
email: resin.auth.getEmail()
url: resin.settings.get('resinUrl')
username: balena.auth.whoami()
email: balena.auth.getEmail()
url: balena.settings.get('balenaUrl')
.then (results) ->
console.log visuals.table.vertical results, [
'$account information$'

View File

@ -2,42 +2,84 @@
# of this action
Promise = require('bluebird')
dockerUtils = require('../utils/docker')
compose = require('../utils/compose')
{ registrySecretsHelp } = require('../utils/messages')
getBundleInfo = Promise.method (options) ->
helpers = require('../utils/helpers')
###
Opts must be an object with the following keys:
if options.application?
# An application was provided
return helpers.getAppInfo(options.application)
.then (app) ->
return [app.arch, app.device_type]
else if options.arch? and options.deviceType?
return [options.arch, options.deviceType]
else
# No information, cannot do resolution
return undefined
app: the app this build is for (optional)
arch: the architecture to build for
deviceType: the device type to build for
buildEmulated
buildOpts: arguments to forward to docker build command
###
buildProject = (docker, logger, composeOpts, opts) ->
compose.loadProject(
logger
composeOpts.projectPath
composeOpts.projectName
undefined # image: name of pre-built image
composeOpts.dockerfilePath # ok if undefined
)
.then (project) ->
appType = opts.app?.application_type?[0]
if appType? and project.descriptors.length > 1 and not appType.supports_multicontainer
logger.logWarn(
'Target application does not support multiple containers.\n' +
'Continuing with build, but you will not be able to deploy.'
)
compose.buildProject(
docker
logger
project.path
project.name
project.composition
opts.arch
opts.deviceType
opts.buildEmulated
opts.buildOpts
composeOpts.inlineLogs
)
.then ->
logger.logSuccess('Build succeeded!')
.tapCatch (e) ->
logger.logError('Build failed')
module.exports =
signature: 'build [source]'
description: 'Build a container locally'
permission: 'user'
help: '''
Use this command to build a container with a provided docker daemon.
description: 'Build a single image or a multicontainer project locally'
primary: true
help: """
Use this command to build an image or a complete multicontainer project with
the provided docker daemon in your development machine or balena device.
(See also the `balena push` command for the option of building images in the
balenaCloud build servers.)
You must provide either an application or a device-type/architecture
pair to use the resin Dockerfile pre-processor
(e.g. Dockerfile.template -> Dockerfile).
You must provide either an application or a device-type/architecture pair to use
the balena Dockerfile pre-processor (e.g. Dockerfile.template -> Dockerfile).
This command will look into the given source directory (or the current working
directory if one isn't specified) for a docker-compose.yml file. If it is found,
this command will build each service defined in the compose file. If a compose
file isn't found, the command will look for a Dockerfile[.template] file (or
alternative Dockerfile specified with the `-f` option), and if yet that isn't
found, it will try to generate one.
#{registrySecretsHelp}
Examples:
$ resin build
$ resin build ./source/
$ resin build --deviceType raspberrypi3 --arch armhf
$ resin build --application MyApp ./source/
$ resin build --docker '/var/run/docker.sock'
$ resin build --dockerHost my.docker.host --dockerPort 2376 --ca ca.pem --key key.pem --cert cert.pem
'''
options: dockerUtils.appendOptions [
$ balena build
$ balena build ./source/
$ balena build --deviceType raspberrypi3 --arch armv7hf --emulated
$ balena build --application MyApp ./source/
$ balena build --docker /var/run/docker.sock # Linux, Mac
$ balena build --docker //./pipe/docker_engine # Windows
$ balena build --dockerHost my.docker.host --dockerPort 2376 --ca ca.pem --key key.pem --cert cert.pem
"""
options: dockerUtils.appendOptions compose.appendOptions [
{
signature: 'arch'
parameter: 'arch'
@ -53,12 +95,61 @@ module.exports =
{
signature: 'application'
parameter: 'application'
description: 'The target resin.io application this build is for'
description: 'The target balena application this build is for'
alias: 'a'
},
]
action: (params, options, done) ->
Logger = require('../utils/logger')
dockerUtils.runBuild(params, options, getBundleInfo, new Logger())
.asCallback(done)
# compositions with many services trigger misleading warnings
require('events').defaultMaxListeners = 1000
sdk = (require('balena-sdk')).fromSharedOptions()
{ validateComposeOptions } = require('../utils/compose_ts')
{ exitWithExpectedError } = require('../utils/patterns')
helpers = require('../utils/helpers')
Logger = require('../utils/logger')
logger = Logger.getLogger()
logger.logDebug('Parsing input...')
# `build` accepts `[source]` as a parameter, but compose expects it
# as an option. swap them here
options.source ?= params.source
delete params.source
Promise.resolve(validateComposeOptions(sdk, options))
.then ->
{ application, arch, deviceType } = options
if (not (arch? and deviceType?) and not application?) or (application? and (arch? or deviceType?))
exitWithExpectedError('You must specify either an application or an arch/deviceType pair to build for')
if arch? and deviceType?
[ undefined, arch, deviceType ]
else
Promise.join(
helpers.getApplication(application)
helpers.getArchAndDeviceType(application)
(app, { arch, device_type }) ->
app.arch = arch
app.device_type = device_type
return app
)
.then (app) ->
[ app, app.arch, app.device_type ]
.then ([ app, arch, deviceType ]) ->
Promise.join(
dockerUtils.getDocker(options)
dockerUtils.generateBuildOpts(options)
compose.generateOpts(options)
(docker, buildOpts, composeOpts) ->
buildProject(docker, logger, composeOpts, {
app
arch
deviceType
buildEmulated: !!options.emulated
buildOpts
})
)
.asCallback(done)

View File

@ -1,100 +0,0 @@
###
Copyright 2016-2017 Resin.io
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.
###
_ = require('lodash')
exports.yes =
signature: 'yes'
description: 'confirm non interactively'
boolean: true
alias: 'y'
exports.optionalApplication =
signature: 'application'
parameter: 'application'
description: 'application name'
alias: [ 'a', 'app' ]
exports.application = _.defaults
required: 'You have to specify an application'
, exports.optionalApplication
exports.optionalDevice =
signature: 'device'
parameter: 'device'
description: 'device uuid'
alias: 'd'
exports.optionalDeviceApiKey =
signature: 'deviceApiKey'
description: 'custom device key - note that this is only supported on ResinOS 2.0.3+'
parameter: 'device-api-key'
alias: 'k'
exports.booleanDevice =
signature: 'device'
description: 'device'
boolean: true
alias: 'd'
exports.osVersion =
signature: 'version'
description: """
exact version number, or a valid semver range,
or 'latest' (includes pre-releases),
or 'default' (excludes pre-releases if at least one stable version is available),
or 'recommended' (excludes pre-releases, will fail if only pre-release versions are available),
or 'menu' (will show the interactive menu)
"""
parameter: 'version'
exports.network =
signature: 'network'
parameter: 'network'
description: 'network type'
alias: 'n'
exports.wifiSsid =
signature: 'ssid'
parameter: 'ssid'
description: 'wifi ssid, if network is wifi'
alias: 's'
exports.wifiKey =
signature: 'key'
parameter: 'key'
description: 'wifi key, if network is wifi'
alias: 'k'
exports.forceUpdateLock =
signature: 'force'
description: 'force action if the update lock is set'
boolean: true
alias: 'f'
exports.drive =
signature: 'drive'
description: 'the drive to write the image to, like `/dev/sdb` or `/dev/mmcblk0`.
Careful with this as you can erase your hard drive.
Check `resin util available-drives` for available options.'
parameter: 'drive'
alias: 'd'
exports.advancedConfig =
signature: 'advanced'
description: 'show advanced configuration options'
boolean: true
alias: 'v'

View File

@ -0,0 +1,157 @@
/*
Copyright 2016-2017 Balena
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import _ = require('lodash');
export const yes = {
signature: 'yes',
description: 'confirm non interactively',
boolean: true,
alias: 'y',
};
export interface YesOption {
yes: boolean;
}
export const optionalApplication = {
signature: 'application',
parameter: 'application',
description: 'application name',
alias: ['a', 'app'],
};
export const application = _.defaults(
{ required: 'You have to specify an application' },
optionalApplication,
);
export const optionalRelease = {
signature: 'release',
parameter: 'release',
description: 'release id',
alias: 'r',
};
export const optionalDevice = {
signature: 'device',
parameter: 'device',
description: 'device uuid',
alias: 'd',
};
export const optionalDeviceApiKey = {
signature: 'deviceApiKey',
description:
'custom device key - note that this is only supported on balenaOS 2.0.3+',
parameter: 'device-api-key',
alias: 'k',
};
export const optionalDeviceType = {
signature: 'deviceType',
description: 'device type slug',
parameter: 'device-type',
};
export const optionalOsVersion = {
signature: 'version',
description: 'a balenaOS version',
parameter: 'version',
};
export type OptionalOsVersionOption = Partial<OsVersionOption>;
export const osVersion = _.defaults(
{
required: 'You have to specify an exact os version',
},
exports.optionalOsVersion,
);
export interface OsVersionOption {
version?: string;
}
export const booleanDevice = {
signature: 'device',
description: 'device',
boolean: true,
alias: 'd',
};
export const osVersionOrSemver = {
signature: 'version',
description: `\
exact version number, or a valid semver range,
or 'latest' (includes pre-releases),
or 'default' (excludes pre-releases if at least one stable version is available),
or 'recommended' (excludes pre-releases, will fail if only pre-release versions are available),
or 'menu' (will show the interactive menu)\
`,
parameter: 'version',
};
export const network = {
signature: 'network',
parameter: 'network',
description: 'network type',
alias: 'n',
};
export const wifiSsid = {
signature: 'ssid',
parameter: 'ssid',
description: 'wifi ssid, if network is wifi',
alias: 's',
};
export const wifiKey = {
signature: 'key',
parameter: 'key',
description: 'wifi key, if network is wifi',
alias: 'k',
};
export const forceUpdateLock = {
signature: 'force',
description: 'force action if the update lock is set',
boolean: true,
alias: 'f',
};
export const drive = {
signature: 'drive',
description: `the drive to write the image to, like \`/dev/sdb\` or \`/dev/mmcblk0\`. \
Careful with this as you can erase your hard drive. \
Check \`balena util available-drives\` for available options.`,
parameter: 'drive',
alias: 'd',
};
export const advancedConfig = {
signature: 'advanced',
description: 'show advanced configuration options',
boolean: true,
alias: 'v',
};
export const hostOSAccess = {
signature: 'host',
boolean: true,
description: 'access host OS (for devices with balenaOS >= 2.0.0+rev1)',
alias: 's',
};

View File

@ -1,5 +1,5 @@
###
Copyright 2016-2017 Resin.io
Copyright 2016-2018 Balena Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -15,6 +15,7 @@ limitations under the License.
###
commandOptions = require('./command-options')
{ normalizeUuidProp } = require('../utils/normalization')
exports.read =
signature: 'config read'
@ -24,13 +25,13 @@ exports.read =
Examples:
$ resin config read --type raspberry-pi
$ resin config read --type raspberry-pi --drive /dev/disk2
$ balena config read --type raspberry-pi
$ balena config read --type raspberry-pi --drive /dev/disk2
'''
options: [
{
signature: 'type'
description: 'device type (Check available types with `resin devices supported`)'
description: 'device type (Check available types with `balena devices supported`)'
parameter: 'type'
alias: 't'
required: 'You have to specify a device type'
@ -46,7 +47,7 @@ exports.read =
root: true
action: (params, options, done) ->
Promise = require('bluebird')
config = require('resin-config-json')
config = require('balena-config-json')
visuals = require('resin-cli-visuals')
umountAsync = Promise.promisify(require('umount').umount)
prettyjson = require('prettyjson')
@ -68,14 +69,14 @@ exports.write =
Examples:
$ resin config write --type raspberry-pi username johndoe
$ resin config write --type raspberry-pi --drive /dev/disk2 username johndoe
$ resin config write --type raspberry-pi files.network/settings "..."
$ balena config write --type raspberry-pi username johndoe
$ balena config write --type raspberry-pi --drive /dev/disk2 username johndoe
$ balena config write --type raspberry-pi files.network/settings "..."
'''
options: [
{
signature: 'type'
description: 'device type (Check available types with `resin devices supported`)'
description: 'device type (Check available types with `balena devices supported`)'
parameter: 'type'
alias: 't'
required: 'You have to specify a device type'
@ -92,7 +93,7 @@ exports.write =
action: (params, options, done) ->
Promise = require('bluebird')
_ = require('lodash')
config = require('resin-config-json')
config = require('balena-config-json')
visuals = require('resin-cli-visuals')
umountAsync = Promise.promisify(require('umount').umount)
@ -116,17 +117,18 @@ exports.inject =
signature: 'config inject <file>'
description: 'inject a device configuration file'
help: '''
Use this command to inject a config.json file to the mounted filesystem (e.g. SD card) of a provisioned device"
Use this command to inject a config.json file to the mounted filesystem
(e.g. SD card or mounted balenaOS image) of a provisioned device"
Examples:
$ resin config inject my/config.json --type raspberry-pi
$ resin config inject my/config.json --type raspberry-pi --drive /dev/disk2
$ balena config inject my/config.json --type raspberry-pi
$ balena config inject my/config.json --type raspberry-pi --drive /dev/disk2
'''
options: [
{
signature: 'type'
description: 'device type (Check available types with `resin devices supported`)'
description: 'device type (Check available types with `balena devices supported`)'
parameter: 'type'
alias: 't'
required: 'You have to specify a device type'
@ -142,7 +144,7 @@ exports.inject =
root: true
action: (params, options, done) ->
Promise = require('bluebird')
config = require('resin-config-json')
config = require('balena-config-json')
visuals = require('resin-cli-visuals')
umountAsync = Promise.promisify(require('umount').umount)
readFileAsync = Promise.promisify(require('fs').readFile)
@ -165,14 +167,14 @@ exports.reconfigure =
Examples:
$ resin config reconfigure --type raspberry-pi
$ resin config reconfigure --type raspberry-pi --advanced
$ resin config reconfigure --type raspberry-pi --drive /dev/disk2
$ balena config reconfigure --type raspberry-pi
$ balena config reconfigure --type raspberry-pi --advanced
$ balena config reconfigure --type raspberry-pi --drive /dev/disk2
'''
options: [
{
signature: 'type'
description: 'device type (Check available types with `resin devices supported`)'
description: 'device type (Check available types with `balena devices supported`)'
parameter: 'type'
alias: 't'
required: 'You have to specify a device type'
@ -194,9 +196,9 @@ exports.reconfigure =
root: true
action: (params, options, done) ->
Promise = require('bluebird')
config = require('resin-config-json')
config = require('balena-config-json')
visuals = require('resin-cli-visuals')
capitanoRunAsync = Promise.promisify(require('capitano').run)
{ runCommand } = require('../utils/helpers')
umountAsync = Promise.promisify(require('umount').umount)
Promise.try ->
@ -207,10 +209,10 @@ exports.reconfigure =
.tap ->
umountAsync(drive)
.then (uuid) ->
configureCommand = "os configure #{drive} #{uuid}"
configureCommand = "os configure #{drive} --device #{uuid}"
if options.advanced
configureCommand += ' --advanced'
return capitanoRunAsync(configureCommand)
return runCommand(configureCommand)
.then ->
console.info('Done')
.nodeify(done)
@ -221,23 +223,38 @@ exports.generate =
help: '''
Use this command to generate a config.json for a device or application.
Calling this command with the exact version number of the targeted image is required.
This is interactive by default, but you can do this automatically without interactivity
by specifying an option for each question on the command line, if you know the questions
that will be asked for the relevant device type.
In case that you want to configure an image for an application with mixed device types,
you can pass the --device-type argument along with --app to specify the target device type.
Examples:
$ resin config generate --device 7cf02a6
$ resin config generate --device 7cf02a6 --device-api-key <existingDeviceKey>
$ resin config generate --device 7cf02a6 --output config.json
$ resin config generate --app MyApp
$ resin config generate --app MyApp --output config.json
$ resin config generate --app MyApp --network wifi --wifiSsid mySsid --wifiKey abcdefgh --appUpdatePollInterval 1
$ balena config generate --device 7cf02a6 --version 2.12.7
$ balena config generate --device 7cf02a6 --version 2.12.7 --generate-device-api-key
$ balena config generate --device 7cf02a6 --version 2.12.7 --device-api-key <existingDeviceKey>
$ balena config generate --device 7cf02a6 --version 2.12.7 --output config.json
$ balena config generate --app MyApp --version 2.12.7
$ balena config generate --app MyApp --version 2.12.7 --device-type fincm3
$ balena config generate --app MyApp --version 2.12.7 --output config.json
$ balena config generate --app MyApp --version 2.12.7 \
--network wifi --wifiSsid mySsid --wifiKey abcdefgh --appUpdatePollInterval 1
'''
options: [
commandOptions.osVersion
commandOptions.optionalApplication
commandOptions.optionalDevice
commandOptions.optionalDeviceApiKey
commandOptions.optionalDeviceType
{
signature: 'generate-device-api-key'
description: 'generate a fresh device key for the device'
boolean: true
}
{
signature: 'output'
description: 'output'
@ -268,42 +285,71 @@ exports.generate =
]
permission: 'user'
action: (params, options, done) ->
normalizeUuidProp(options, 'device')
Promise = require('bluebird')
writeFileAsync = Promise.promisify(require('fs').writeFile)
resin = require('resin-sdk-preconfigured')
_ = require('lodash')
balena = require('balena-sdk').fromSharedOptions()
form = require('resin-cli-form')
deviceConfig = require('resin-device-config')
prettyjson = require('prettyjson')
{ generateDeviceConfig, generateApplicationConfig } = require('../utils/config')
helpers = require('../utils/helpers')
{ exitWithExpectedError } = require('../utils/patterns')
if not options.device? and not options.application?
throw new Error '''
exitWithExpectedError '''
You have to pass either a device or an application.
See the help page for examples:
$ resin help config generate
$ balena help config generate
'''
if !options.application and options.deviceType
exitWithExpectedError '''
Specifying a different device type is only supported when
generating a config for an application:
* An application, with --app <appname>
* A specific device type, with --device-type <deviceTypeSlug>
See the help page for examples:
$ balena help config generate
'''
Promise.try ->
if options.device?
return resin.models.device.get(options.device)
return resin.models.application.get(options.application)
return balena.models.device.get(options.device)
return balena.models.application.get(options.application)
.then (resource) ->
resin.models.device.getManifestBySlug(resource.device_type)
.get('options')
deviceType = options.deviceType || resource.device_type
manifestPromise = balena.models.device.getManifestBySlug(deviceType)
if options.application && options.deviceType
app = resource
appManifestPromise = balena.models.device.getManifestBySlug(app.device_type)
manifestPromise = manifestPromise.tap (paramDeviceType) ->
appManifestPromise.then (appDeviceType) ->
if not helpers.areDeviceTypesCompatible(appDeviceType, paramDeviceType)
throw new balena.errors.BalenaInvalidDeviceType(
"Device type #{options.deviceType} is incompatible with application #{options.application}"
)
manifestPromise.get('options')
.then (formOptions) ->
# Pass params as an override: if there is any param with exactly the same name as a
# required option, that value is used (and the corresponding question is not asked)
form.run(formOptions, override: options)
.then (answers) ->
answers.version = options.version
if resource.uuid?
generateDeviceConfig(resource, options.deviceApiKey, answers)
generateDeviceConfig(resource, options.deviceApiKey || options['generate-device-api-key'], answers)
else
answers.deviceType = deviceType
generateApplicationConfig(resource, answers)
.then (config) ->
deviceConfig.validate(config)
if options.output?
return writeFileAsync(options.output, JSON.stringify(config))

View File

@ -1,145 +1,176 @@
# Imported here because it's needed for the setup
# of this action
Promise = require('bluebird')
dockerUtils = require('../utils/docker')
compose = require('../utils/compose')
{ registrySecretsHelp } = require('../utils/messages')
getBuilderPushEndpoint = (baseUrl, owner, app) ->
querystring = require('querystring')
args = querystring.stringify({ owner, app })
"https://builder.#{baseUrl}/v1/push?#{args}"
###
Opts must be an object with the following keys:
getBuilderLogPushEndpoint = (baseUrl, buildId, owner, app) ->
querystring = require('querystring')
args = querystring.stringify({ owner, app, buildId })
"https://builder.#{baseUrl}/v1/pushLogs?#{args}"
app: the application instance to deploy to
image: the image to deploy; optional
dockerfilePath: name of an alternative Dockerfile; optional
shouldPerformBuild
shouldUploadLogs
buildEmulated
buildOpts: arguments to forward to docker build command
###
deployProject = (docker, logger, composeOpts, opts) ->
_ = require('lodash')
doodles = require('resin-doodles')
sdk = require('balena-sdk').fromSharedOptions()
formatImageName = (image) ->
image.split('/').pop()
compose.loadProject(
logger
composeOpts.projectPath
composeOpts.projectName
opts.image
composeOpts.dockerfilePath # ok if undefined
)
.then (project) ->
if project.descriptors.length > 1 and !opts.app.application_type?[0]?.supports_multicontainer
throw new Error('Target application does not support multiple containers. Aborting!')
parseInput = Promise.method (params, options) ->
if not params.appName?
throw new Error('Need an application to deploy to!')
appName = params.appName
image = undefined
if params.image?
if options.build or options.source?
throw new Error('Build and source parameters are not applicable when specifying an image')
options.build = false
image = params.image
else if options.build
source = options.source || '.'
else
throw new Error('Need either an image or a build flag!')
# find which services use images that already exist locally
Promise.map project.descriptors, (d) ->
# unconditionally build (or pull) if explicitly requested
return d if opts.shouldPerformBuild
docker.getImage(d.image.tag ? d.image).inspect()
.return(d.serviceName)
.catchReturn()
.filter (d) -> !!d
.then (servicesToSkip) ->
# multibuild takes in a composition and always attempts to
# build or pull all services. we workaround that here by
# passing a modified composition.
compositionToBuild = _.cloneDeep(project.composition)
compositionToBuild.services = _.omit(compositionToBuild.services, servicesToSkip)
if _.size(compositionToBuild.services) is 0
logger.logInfo('Everything is up to date (use --build to force a rebuild)')
return {}
compose.buildProject(
docker
logger
project.path
project.name
compositionToBuild
opts.app.arch
opts.app.device_type
opts.buildEmulated
opts.buildOpts
composeOpts.inlineLogs
)
.then (builtImages) ->
_.keyBy(builtImages, 'serviceName')
.then (builtImages) ->
project.descriptors.map (d) ->
builtImages[d.serviceName] ? {
serviceName: d.serviceName,
name: d.image.tag ? d.image
logs: 'Build skipped; image for service already exists.'
props: {}
}
.then (images) ->
if opts.app.application_type?[0]?.is_legacy
chalk = require('chalk')
legacyDeploy = require('../utils/deploy-legacy')
return [appName, options.build, source, image]
msg = chalk.yellow('Target application requires legacy deploy method.')
logger.logWarn(msg)
showPushProgress = (message) ->
visuals = require('resin-cli-visuals')
progressBar = new visuals.Progress(message)
progressBar.update({ percentage: 0 })
return progressBar
getBundleInfo = (options) ->
helpers = require('../utils/helpers')
helpers.getAppInfo(options.appName)
.then (app) ->
[app.arch, app.device_type]
performUpload = (imageStream, token, username, url, appName, logger) ->
request = require('request')
progressStream = require('progress-stream')
zlib = require('zlib')
# Need to strip off the newline
progressMessage = logger.formatMessage('info', 'Deploying').slice(0, -1)
progressBar = showPushProgress(progressMessage)
streamWithProgress = imageStream.pipe progressStream
time: 500,
length: imageStream.length
, ({ percentage, eta }) ->
progressBar.update
percentage: Math.min(percentage, 100)
eta: eta
uploadRequest = request.post
url: getBuilderPushEndpoint(url, username, appName)
headers:
'Content-Encoding': 'gzip'
auth:
bearer: token
body: streamWithProgress.pipe(zlib.createGzip({
level: 6
}))
uploadToPromise(uploadRequest, logger)
uploadLogs = (logs, token, url, buildId, username, appName) ->
request = require('request')
request.post
json: true
url: getBuilderLogPushEndpoint(url, buildId, username, appName)
auth:
bearer: token
body: Buffer.from(logs)
uploadToPromise = (uploadRequest, logger) ->
new Promise (resolve, reject) ->
handleMessage = (data) ->
data = data.toString()
logger.logDebug("Received data: #{data}")
try
obj = JSON.parse(data)
catch e
logger.logError('Error parsing reply from remote side')
reject(e)
return
if obj.type?
switch obj.type
when 'error' then reject(new Error("Remote error: #{obj.error}"))
when 'success' then resolve(obj)
when 'status' then logger.logInfo("Remote: #{obj.message}")
else reject(new Error("Received unexpected reply from remote: #{data}"))
else
reject(new Error("Received unexpected reply from remote: #{data}"))
uploadRequest
.on('error', reject)
.on('data', handleMessage)
return Promise.join(
docker
logger
sdk.auth.getToken()
sdk.auth.whoami()
sdk.settings.get('balenaUrl')
{
# opts.appName may be prefixed by 'owner/', unlike opts.app.app_name
appName: opts.appName
imageName: images[0].name
buildLogs: images[0].logs
shouldUploadLogs: opts.shouldUploadLogs
}
legacyDeploy
)
.then (releaseId) ->
sdk.models.release.get(releaseId, $select: [ 'commit' ])
Promise.join(
sdk.auth.getUserId()
sdk.auth.getToken()
sdk.settings.get('apiUrl')
(userId, auth, apiEndpoint) ->
compose.deployProject(
docker
logger
project.composition
images
opts.app.id
userId
"Bearer #{auth}"
apiEndpoint
!opts.shouldUploadLogs
)
)
.then (release) ->
logger.logSuccess('Deploy succeeded!')
logger.logSuccess("Release: #{release.commit}")
console.log()
console.log(doodles.getDoodle()) # Show charlie
console.log()
.tapCatch (e) ->
logger.logError('Deploy failed')
module.exports =
signature: 'deploy <appName> [image]'
description: 'Deploy an image to a resin.io application'
help: '''
Use this command to deploy an image to an application, optionally building it first.
description: 'Deploy a single image or a multicontainer project to a balena application'
help: """
Usage: `deploy <appName> ([image] | --build [--source build-dir])`
To deploy to an app on which you're a collaborator, use
`resin deploy <appOwnerUsername>/<appName>`.
Use this command to deploy an image or a complete multicontainer project to an
application, optionally building it first. The source images are searched for
(and optionally built) using the docker daemon in your development machine or
balena device. (See also the `balena push` command for the option of building
the image in the balenaCloud build servers.)
Note: If building with this command, all options supported by `resin build`
are also supported with this command.
Unless an image is specified, this command will look into the current directory
(or the one specified by --source) for a docker-compose.yml file. If one is
found, this command will deploy each service defined in the compose file,
building it first if an image for it doesn't exist. If a compose file isn't
found, the command will look for a Dockerfile[.template] file (or alternative
Dockerfile specified with the `-f` option), and if yet that isn't found, it
will try to generate one.
To deploy to an app on which you're a collaborator, use
`balena deploy <appOwnerUsername>/<appName>`.
When --build is used, all options supported by `balena build` are also supported
by this command.
#{registrySecretsHelp}
Examples:
$ resin deploy myApp --build --source myBuildDir/
$ resin deploy myApp myApp/myImage
'''
$ balena deploy myApp
$ balena deploy myApp --build --source myBuildDir/
$ balena deploy myApp myApp/myImage
"""
permission: 'user'
options: dockerUtils.appendOptions [
{
signature: 'build'
boolean: true
description: 'Build image then deploy'
alias: 'b'
},
primary: true
options: dockerUtils.appendOptions compose.appendOptions [
{
signature: 'source'
parameter: 'source'
description: 'The source directory to use when building the image'
description: 'Specify an alternate source directory; default is the working directory'
alias: 's'
},
{
signature: 'build'
boolean: true
description: 'Force a rebuild before deploy'
alias: 'b'
},
{
signature: 'nologupload'
description: "Don't upload build logs to the dashboard with image (if building)"
@ -147,83 +178,57 @@ module.exports =
}
]
action: (params, options, done) ->
_ = require('lodash')
tmp = require('tmp')
tmpNameAsync = Promise.promisify(tmp.tmpName)
resin = require('resin-sdk-preconfigured')
# compositions with many services trigger misleading warnings
require('events').defaultMaxListeners = 1000
sdk = (require('balena-sdk')).fromSharedOptions()
{ validateComposeOptions } = require('../utils/compose_ts')
helpers = require('../utils/helpers')
Logger = require('../utils/logger')
logger = new Logger()
# Ensure the tmp files gets deleted
tmp.setGracefulCleanup()
logger = Logger.getLogger()
logger.logDebug('Parsing input...')
logs = ''
# when Capitano converts a positional parameter (but not an option)
# to a number, the original value is preserved with the _raw suffix
{ appName, appName_raw, image } = params
upload = (token, username, url) ->
dockerUtils.getDocker(options)
.then (docker) ->
# Check input parameters
parseInput(params, options)
.then ([appName, build, source, imageName]) ->
tmpNameAsync()
.then (bufferFile) ->
# look into "balena build" options if appName isn't given
appName = appName_raw || appName || options.application
delete options.application
# Setup the build args for how the build routine expects them
options = _.assign({}, options, { appName })
params = _.assign({}, params, { source })
Promise.resolve(validateComposeOptions(sdk, options))
.then ->
if not appName?
throw new Error('Please specify the name of the application to deploy')
Promise.try ->
if build
dockerUtils.runBuild(params, options, getBundleInfo, logger)
else
{ image: imageName, log: '' }
.then ({ image: imageName, log: buildLogs }) ->
logger.logInfo('Initializing deploy...')
if image? and options.build
throw new Error('Build option is not applicable when specifying an image')
logs = buildLogs
Promise.all [
dockerUtils.bufferImage(docker, imageName, bufferFile)
token
username
url
params.appName
logger
]
.spread(performUpload)
.finally ->
# If the file was never written to (for instance because an error
# has occured before any data was written) this call will throw an
# ugly error, just suppress it
Promise.try ->
require('mz/fs').unlink(bufferFile)
.catch(_.noop)
.tap ({ image: imageName, buildId }) ->
logger.logSuccess("Successfully deployed image: #{formatImageName(imageName)}")
return buildId
.then ({ image: imageName, buildId }) ->
if logs is '' or options.nologupload?
return ''
Promise.join(
helpers.getApplication(appName)
helpers.getArchAndDeviceType(appName)
(app, { arch, device_type }) ->
app.arch = arch
app.device_type = device_type
return app
)
.then (app) ->
[ app, image, !!options.build, !options.nologupload ]
logger.logInfo('Uploading logs to dashboard...')
Promise.join(
logs
token
url
buildId
username
params.appName
uploadLogs
)
.return('Successfully uploaded logs')
.then (msg) ->
logger.logSuccess(msg) if msg isnt ''
.asCallback(done)
Promise.join(
resin.auth.getToken()
resin.auth.whoami()
resin.settings.get('resinUrl')
upload
)
.then ([ app, image, shouldPerformBuild, shouldUploadLogs ]) ->
Promise.join(
dockerUtils.getDocker(options)
dockerUtils.generateBuildOpts(options)
compose.generateOpts(options)
(docker, buildOpts, composeOpts) ->
deployProject(docker, logger, composeOpts, {
app
appName # may be prefixed by 'owner/', unlike app.app_name
image
shouldPerformBuild
shouldUploadLogs
buildEmulated: !!options.emulated
buildOpts
})
)
.asCallback(done)

View File

@ -1,5 +1,5 @@
###
Copyright 2016-2017 Resin.io
Copyright 2016-2017 Balena
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -16,6 +16,11 @@ limitations under the License.
commandOptions = require('./command-options')
_ = require('lodash')
{ normalizeUuidProp } = require('../utils/normalization')
expandForAppName = {
$expand: belongs_to__application: $select: 'app_name'
}
exports.list =
signature: 'devices'
@ -27,33 +32,35 @@ exports.list =
Examples:
$ resin devices
$ resin devices --application MyApp
$ resin devices --app MyApp
$ resin devices -a MyApp
$ balena devices
$ balena devices --application MyApp
$ balena devices --app MyApp
$ balena devices -a MyApp
'''
options: [ commandOptions.optionalApplication ]
permission: 'user'
primary: true
action: (params, options, done) ->
Promise = require('bluebird')
resin = require('resin-sdk-preconfigured')
balena = require('balena-sdk').fromSharedOptions()
visuals = require('resin-cli-visuals')
Promise.try ->
if options.application?
return resin.models.device.getAllByApplication(options.application)
return resin.models.device.getAll()
return balena.models.device.getAllByApplication(options.application, expandForAppName)
return balena.models.device.getAll(expandForAppName)
.tap (devices) ->
devices = _.map devices, (device) ->
device.dashboard_url = balena.models.device.getDashboardUrl(device.uuid)
device.application_name = device.belongs_to__application[0].app_name
device.uuid = device.uuid.slice(0, 7)
return device
console.log visuals.table.horizontal devices, [
'id'
'uuid'
'name'
'device_name'
'device_type'
'application_name'
'status'
@ -72,21 +79,25 @@ exports.info =
Examples:
$ resin device 7cf02a6
$ balena device 7cf02a6
'''
permission: 'user'
primary: true
action: (params, options, done) ->
resin = require('resin-sdk-preconfigured')
normalizeUuidProp(params)
balena = require('balena-sdk').fromSharedOptions()
visuals = require('resin-cli-visuals')
resin.models.device.get(params.uuid).then (device) ->
resin.models.device.getStatus(device).then (status) ->
balena.models.device.get(params.uuid, expandForAppName)
.then (device) ->
balena.models.device.getStatus(device).then (status) ->
device.status = status
device.dashboard_url = balena.models.device.getDashboardUrl(device.uuid)
device.application_name = device.belongs_to__application[0].app_name
device.commit = device.is_on__commit
console.log visuals.table.vertical device, [
"$#{device.name}$"
"$#{device.device_name}$"
'id'
'device_type'
'status'
@ -104,40 +115,16 @@ exports.info =
]
.nodeify(done)
exports.supported =
signature: 'devices supported'
description: 'list all supported devices'
help: '''
Use this command to get the list of all supported devices
Examples:
$ resin devices supported
'''
action: (params, options, done) ->
resin = require('resin-sdk-preconfigured')
visuals = require('resin-cli-visuals')
resin.models.config.getDeviceTypes().then (deviceTypes) ->
console.log visuals.table.horizontal deviceTypes, [
'slug'
'name'
]
.nodeify(done)
exports.register =
signature: 'device register <application>'
description: 'register a device'
help: '''
Use this command to register a device to an application.
Note that device api keys are only supported on ResinOS 2.0.3+
Examples:
$ resin device register MyApp
$ resin device register MyApp --uuid <uuid>
$ resin device register MyApp --uuid <uuid> --device-api-key <existingDeviceKey>
$ balena device register MyApp
$ balena device register MyApp --uuid <uuid>
'''
permission: 'user'
options: [
@ -147,23 +134,17 @@ exports.register =
parameter: 'uuid'
alias: 'u'
}
commandOptions.optionalDeviceApiKey
]
action: (params, options, done) ->
Promise = require('bluebird')
resin = require('resin-sdk-preconfigured')
balena = require('balena-sdk').fromSharedOptions()
Promise.join(
resin.models.application.get(params.application)
options.uuid ? resin.models.device.generateUniqueKey()
options.deviceApiKey ? resin.models.device.generateUniqueKey()
(application, uuid, deviceApiKey) ->
balena.models.application.get(params.application)
options.uuid ? balena.models.device.generateUniqueKey()
(application, uuid) ->
console.info("Registering to #{application.app_name}: #{uuid}")
if not options.deviceApiKey?
console.info("Using generated device api key: #{deviceApiKey}")
else
console.info('Using provided device api key')
return resin.models.device.register(application.id, uuid, deviceApiKey)
return balena.models.device.register(application.id, uuid)
)
.get('uuid')
.nodeify(done)
@ -172,24 +153,25 @@ exports.remove =
signature: 'device rm <uuid>'
description: 'remove a device'
help: '''
Use this command to remove a device from resin.io.
Use this command to remove a device from balena.
Notice this command asks for confirmation interactively.
You can avoid this by passing the `--yes` boolean option.
Examples:
$ resin device rm 7cf02a6
$ resin device rm 7cf02a6 --yes
$ balena device rm 7cf02a6
$ balena device rm 7cf02a6 --yes
'''
options: [ commandOptions.yes ]
permission: 'user'
action: (params, options, done) ->
resin = require('resin-sdk-preconfigured')
normalizeUuidProp(params)
balena = require('balena-sdk').fromSharedOptions()
patterns = require('../utils/patterns')
patterns.confirm(options.yes, 'Are you sure you want to delete the device?').then ->
resin.models.device.remove(params.uuid)
balena.models.device.remove(params.uuid)
.nodeify(done)
exports.identify =
@ -202,12 +184,13 @@ exports.identify =
Examples:
$ resin device identify 23c73a1
$ balena device identify 23c73a1
'''
permission: 'user'
action: (params, options, done) ->
resin = require('resin-sdk-preconfigured')
resin.models.device.identify(params.uuid).nodeify(done)
normalizeUuidProp(params)
balena = require('balena-sdk').fromSharedOptions()
balena.models.device.identify(params.uuid).nodeify(done)
exports.reboot =
signature: 'device reboot <uuid>'
@ -217,13 +200,14 @@ exports.reboot =
Examples:
$ resin device reboot 23c73a1
$ balena device reboot 23c73a1
'''
options: [ commandOptions.forceUpdateLock ]
permission: 'user'
action: (params, options, done) ->
resin = require('resin-sdk-preconfigured')
resin.models.device.reboot(params.uuid, options).nodeify(done)
normalizeUuidProp(params)
balena = require('balena-sdk').fromSharedOptions()
balena.models.device.reboot(params.uuid, options).nodeify(done)
exports.shutdown =
signature: 'device shutdown <uuid>'
@ -233,13 +217,14 @@ exports.shutdown =
Examples:
$ resin device shutdown 23c73a1
$ balena device shutdown 23c73a1
'''
options: [ commandOptions.forceUpdateLock ]
permission: 'user'
action: (params, options, done) ->
resin = require('resin-sdk-preconfigured')
resin.models.device.shutdown(params.uuid, options).nodeify(done)
normalizeUuidProp(params)
balena = require('balena-sdk').fromSharedOptions()
balena.models.device.shutdown(params.uuid, options).nodeify(done)
exports.enableDeviceUrl =
signature: 'device public-url enable <uuid>'
@ -249,12 +234,13 @@ exports.enableDeviceUrl =
Examples:
$ resin device public-url enable 23c73a1
$ balena device public-url enable 23c73a1
'''
permission: 'user'
action: (params, options, done) ->
resin = require('resin-sdk-preconfigured')
resin.models.device.enableDeviceUrl(params.uuid).nodeify(done)
normalizeUuidProp(params)
balena = require('balena-sdk').fromSharedOptions()
balena.models.device.enableDeviceUrl(params.uuid).nodeify(done)
exports.disableDeviceUrl =
signature: 'device public-url disable <uuid>'
@ -264,12 +250,13 @@ exports.disableDeviceUrl =
Examples:
$ resin device public-url disable 23c73a1
$ balena device public-url disable 23c73a1
'''
permission: 'user'
action: (params, options, done) ->
resin = require('resin-sdk-preconfigured')
resin.models.device.disableDeviceUrl(params.uuid).nodeify(done)
normalizeUuidProp(params)
balena = require('balena-sdk').fromSharedOptions()
balena.models.device.disableDeviceUrl(params.uuid).nodeify(done)
exports.getDeviceUrl =
signature: 'device public-url <uuid>'
@ -279,12 +266,13 @@ exports.getDeviceUrl =
Examples:
$ resin device public-url 23c73a1
$ balena device public-url 23c73a1
'''
permission: 'user'
action: (params, options, done) ->
resin = require('resin-sdk-preconfigured')
resin.models.device.getDeviceUrl(params.uuid).then (url) ->
normalizeUuidProp(params)
balena = require('balena-sdk').fromSharedOptions()
balena.models.device.getDeviceUrl(params.uuid).then (url) ->
console.log(url)
.nodeify(done)
@ -296,18 +284,19 @@ exports.hasDeviceUrl =
Examples:
$ resin device public-url status 23c73a1
$ balena device public-url status 23c73a1
'''
permission: 'user'
action: (params, options, done) ->
resin = require('resin-sdk-preconfigured')
resin.models.device.hasDeviceUrl(params.uuid).then (hasDeviceUrl) ->
normalizeUuidProp(params)
balena = require('balena-sdk').fromSharedOptions()
balena.models.device.hasDeviceUrl(params.uuid).then (hasDeviceUrl) ->
console.log(hasDeviceUrl)
.nodeify(done)
exports.rename =
signature: 'device rename <uuid> [newName]'
description: 'rename a resin device'
description: 'rename a balena device'
help: '''
Use this command to rename a device.
@ -315,13 +304,14 @@ exports.rename =
Examples:
$ resin device rename 7cf02a6
$ resin device rename 7cf02a6 MyPi
$ balena device rename 7cf02a6
$ balena device rename 7cf02a6 MyPi
'''
permission: 'user'
action: (params, options, done) ->
normalizeUuidProp(params)
Promise = require('bluebird')
resin = require('resin-sdk-preconfigured')
balena = require('balena-sdk').fromSharedOptions()
form = require('resin-cli-form')
Promise.try ->
@ -331,7 +321,7 @@ exports.rename =
message: 'How do you want to name this device?'
type: 'input'
.then(_.partial(resin.models.device.rename, params.uuid))
.then(_.partial(balena.models.device.rename, params.uuid))
.nodeify(done)
exports.move =
@ -344,30 +334,42 @@ exports.move =
Examples:
$ resin device move 7cf02a6
$ resin device move 7cf02a6 --application MyNewApp
$ balena device move 7cf02a6
$ balena device move 7cf02a6 --application MyNewApp
'''
permission: 'user'
options: [ commandOptions.optionalApplication ]
action: (params, options, done) ->
resin = require('resin-sdk-preconfigured')
normalizeUuidProp(params)
balena = require('balena-sdk').fromSharedOptions()
patterns = require('../utils/patterns')
resin.models.device.get(params.uuid).then (device) ->
return options.application or patterns.selectApplication (application) ->
return _.every [
application.device_type is device.device_type
device.application_name isnt application.app_name
]
balena.models.device.get(params.uuid, expandForAppName).then (device) ->
return options.application if options.application
return Promise.all([
balena.models.device.getManifestBySlug(device.device_type)
balena.models.config.getDeviceTypes()
]).then ([deviceDeviceType, deviceTypes]) ->
compatibleDeviceTypes = deviceTypes.filter (dt) ->
balena.models.os.isArchitectureCompatibleWith(deviceDeviceType.arch, dt.arch) &&
!!dt.isDependent == !!deviceDeviceType.isDependent &&
dt.state != 'DISCONTINUED'
return patterns.selectApplication (application) ->
return _.every [
_.some(compatibleDeviceTypes, (dt) -> dt.slug == application.device_type)
device.belongs_to__application[0].app_name isnt application.app_name
]
.tap (application) ->
return resin.models.device.move(params.uuid, application)
return balena.models.device.move(params.uuid, application)
.then (application) ->
console.info("#{params.uuid} was moved to #{application}")
.nodeify(done)
exports.init =
signature: 'device init'
description: 'initialise a device with resinOS'
description: 'initialise a device with balenaOS'
help: '''
Use this command to download the OS image of a certain application and write it to an SD Card.
@ -376,71 +378,74 @@ exports.init =
Examples:
$ resin device init
$ resin device init --application MyApp
$ balena device init
$ balena device init --application MyApp
'''
options: [
commandOptions.optionalApplication
commandOptions.yes
commandOptions.advancedConfig
_.assign({}, commandOptions.osVersion, { signature: 'os-version', parameter: 'os-version' })
_.assign({}, commandOptions.osVersionOrSemver, { signature: 'os-version', parameter: 'os-version' })
commandOptions.drive
{
signature: 'config'
description: 'path to the config JSON file, see `resin os build-config`'
description: 'path to the config JSON file, see `balena os build-config`'
parameter: 'config'
}
]
permission: 'user'
action: (params, options, done) ->
Promise = require('bluebird')
capitanoRunAsync = Promise.promisify(require('capitano').run)
rimraf = Promise.promisify(require('rimraf'))
tmp = require('tmp')
tmpNameAsync = Promise.promisify(tmp.tmpName)
tmp.setGracefulCleanup()
resin = require('resin-sdk-preconfigured')
helpers = require('../utils/helpers')
balena = require('balena-sdk').fromSharedOptions()
patterns = require('../utils/patterns')
{ runCommand } = require('../utils/helpers')
Promise.try ->
return options.application if options.application?
return patterns.selectApplication()
.then(resin.models.application.get)
.then(balena.models.application.get)
.then (application) ->
download = ->
tmpNameAsync().then (tempPath) ->
osVersion = options['os-version'] or 'default'
capitanoRunAsync("os download #{application.device_type} --output '#{tempPath}' --version #{osVersion}")
runCommand("os download #{application.device_type} --output '#{tempPath}' --version #{osVersion}")
.disposer (tempPath) ->
return rimraf(tempPath)
Promise.using download(), (tempPath) ->
capitanoRunAsync("device register #{application.app_name}")
.then(resin.models.device.get)
runCommand("device register #{application.app_name}")
.then(balena.models.device.get)
.tap (device) ->
configureCommand = "os configure '#{tempPath}' #{device.uuid}"
configureCommand = "os configure '#{tempPath}' --device #{device.uuid}"
if options.config
configureCommand += " --config '#{options.config}'"
else if options.advanced
configureCommand += ' --advanced'
capitanoRunAsync(configureCommand)
runCommand(configureCommand)
.then ->
osInitCommand = "os initialize '#{tempPath}' --type #{application.device_type}"
if options.yes
osInitCommand += ' --yes'
if options.drive
osInitCommand += " --drive #{options.drive}"
capitanoRunAsync(osInitCommand)
runCommand(osInitCommand)
# Make sure the device resource is removed if there is an
# error when configuring or initializing a device image
.catch (error) ->
resin.models.device.remove(device.uuid).finally ->
balena.models.device.remove(device.uuid).finally ->
throw error
.then (device) ->
console.log('Done')
return device.uuid
.nodeify(done)
tsActions = require('./device_ts')
exports.osUpdate = tsActions.osUpdate
exports.supported = tsActions.supported

140
lib/actions/device_ts.ts Normal file
View File

@ -0,0 +1,140 @@
/*
Copyright 2016-2017 Balena
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { Device } from 'balena-sdk';
import { CommandDefinition } from 'capitano';
import { stripIndent } from 'common-tags';
import { normalizeUuidProp } from '../utils/normalization';
import * as commandOptions from './command-options';
export const supported: CommandDefinition<{}, {}> = {
signature: 'devices supported',
description: 'list all supported devices',
help: stripIndent`
Use this command to get the list of all supported devices.
Examples:
$ balena devices supported
`,
async action(_params, _options) {
const sdk = (await import('balena-sdk')).fromSharedOptions();
const visuals = await import('resin-cli-visuals');
const _ = await import('lodash');
let deviceTypes = await sdk.models.config.getDeviceTypes();
const fields = ['slug', 'name'];
deviceTypes = _.sortBy(deviceTypes, fields).filter(
dt => dt.state !== 'DISCONTINUED',
);
const output = await visuals.table.horizontal(deviceTypes, fields);
console.log(output);
},
};
// tslint:disable-next-line:no-namespace
namespace OsUpdate {
export interface Args {
uuid: string;
}
export type Options = commandOptions.OptionalOsVersionOption &
commandOptions.YesOption;
}
export const osUpdate: CommandDefinition<OsUpdate.Args, OsUpdate.Options> = {
signature: 'device os-update <uuid>',
description: 'Start a Host OS update for a device',
help: stripIndent`
Use this command to trigger a Host OS update for a device.
Notice this command will ask for confirmation interactively.
You can avoid this by passing the \`--yes\` boolean option.
Examples:
$ balena device os-update 23c73a1
$ balena device os-update 23c73a1 --version 2.31.0+rev1.prod
`,
options: [commandOptions.optionalOsVersion, commandOptions.yes],
permission: 'user',
async action(params, options, done) {
normalizeUuidProp(params);
const balena = await import('balena-sdk');
const _ = await import('lodash');
const sdk = balena.fromSharedOptions();
const patterns = await import('../utils/patterns');
const form = await import('resin-cli-form');
return sdk.models.device
.get(params.uuid, {
$select: ['uuid', 'device_type', 'os_version', 'os_variant'],
})
.then(async ({ uuid, device_type, os_version, os_variant }) => {
const currentOsVersion = sdk.models.device.getOsVersion({
os_version,
os_variant,
} as Device);
if (!currentOsVersion) {
patterns.exitWithExpectedError(
'The current os version of the device is not available',
);
// Just to make TS happy
return;
}
return sdk.models.os
.getSupportedOsUpdateVersions(device_type, currentOsVersion)
.then(hupVersionInfo => {
if (hupVersionInfo.versions.length === 0) {
patterns.exitWithExpectedError(
'There are no available Host OS update targets for this device',
);
}
if (options.version != null) {
if (!_.includes(hupVersionInfo.versions, options.version)) {
patterns.exitWithExpectedError(
'The provided version is not in the Host OS update targets for this device',
);
}
return options.version;
}
return form.ask({
message: 'Target OS version',
type: 'list',
choices: hupVersionInfo.versions.map(version => ({
name:
hupVersionInfo.recommended === version
? `${version} (recommended)`
: version,
value: version,
})),
});
})
.then(version =>
patterns
.confirm(
options.yes || false,
'Host OS updates require a device restart when they complete. Are you sure you want to proceed?',
)
.then(() => sdk.models.device.startOsUpdate(uuid, version))
.then(() => patterns.awaitDeviceOsUpdate(uuid, version)),
);
})
.nodeify(done);
},
};

View File

@ -1,182 +0,0 @@
###
Copyright 2016-2017 Resin.io
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.
###
commandOptions = require('./command-options')
exports.list =
signature: 'envs'
description: 'list all environment variables'
help: '''
Use this command to list all environment variables for
a particular application or device.
This command lists all custom environment variables.
If you want to see all environment variables, including private
ones used by resin, use the verbose option.
Example:
$ resin envs --application MyApp
$ resin envs --application MyApp --verbose
$ resin envs --device 7cf02a6
'''
options: [
commandOptions.optionalApplication
commandOptions.optionalDevice
{
signature: 'verbose'
description: 'show private environment variables'
boolean: true
alias: 'v'
}
]
permission: 'user'
action: (params, options, done) ->
Promise = require('bluebird')
_ = require('lodash')
resin = require('resin-sdk-preconfigured')
visuals = require('resin-cli-visuals')
Promise.try ->
if options.application?
return resin.models.environmentVariables.getAllByApplication(options.application)
else if options.device?
return resin.models.environmentVariables.device.getAll(options.device)
else
throw new Error('You must specify an application or device')
.tap (environmentVariables) ->
if _.isEmpty(environmentVariables)
throw new Error('No environment variables found')
if not options.verbose
isSystemVariable = resin.models.environmentVariables.isSystemVariable
environmentVariables = _.reject(environmentVariables, isSystemVariable)
console.log visuals.table.horizontal environmentVariables, [
'id'
'name'
'value'
]
.nodeify(done)
exports.remove =
signature: 'env rm <id>'
description: 'remove an environment variable'
help: '''
Use this command to remove an environment variable from an application.
Don't remove resin specific variables, as things might not work as expected.
Notice this command asks for confirmation interactively.
You can avoid this by passing the `--yes` boolean option.
If you want to eliminate a device environment variable, pass the `--device` boolean option.
Examples:
$ resin env rm 215
$ resin env rm 215 --yes
$ resin env rm 215 --device
'''
options: [
commandOptions.yes
commandOptions.booleanDevice
]
permission: 'user'
action: (params, options, done) ->
resin = require('resin-sdk-preconfigured')
patterns = require('../utils/patterns')
patterns.confirm(options.yes, 'Are you sure you want to delete the environment variable?').then ->
if options.device
resin.models.environmentVariables.device.remove(params.id)
else
resin.models.environmentVariables.remove(params.id)
.nodeify(done)
exports.add =
signature: 'env add <key> [value]'
description: 'add an environment variable'
help: '''
Use this command to add an enviroment variable to an application.
If value is omitted, the tool will attempt to use the variable's value
as defined in your host machine.
Use the `--device` option if you want to assign the environment variable
to a specific device.
If the value is grabbed from the environment, a warning message will be printed.
Use `--quiet` to remove it.
Examples:
$ resin env add EDITOR vim --application MyApp
$ resin env add TERM --application MyApp
$ resin env add EDITOR vim --device 7cf02a6
'''
options: [
commandOptions.optionalApplication
commandOptions.optionalDevice
]
permission: 'user'
action: (params, options, done) ->
Promise = require('bluebird')
resin = require('resin-sdk-preconfigured')
Promise.try ->
if not params.value?
params.value = process.env[params.key]
if not params.value?
throw new Error("Environment value not found for key: #{params.key}")
else
console.info("Warning: using #{params.key}=#{params.value} from host environment")
if options.application?
resin.models.environmentVariables.create(options.application, params.key, params.value)
else if options.device?
resin.models.environmentVariables.device.create(options.device, params.key, params.value)
else
throw new Error('You must specify an application or device')
.nodeify(done)
exports.rename =
signature: 'env rename <id> <value>'
description: 'rename an environment variable'
help: '''
Use this command to rename an enviroment variable from an application.
Pass the `--device` boolean option if you want to rename a device environment variable.
Examples:
$ resin env rename 376 emacs
$ resin env rename 376 emacs --device
'''
permission: 'user'
options: [ commandOptions.booleanDevice ]
action: (params, options, done) ->
Promise = require('bluebird')
resin = require('resin-sdk-preconfigured')
Promise.try ->
if options.device
resin.models.environmentVariables.device.update(params.id, params.value)
else
resin.models.environmentVariables.update(params.id, params.value)
.nodeify(done)

View File

@ -1,5 +1,5 @@
###
Copyright 2016-2017 Resin.io
Copyright 2016-2017 Balena
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -17,10 +17,14 @@ limitations under the License.
_ = require('lodash')
capitano = require('capitano')
columnify = require('columnify')
messages = require('../utils/messages')
{ getManualSortCompareFunction } = require('../utils/helpers')
{ exitWithExpectedError } = require('../utils/patterns')
{ getOclifHelpLinePairs } = require('./help_ts')
parse = (object) ->
return _.fromPairs _.map object, (item) ->
return _.map object, (item) ->
# Hacky way to determine if an object is
# a function or a command
@ -39,13 +43,32 @@ indent = (text) ->
return ' ' + line
return text.join('\n')
print = (data) ->
console.log indent columnify data,
print = (usageDescriptionPairs) ->
console.log indent columnify _.fromPairs(usageDescriptionPairs),
showHeaders: false
minWidth: 35
manuallySortedPrimaryCommands = [
'help',
'login',
'push',
'logs',
'ssh',
'apps',
'app',
'devices',
'device',
'tunnel',
'preload',
'build',
'deploy',
'join',
'leave',
'local scan',
]
general = (params, options, done) ->
console.log('Usage: resin [COMMAND] [OPTIONS]\n')
console.log('Usage: balena [COMMAND] [OPTIONS]\n')
console.log(messages.reachingOut)
console.log('\nPrimary commands:\n')
@ -55,36 +78,39 @@ general = (params, options, done) ->
return command.hidden or command.isWildcard()
groupedCommands = _.groupBy commands, (command) ->
if command.plugin
return 'plugins'
if command.primary
return 'primary'
return 'secondary'
print(parse(groupedCommands.primary))
print parse(groupedCommands.primary).sort(getManualSortCompareFunction(
manuallySortedPrimaryCommands,
([signature, description], manualItem) ->
signature == manualItem or signature.startsWith("#{manualItem} ")
))
if options.verbose
if not _.isEmpty(groupedCommands.plugins)
console.log('\nInstalled plugins:\n')
print(parse(groupedCommands.plugins))
console.log('\nAdditional commands:\n')
print(parse(groupedCommands.secondary))
secondaryCommandPromise = getOclifHelpLinePairs()
.then (oclifHelpLinePairs) ->
print parse(groupedCommands.secondary).concat(oclifHelpLinePairs).sort()
else
console.log('\nRun `resin help --verbose` to list additional commands')
console.log('\nRun `balena help --verbose` to list additional commands')
secondaryCommandPromise = Promise.resolve()
if not _.isEmpty(capitano.state.globalOptions)
console.log('\nGlobal Options:\n')
print(parse(capitano.state.globalOptions))
return done()
secondaryCommandPromise
.then ->
if not _.isEmpty(capitano.state.globalOptions)
console.log('\nGlobal Options:\n')
print parse(capitano.state.globalOptions).sort()
done()
.catch(done)
command = (params, options, done) ->
capitano.state.getMatchCommand params.command, (error, command) ->
return done(error) if error?
if not command? or command.isWildcard()
return done(new Error("Command not found: #{params.command}"))
exitWithExpectedError("Command not found: #{params.command}")
console.log("Usage: #{command.signature}")
@ -95,7 +121,7 @@ command = (params, options, done) ->
if not _.isEmpty(command.options)
console.log('\nOptions:\n')
print(parse(command.options))
print parse(command.options).sort()
return done()
@ -107,8 +133,8 @@ exports.help =
Examples:
$ resin help apps
$ resin help os download
$ balena help apps
$ balena help os download
'''
primary: true
options: [

53
lib/actions/help_ts.ts Normal file
View File

@ -0,0 +1,53 @@
/**
* @license
* Copyright 2019 Balena Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Command } from '@oclif/command';
import * as Bluebird from 'bluebird';
import * as _ from 'lodash';
import * as path from 'path';
import { capitanoizeOclifUsage } from '../utils/oclif-utils';
export async function getOclifHelpLinePairs(): Promise<
Array<[string, string]>
> {
const { convertedCommands } = await import('../preparser');
const cmdClasses: Array<Promise<typeof Command>> = [];
for (const convertedCmd of convertedCommands) {
const [topic, cmd] = convertedCmd.split(':');
const pathComponents = ['..', 'actions-oclif', topic];
if (cmd) {
pathComponents.push(cmd);
}
// note that `import(path)` returns a promise
cmdClasses.push(import(path.join(...pathComponents)));
}
return Bluebird.map(cmdClasses, getCmdUsageDescriptionLinePair);
}
function getCmdUsageDescriptionLinePair(cmdModule: any): [string, string] {
const cmd: typeof Command = cmdModule.default;
const usage = capitanoizeOclifUsage(cmd.usage);
let description = '';
// note: [^] matches any characters (including line breaks), achieving the
// same effect as the 's' regex flag which is only supported by Node 9+
const matches = /\s*([^]+?)\n[^]*/.exec(cmd.description || '');
if (matches && matches.length > 1) {
description = _.lowerFirst(_.trimEnd(matches[1], '.'));
}
return [usage, description];
}

View File

@ -1,5 +1,5 @@
###
Copyright 2016-2017 Resin.io
Copyright 2016-2017 Balena
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -15,24 +15,27 @@ limitations under the License.
###
module.exports =
wizard: require('./wizard')
apiKey: require('./api-key')
app: require('./app')
info: require('./info')
auth: require('./auth')
device: require('./device')
env: require('./environment-variables')
tags: require('./tags')
keys: require('./keys')
logs: require('./logs')
local: require('./local')
scan: require('./scan')
notes: require('./notes')
help: require('./help')
os: require('./os')
settings: require('./settings')
config: require('./config')
sync: require('./sync')
ssh: require('./ssh')
internal: require('./internal')
build: require('./build')
deploy: require('./deploy')
util: require('./util')
preload: require('./preload')
push: require('./push')
join: require('./join')
leave: require('./leave')
tunnel: require('./tunnel')

View File

@ -1,26 +0,0 @@
###
Copyright 2016-2017 Resin.io
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.
###
exports.version =
signature: 'version'
description: 'output the version number'
help: '''
Display the Resin CLI version.
'''
action: (params, options, done) ->
packageJSON = require('../../package.json')
console.log(packageJSON.version)
return done()

View File

@ -1,5 +1,5 @@
###
Copyright 2016-2017 Resin.io
Copyright 2016-2017 Balena
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -21,17 +21,36 @@ exports.osInit =
signature: 'internal osinit <image> <type> <config>'
description: 'do actual init of the device with the preconfigured os image'
help: '''
Don't use this command directly! Use `resin os initialize <image>` instead.
Don't use this command directly! Use `balena os initialize <image>` instead.
'''
hidden: true
root: true
action: (params, options, done) ->
Promise = require('bluebird')
init = require('resin-device-init')
init = require('balena-device-init')
helpers = require('../utils/helpers')
return Promise.try ->
config = JSON.parse(params.config)
init.initialize(params.image, params.type, config)
configPromise = Promise.try -> JSON.parse(params.config)
manifestPromise = helpers.getManifest(params.image, params.type)
Promise.join configPromise, manifestPromise, (config, manifest) ->
init.initialize(params.image, manifest, config)
.then(helpers.osProgressHandler)
.nodeify(done)
exports.scanDevices =
signature: 'internal scandevices'
description: 'scan for local balena-enabled devices and show a picker to choose one'
help: '''
Don't use this command directly!
'''
hidden: true
root: true
action: (params, options, done) ->
Promise = require('bluebird')
{ forms } = require('balena-sync')
return Promise.try ->
forms.selectLocalBalenaOsDevice()
.then (hostnameOrIp) ->
console.error("==> Selected device: #{hostnameOrIp}")
.nodeify(done)

74
lib/actions/join.ts Normal file
View File

@ -0,0 +1,74 @@
/*
Copyright 2016-2017 Balena
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import * as Bluebird from 'bluebird';
import { CommandDefinition } from 'capitano';
import { stripIndent } from 'common-tags';
interface Args {
deviceIp?: string;
}
interface Options {
application?: string;
}
export const join: CommandDefinition<Args, Options> = {
signature: 'join [deviceIp]',
description:
'Promote a local device running balenaOS to join an application on a balena server',
help: stripIndent`
Use this command to move a local device to an application on another balena server.
For example, you could provision a device against an openBalena installation
where you perform end-to-end tests and then move it to balenaCloud when it's
ready for production.
Moving a device between applications on the same server is not supported.
If you don't specify a device hostname or IP, this command will automatically
scan the local network for balenaOS devices and prompt you to select one
from an interactive picker. This usually requires root privileges.
Examples:
$ balena join
$ balena join balena.local
$ balena join balena.local --application MyApp
$ balena join 192.168.1.25
$ balena join 192.168.1.25 --application MyApp
`,
options: [
{
signature: 'application',
parameter: 'application',
alias: 'a',
description: 'The name of the application the device should join',
},
],
primary: true,
async action(params, options, done) {
const balena = await import('balena-sdk');
const Logger = await import('../utils/logger');
const promote = await import('../utils/promote');
const sdk = balena.fromSharedOptions();
const logger = Logger.getLogger();
return Bluebird.try(() => {
return promote.join(logger, sdk, params.deviceIp, options.application);
}).nodeify(done);
},
};

View File

@ -1,5 +1,5 @@
###
Copyright 2016-2017 Resin.io
Copyright 2016-2017 Balena
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -24,14 +24,14 @@ exports.list =
Examples:
$ resin keys
$ balena keys
'''
permission: 'user'
action: (params, options, done) ->
resin = require('resin-sdk-preconfigured')
balena = require('balena-sdk').fromSharedOptions()
visuals = require('resin-cli-visuals')
resin.models.key.getAll().then (keys) ->
balena.models.key.getAll().then (keys) ->
console.log visuals.table.horizontal keys, [
'id'
'title'
@ -46,14 +46,14 @@ exports.info =
Examples:
$ resin key 17
$ balena key 17
'''
permission: 'user'
action: (params, options, done) ->
resin = require('resin-sdk-preconfigured')
balena = require('balena-sdk').fromSharedOptions()
visuals = require('resin-cli-visuals')
resin.models.key.get(params.id).then (key) ->
balena.models.key.get(params.id).then (key) ->
console.log visuals.table.vertical key, [
'id'
'title'
@ -61,7 +61,7 @@ exports.info =
# Since the public key string is long, it might
# wrap to lines below, causing the table layout to break.
# See https://github.com/resin-io/resin-cli/issues/151
# See https://github.com/balena-io/balena-cli/issues/151
console.log('\n' + key.public_key)
.nodeify(done)
@ -69,29 +69,29 @@ exports.remove =
signature: 'key rm <id>'
description: 'remove a ssh key'
help: '''
Use this command to remove a SSH key from resin.io.
Use this command to remove a SSH key from balena.
Notice this command asks for confirmation interactively.
You can avoid this by passing the `--yes` boolean option.
Examples:
$ resin key rm 17
$ resin key rm 17 --yes
$ balena key rm 17
$ balena key rm 17 --yes
'''
options: [ commandOptions.yes ]
permission: 'user'
action: (params, options, done) ->
resin = require('resin-sdk-preconfigured')
balena = require('balena-sdk').fromSharedOptions()
patterns = require('../utils/patterns')
patterns.confirm(options.yes, 'Are you sure you want to delete the key?').then ->
resin.models.key.remove(params.id)
balena.models.key.remove(params.id)
.nodeify(done)
exports.add =
signature: 'key add <name> [path]'
description: 'add a SSH key to resin.io'
description: 'add a SSH key to balena'
help: '''
Use this command to associate a new SSH key with your account.
@ -100,8 +100,8 @@ exports.add =
Examples:
$ resin key add Main ~/.ssh/id_rsa.pub
$ cat ~/.ssh/id_rsa.pub | resin key add Main
$ balena key add Main ~/.ssh/id_rsa.pub
$ cat ~/.ssh/id_rsa.pub | balena key add Main
'''
permission: 'user'
action: (params, options, done) ->
@ -109,7 +109,7 @@ exports.add =
Promise = require('bluebird')
readFileAsync = Promise.promisify(require('fs').readFile)
capitano = require('capitano')
resin = require('resin-sdk-preconfigured')
balena = require('balena-sdk').fromSharedOptions()
Promise.try ->
return readFileAsync(params.path, encoding: 'utf8') if params.path?
@ -119,5 +119,5 @@ exports.add =
capitano.utils.getStdin (data) ->
return callback(null, data)
.then(_.partial(resin.models.key.create, params.name))
.then(_.partial(balena.models.key.create, params.name))
.nodeify(done)

59
lib/actions/leave.ts Normal file
View File

@ -0,0 +1,59 @@
/*
Copyright 2016-2017 Balena
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import * as Bluebird from 'bluebird';
import { CommandDefinition } from 'capitano';
import { stripIndent } from 'common-tags';
interface Args {
deviceIp?: string;
}
export const leave: CommandDefinition<Args, {}> = {
signature: 'leave [deviceIp]',
description: 'Detach a local device from its balena application',
help: stripIndent`
Use this command to make a local device leave the balena server it is
provisioned on. This effectively makes the device "unmanaged".
The device entry on the server is preserved after running this command,
so the device can subsequently re-join the server if needed.
If you don't specify a device hostname or IP, this command will automatically
scan the local network for balenaOS devices and prompt you to select one
from an interactive picker. This usually requires root privileges.
Examples:
$ balena leave
$ balena leave balena.local
$ balena leave 192.168.1.25
`,
options: [],
permission: 'user',
primary: true,
async action(params, _options, done) {
const balena = await import('balena-sdk');
const Logger = await import('../utils/logger');
const promote = await import('../utils/promote');
const sdk = balena.fromSharedOptions();
const logger = Logger.getLogger();
return Bluebird.try(() => {
return promote.leave(logger, sdk, params.deviceIp);
}).nodeify(done);
},
};

View File

@ -1,19 +1,21 @@
Promise = require('bluebird')
_ = require('lodash')
Docker = require('docker-toolbelt')
form = require('resin-cli-form')
chalk = require('chalk')
dockerUtils = require('../../utils/docker')
{ exitWithExpectedError } = require('../../utils/patterns')
exports.dockerPort = dockerPort = 2375
exports.dockerTimeout = dockerTimeout = 2000
exports.filterOutSupervisorContainer = filterOutSupervisorContainer = (container) ->
for name in container.Names
return false if name.includes('resin_supervisor')
return false if (name.includes('resin_supervisor') or name.includes('balena_supervisor'))
return true
exports.selectContainerFromDevice = Promise.method (deviceIp, filterSupervisor = false) ->
docker = new Docker(host: deviceIp, port: dockerPort, timeout: dockerTimeout)
form = require('resin-cli-form')
docker = dockerUtils.createClient(host: deviceIp, port: dockerPort, timeout: dockerTimeout)
# List all containers, including those not running
docker.listContainersAsync(all: true)
@ -22,23 +24,22 @@ exports.selectContainerFromDevice = Promise.method (deviceIp, filterSupervisor =
filterOutSupervisorContainer(container)
.then (containers) ->
if _.isEmpty(containers)
throw new Error("No containers found in #{deviceIp}")
exitWithExpectedError("No containers found in #{deviceIp}")
return form.ask
message: 'Select a container'
type: 'list'
choices: _.map containers, (container) ->
containerName = container.Names[0] or 'Untitled'
containerName = container.Names?[0] or 'Untitled'
shortContainerId = ('' + container.Id).substr(0, 11)
containerStatus = container.Status
return {
name: "#{containerName} (#{shortContainerId}) - #{containerStatus}"
name: "#{containerName} (#{shortContainerId})"
value: container.Id
}
exports.pipeContainerStream = Promise.method ({ deviceIp, name, outStream, follow = false }) ->
docker = new Docker(host: deviceIp, port: dockerPort)
docker = dockerUtils.createClient(host: deviceIp, port: dockerPort)
container = docker.getContainer(name)
container.inspectAsync()

View File

@ -1,5 +1,5 @@
###
Copyright 2017 Resin.io
Copyright 2017 Balena
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
###
BOOT_PARTITION = { primary: 1 }
BOOT_PARTITION = 1
CONNECTIONS_FOLDER = '/system-connections'
getConfigurationSchema = (connnectionFileName = 'resin-wifi') ->
@ -51,6 +51,8 @@ getConfigurationSchema = (connnectionFileName = 'resin-wifi') ->
type: 'ini'
location:
path: CONNECTIONS_FOLDER.slice(1)
# Reconfix still uses the older resin-image-fs, so still needs an
# object-based partition definition.
partition: BOOT_PARTITION
config_json:
type: 'json'
@ -151,7 +153,7 @@ prepareConnectionFile = (target) ->
if _.includes(files, 'resin-wifi')
return null
# Fresh image, new mode, accoding to https://github.com/resin-os/meta-resin/pull/770/files
# Fresh image, new mode, accoding to https://github.com/balena-os/meta-balena/pull/770/files
if _.includes(files, 'resin-sample.ignore')
return imagefs.copy
image: target
@ -191,18 +193,19 @@ removeHostname = (schema) ->
module.exports =
signature: 'local configure <target>'
description: '(Re)configure a resinOS drive or image'
description: '(Re)configure a balenaOS drive or image'
help: '''
Use this command to configure or reconfigure a resinOS drive or image.
Use this command to configure or reconfigure a balenaOS drive or image.
Examples:
$ resin local configure /dev/sdc
$ resin local configure path/to/image.img
$ balena local configure /dev/sdc
$ balena local configure path/to/image.img
'''
root: true
action: (params, options, done) ->
Promise = require('bluebird')
path = require('path')
umount = require('umount')
umountAsync = Promise.promisify(umount.umount)
isMountedAsync = Promise.promisify(umount.isMounted)
@ -215,7 +218,12 @@ module.exports =
return if not isMounted
umountAsync(params.target)
.then (configurationSchema) ->
denymount params.target, (cb) ->
dmOpts = {}
if process.pkg
# when running in a standalone pkg install, the 'denymount'
# executable is placed on the same folder as process.execPath
dmOpts.executablePath = path.join(path.dirname(process.execPath), 'denymount')
dmHandler = (cb) ->
reconfix.readConfiguration(configurationSchema, params.target)
.then(getConfiguration)
.then (answers) ->
@ -223,6 +231,7 @@ module.exports =
removeHostname(configurationSchema)
reconfix.writeConfiguration(configurationSchema, answers, params.target)
.asCallback(cb)
denymount params.target, dmHandler, dmOpts
.then ->
console.log('Done!')
.asCallback(done)

View File

@ -1,120 +0,0 @@
###
Copyright 2017 Resin.io
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.
###
module.exports =
signature: 'local flash <image>'
description: 'Flash an image to a drive'
help: '''
Use this command to flash a resinOS image to a drive.
Examples:
$ resin local flash path/to/resinos.img
$ resin local flash path/to/resinos.img --drive /dev/disk2
$ resin local flash path/to/resinos.img --drive /dev/disk2 --yes
'''
options: [
signature: 'yes'
boolean: true
description: 'confirm non-interactively'
alias: 'y'
,
signature: 'drive'
parameter: 'drive'
description: 'drive'
alias: 'd'
]
root: true
action: (params, options, done) ->
_ = require('lodash')
os = require('os')
Promise = require('bluebird')
umountAsync = Promise.promisify(require('umount').umount)
fs = Promise.promisifyAll(require('fs'))
driveListAsync = Promise.promisify(require('drivelist').list)
chalk = require('chalk')
visuals = require('resin-cli-visuals')
form = require('resin-cli-form')
imageWrite = require('etcher-image-write')
form.run [
{
message: 'Select drive'
type: 'drive'
name: 'drive'
},
{
message: 'This will erase the selected drive. Are you sure?'
type: 'confirm'
name: 'yes'
default: false
}
],
override:
drive: options.drive
# If `options.yes` is `false`, pass `undefined`,
# otherwise the question will not be asked because
# `false` is a defined value.
yes: options.yes || undefined
# TODO: dedupe with the resin-device-operations
.then (answers) ->
if answers.yes isnt true
console.log(chalk.red.bold('Aborted image flash'))
process.exit(0)
driveListAsync().then (drives) ->
selectedDrive = _.find(drives, device: answers.drive)
if not selectedDrive?
throw new Error("Drive not found: #{answers.drive}")
return selectedDrive
.then (selectedDrive) ->
progressBars =
write: new visuals.Progress('Flashing')
check: new visuals.Progress('Validating')
umountAsync(selectedDrive.device).then ->
Promise.props
imageSize: fs.statAsync(params.image).get('size'),
imageStream: Promise.resolve(fs.createReadStream(params.image))
driveFileDescriptor: fs.openAsync(selectedDrive.raw, 'rs+')
.then (results) ->
imageWrite.write
fd: results.driveFileDescriptor
device: selectedDrive.raw
size: selectedDrive.size
,
stream: results.imageStream,
size: results.imageSize
,
check: true
.then (writer) ->
new Promise (resolve, reject) ->
writer.on 'progress', (state) ->
progressBars[state.type].update(state)
writer.on('error', reject)
writer.on('done', resolve)
.then ->
if (os.platform() is 'win32') and selectedDrive.mountpoint?
ejectAsync = Promise.promisify(require('removedrive').eject)
return ejectAsync(selectedDrive.mountpoint)
return umountAsync(selectedDrive.device)
.asCallback(done)

120
lib/actions/local/flash.ts Normal file
View File

@ -0,0 +1,120 @@
/*
Copyright 2017 Balena
Licensed under the Apache License, Version 2.0 (the 'License');
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an 'AS IS' BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { CommandDefinition } from 'capitano';
import chalk from 'chalk';
import { stripIndent } from 'common-tags';
import * as SDK from 'etcher-sdk';
async function getDrive(options: {
drive?: string;
}): Promise<SDK.sourceDestination.BlockDevice> {
const sdk = await import('etcher-sdk');
const adapter = new sdk.scanner.adapters.BlockDeviceAdapter(() => false);
const scanner = new sdk.scanner.Scanner([adapter]);
await scanner.start();
let drive: SDK.sourceDestination.BlockDevice;
if (options.drive !== undefined) {
const d = scanner.getBy('device', options.drive);
if (d === undefined || !(d instanceof sdk.sourceDestination.BlockDevice)) {
throw new Error(`Drive not found: ${options.drive}`);
}
drive = d;
} else {
const { DriveList } = await import('../../utils/visuals/drive-list');
const driveList = new DriveList(scanner);
drive = await driveList.run();
}
scanner.stop();
return drive;
}
export const flash: CommandDefinition<
{ image: string },
{ drive: string; yes: boolean }
> = {
signature: 'local flash <image>',
description: 'Flash an image to a drive',
help: stripIndent`
Use this command to flash a balenaOS image to a drive.
Examples:
$ balena local flash path/to/balenaos.img[.zip|.gz|.bz2|.xz]
$ balena local flash path/to/balenaos.img --drive /dev/disk2
$ balena local flash path/to/balenaos.img --drive /dev/disk2 --yes
`,
options: [
{
signature: 'yes',
boolean: true,
description: 'confirm non-interactively',
alias: 'y',
},
{
signature: 'drive',
parameter: 'drive',
description: 'drive',
alias: 'd',
},
],
async action(params, options) {
const visuals = await import('resin-cli-visuals');
const form = await import('resin-cli-form');
const { sourceDestination, multiWrite } = await import('etcher-sdk');
const drive = await getDrive(options);
const yes =
options.yes ||
(await form.ask({
message: 'This will erase the selected drive. Are you sure?',
type: 'confirm',
name: 'yes',
default: false,
}));
if (yes !== true) {
console.log(chalk.red.bold('Aborted image flash'));
process.exit(0);
}
const file = new sourceDestination.File(
params.image,
sourceDestination.File.OpenFlags.Read,
);
const source = await file.getInnerSource();
const progressBars: { [key: string]: any } = {
flashing: new visuals.Progress('Flashing'),
verifying: new visuals.Progress('Validating'),
};
await multiWrite.pipeSourceToDestinations(
source,
[drive],
(_, error) => {
// onFail
console.log(chalk.red.bold(error.message));
},
(progress: SDK.multiWrite.MultiDestinationProgress) => {
// onProgress
progressBars[progress.type].update(progress);
},
true, // verify
);
},
};

View File

@ -1,5 +1,5 @@
###
Copyright 2017 Resin.io
Copyright 2017 Balena
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -15,9 +15,4 @@ limitations under the License.
###
exports.configure = require('./configure')
exports.flash = require('./flash')
exports.logs = require('./logs')
exports.scan = require('./scan')
exports.ssh = require('./ssh')
exports.push = require('./push')
exports.stop = require('./stop')
exports.flash = require('./flash').flash

View File

@ -1,5 +1,5 @@
###
Copyright 2017 Resin.io
Copyright 2017 Balena
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -22,16 +22,16 @@ limitations under the License.
#
module.exports =
signature: 'local logs [deviceIp]'
description: 'Get or attach to logs of a running container on a resinOS device'
description: 'Get or attach to logs of a running container on a balenaOS device'
help: '''
Examples:
$ resin local logs
$ resin local logs -f
$ resin local logs 192.168.1.10
$ resin local logs 192.168.1.10 -f
$ resin local logs 192.168.1.10 -f --app-name myapp
$ balena local logs
$ balena local logs -f
$ balena local logs 192.168.1.10
$ balena local logs 192.168.1.10 -f
$ balena local logs 192.168.1.10 -f --app-name myapp
'''
options: [
signature: 'follow'
@ -47,12 +47,12 @@ module.exports =
root: true
action: (params, options, done) ->
Promise = require('bluebird')
{ forms } = require('resin-sync')
{ forms } = require('balena-sync')
{ selectContainerFromDevice, pipeContainerStream } = require('./common')
Promise.try ->
if not params.deviceIp?
return forms.selectLocalResinOsDevice()
return forms.selectLocalBalenaOsDevice()
return params.deviceIp
.then (@deviceIp) =>
if not options['app-name']?

View File

@ -1,5 +1,5 @@
###
Copyright 2016-2017 Resin.io
Copyright 2016-2017 Balena
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -14,43 +14,57 @@ See the License for the specific language governing permissions and
limitations under the License.
###
# Loads '.resin-sync.yml' configuration from 'source' directory.
# Loads '.balena-sync.yml' configuration from 'source' directory.
# Returns the configuration object on success
#
_ = require('lodash')
resinPush = require('resin-sync').capitano('resin-toolbox')
balenaPush = require('balena-sync').capitano('balena-toolbox')
originalAction = balenaPush.action
# TODO: This is a temporary workaround to reuse the existing `rdt push`
# capitano frontend in `resin local push`.
# capitano frontend in `balena local push`.
resinPushHelp = '''
Warning: 'resin local push' requires an openssh-compatible client and 'rsync' to
be correctly installed in your shell environment. For more information (including
Windows support) please check the README here: https://github.com/resin-io/resin-cli
# coffeelint: disable-next-line ("Line ends with trailing whitespace")
deprecationMsg = '''
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Deprecation notice: `balena local push` is deprecated and will be removed in a
future release of the CLI. Please use `balena push <ipAddress>` instead.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Use this command to push your local changes to a container on a LAN-accessible resinOS device on the fly.
'''
balenaPushHelp = """#{deprecationMsg}
Use this command to push your local changes to a container on a LAN-accessible
balenaOS device on the fly.
This command requires an openssh-compatible 'ssh' client and 'rsync' to be
available in the executable PATH of the shell environment. For more information
(including Windows support) please check the README at:
https://github.com/balena-io/balena-cli
If `Dockerfile` or any file in the 'build-triggers' list is changed,
a new container will be built and run on your device.
If not, changes will simply be synced with `rsync` into the application container.
After every 'resin local push' the updated settings will be saved in
'<source>/.resin-sync.yml' and will be used in later invocations. You can
also change any option by editing '.resin-sync.yml' directly.
After every 'balena local push' the updated settings will be saved in
'<source>/.balena-sync.yml' and will be used in later invocations. You can
also change any option by editing '.balena-sync.yml' directly.
Here is an example '.resin-sync.yml' :
Here is an example '.balena-sync.yml' :
$ cat $PWD/.resin-sync.yml
destination: '/usr/src/app'
before: 'echo Hello'
after: 'echo Done'
ignore:
- .git
- node_modules/
$ cat $PWD/.balena-sync.yml
local_balenaos:
app-name: local-app
build-triggers:
- Dockerfile: file-hash-abcdefabcdefabcdefabcdefabcdefabcdef
- package.json: file-hash-abcdefabcdefabcdefabcdefabcdefabcdef
environment:
- MY_VARIABLE=123
Command line options have precedence over the ones saved in '.resin-sync.yml'.
Command line options have precedence over the ones saved in '.balena-sync.yml'.
If '.gitignore' is found in the source directory then all explicitly listed files will be
excluded when using rsync to update the container. You can choose to change this default behavior with the
@ -58,19 +72,24 @@ resinPushHelp = '''
Examples:
$ resin local push
$ resin local push --app-name test-server --build-triggers package.json,requirements.txt
$ resin local push --force-build
$ resin local push --force-build --skip-logs
$ resin local push --ignore lib/
$ resin local push --verbose false
$ resin local push 192.168.2.10 --source . --destination /usr/src/app
$ resin local push 192.168.2.10 -s /home/user/myResinProject -d /usr/src/app --before 'echo Hello' --after 'echo Done'
'''
$ balena local push
$ balena local push --app-name test-server --build-triggers package.json,requirements.txt
$ balena local push --force-build
$ balena local push --force-build --skip-logs
$ balena local push --ignore lib/
$ balena local push --verbose false
$ balena local push 192.168.2.10 --source . --destination /usr/src/app
$ balena local push 192.168.2.10 -s /home/user/balenaProject -d /usr/src/app --before 'echo Hello' --after 'echo Done'
"""
module.exports = _.assign resinPush,
module.exports = _.assign balenaPush,
signature: 'local push [deviceIp]'
help: resinPushHelp
primary: true
description: '[deprecated: use "balena push ipAddress"] ' + balenaPush.description
help: balenaPushHelp
primary: false
root: true
action: (params, options, done) ->
console.log deprecationMsg
originalAction(params, options, done)

View File

@ -1,5 +1,5 @@
###
Copyright 2017 Resin.io
Copyright 2017 Balena
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -14,28 +14,34 @@ See the License for the specific language governing permissions and
limitations under the License.
###
{ hostOSAccess } = require('../command-options')
_ = require('lodash')
localHostOSAccessOption = _.cloneDeep(hostOSAccess)
localHostOSAccessOption.description = 'get a shell into the host OS'
module.exports =
signature: 'local ssh [deviceIp]'
description: 'Get a shell into a resinOS device'
description: 'Get a shell into a balenaOS device'
help: '''
Warning: 'resin local ssh' requires an openssh-compatible client to be correctly
Warning: 'balena local ssh' requires an openssh-compatible client to be correctly
installed in your shell environment. For more information (including Windows
support) please check the README here: https://github.com/resin-io/resin-cli
support) please check the README here: https://github.com/balena-io/balena-cli
Use this command to get a shell into the running application container of
your device.
The '--host' option will get you a shell into the Host OS of the resinOS device.
The '--host' option will get you a shell into the Host OS of the balenaOS device.
No option will return a list of containers to enter or you can explicitly select
one by passing its name to the --container option
Examples:
$ resin local ssh
$ resin local ssh --host
$ resin local ssh --container chaotic_water
$ resin local ssh --container chaotic_water --port 22222
$ resin local ssh --verbose
$ balena local ssh
$ balena local ssh --host
$ balena local ssh --container chaotic_water
$ balena local ssh --container chaotic_water --port 22222
$ balena local ssh --verbose
'''
options: [
signature: 'verbose'
@ -43,11 +49,7 @@ module.exports =
description: 'increase verbosity'
alias: 'v'
,
signature: 'host'
boolean: true
description: 'get a shell into the host OS'
alias: 's'
,
localHostOSAccessOption,
signature: 'container'
parameter: 'container'
default: null
@ -64,11 +66,13 @@ module.exports =
child_process = require('child_process')
Promise = require 'bluebird'
_ = require('lodash')
{ forms } = require('resin-sync')
{ forms } = require('balena-sync')
{ selectContainerFromDevice, getSubShellCommand } = require('./common')
{ exitWithExpectedError } = require('../../utils/patterns')
if (options.host is true and options.container?)
throw new Error('Please pass either --host or --container option')
exitWithExpectedError('Please pass either --host or --container option')
if not options.port?
options.port = 22222
@ -77,7 +81,7 @@ module.exports =
Promise.try ->
if not params.deviceIp?
return forms.selectLocalResinOsDevice()
return forms.selectLocalBalenaOsDevice()
return params.deviceIp
.then (deviceIp) ->
_.assign(options, { deviceIp })
@ -101,7 +105,8 @@ module.exports =
if not options.host
shellCmd = '''/bin/sh -c $"'if [ -e /bin/bash ]; then exec /bin/bash; else exec /bin/sh; fi'"'''
command += " docker exec -ti #{container} #{shellCmd}"
dockerCmd = "'$(if [ -f /usr/bin/balena ]; then echo \"balena\"; else echo \"docker\"; fi)'"
command += " #{dockerCmd} exec -ti #{container} #{shellCmd}"
subShellCommand = getSubShellCommand(command)
child_process.spawn subShellCommand.program, subShellCommand.args,

View File

@ -1,5 +1,5 @@
###
Copyright 2017 Resin.io
Copyright 2017 Balena
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -22,16 +22,16 @@ limitations under the License.
#
module.exports =
signature: 'local stop [deviceIp]'
description: 'Stop a running container on a resinOS device'
description: 'Stop a running container on a balenaOS device'
help: '''
Examples:
$ resin local stop
$ resin local stop --app-name myapp
$ resin local stop --all
$ resin local stop 192.168.1.10
$ resin local stop 192.168.1.10 --app-name myapp
$ balena local stop
$ balena local stop --app-name myapp
$ balena local stop --all
$ balena local stop 192.168.1.10
$ balena local stop 192.168.1.10 --app-name myapp
'''
options: [
signature: 'all'
@ -47,15 +47,15 @@ module.exports =
action: (params, options, done) ->
Promise = require('bluebird')
chalk = require('chalk')
{ forms, config, ResinLocalDockerUtils } = require('resin-sync')
{ forms, config, BalenaLocalDockerUtils } = require('balena-sync')
{ selectContainerFromDevice, filterOutSupervisorContainer } = require('./common')
Promise.try ->
if not params.deviceIp?
return forms.selectLocalResinOsDevice()
return forms.selectLocalBalenaOsDevice()
return params.deviceIp
.then (@deviceIp) =>
@docker = new ResinLocalDockerUtils(@deviceIp)
@docker = new BalenaLocalDockerUtils(@deviceIp)
if options.all
# Only list running containers
@ -67,7 +67,7 @@ module.exports =
@docker.stopContainer(Id)
ymlConfig = config.load()
@appName = options['app-name'] ? ymlConfig['local_resinos']?['app-name']
@appName = options['app-name'] ? ymlConfig['local_balenaos']?['app-name']
@docker.checkForRunningContainer(@appName)
.then (isRunning) =>
if not isRunning

View File

@ -1,69 +0,0 @@
###
Copyright 2016-2017 Resin.io
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.
###
module.exports =
signature: 'logs <uuid>'
description: 'show device logs'
help: '''
Use this command to show logs for a specific device.
By default, the command prints all log messages and exit.
To continuously stream output, and see new logs in real time, use the `--tail` option.
Note that for now you need to provide the whole UUID for this command to work correctly.
This is due to some technical limitations that we plan to address soon.
Examples:
$ resin logs 23c73a1
$ resin logs 23c73a1
'''
options: [
{
signature: 'tail'
description: 'continuously stream output'
boolean: true
alias: 't'
}
]
permission: 'user'
primary: true
action: (params, options, done) ->
_ = require('lodash')
resin = require('resin-sdk-preconfigured')
moment = require('moment')
printLine = (line) ->
timestamp = moment(line.timestamp).format('DD.MM.YY HH:mm:ss (ZZ)')
console.log("#{timestamp} #{line.message}")
promise = resin.logs.history(params.uuid).each(printLine)
if not options.tail
# PubNub keeps the process alive after a history query.
# Until this is fixed, we force the process to exit.
# This of course prevents this command to be used programatically
return promise.catch(done).finally ->
process.exit(0)
promise.then ->
resin.logs.subscribe(params.uuid).then (logs) ->
logs.on('line', printLine)
logs.on('error', done)
.catch(done)

184
lib/actions/logs.ts Normal file
View File

@ -0,0 +1,184 @@
/*
Copyright 2016-2019 Balena
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { CommandDefinition } from 'capitano';
import { stripIndent } from 'common-tags';
import { normalizeUuidProp } from '../utils/normalization';
import { validateDotLocalUrl } from '../utils/validation';
type CloudLog =
| {
isSystem: false;
serviceId: number;
timestamp: number;
message: string;
}
| {
isSystem: true;
timestamp: number;
message: string;
};
export const logs: CommandDefinition<
{
uuidOrDevice: string;
},
{
tail?: boolean;
service?: [string] | string;
system?: boolean;
}
> = {
signature: 'logs <uuidOrDevice>',
description: 'show device logs',
help: stripIndent`
Use this command to show logs for a specific device.
By default, the command prints all log messages and exits.
To continuously stream output, and see new logs in real time, use the \`--tail\` option.
If an IP or .local address is passed to this command, logs are displayed from
a local mode device with that address. Note that --tail is implied
when this command is provided a local mode device.
Logs from a single service can be displayed with the --service flag. Just system logs
can be shown with the --system flag. Note that these flags can be used together.
Examples:
$ balena logs 23c73a1
$ balena logs 23c73a1 --tail
$ balena logs 192.168.0.31
$ balena logs 192.168.0.31 --service my-service
$ balena logs 192.168.0.31 --service my-service-1 --service my-service-2
$ balena logs 23c73a1.local --system
$ balena logs 23c73a1.local --system --service my-service`,
options: [
{
signature: 'tail',
description: 'continuously stream output',
boolean: true,
alias: 't',
},
{
signature: 'service',
description: stripIndent`
Reject logs not originating from this service.
This can be used in combination with --system or other --service flags.`,
parameter: 'service',
alias: 's',
},
{
signature: 'system',
alias: 'S',
boolean: true,
description:
'Only show system logs. This can be used in combination with --service.',
},
],
primary: true,
async action(params, options, done) {
normalizeUuidProp(params);
const balena = (await import('balena-sdk')).fromSharedOptions();
const isArray = await import('lodash/isArray');
const { serviceIdToName } = await import('../utils/cloud');
const { displayDeviceLogs, displayLogObject } = await import(
'../utils/device/logs'
);
const { validateIPAddress } = await import('../utils/validation');
const { exitIfNotLoggedIn, exitWithExpectedError } = await import(
'../utils/patterns'
);
const Logger = await import('../utils/logger');
const logger = Logger.getLogger();
const servicesToDisplay =
options.service != null
? isArray(options.service)
? options.service
: [options.service]
: undefined;
const displayCloudLog = async (line: CloudLog) => {
if (!line.isSystem) {
let serviceName = await serviceIdToName(balena, line.serviceId);
if (serviceName == null) {
serviceName = 'Unknown service';
}
displayLogObject(
{ serviceName, ...line },
logger,
options.system || false,
servicesToDisplay,
);
} else {
displayLogObject(
line,
logger,
options.system || false,
servicesToDisplay,
);
}
};
if (
validateIPAddress(params.uuidOrDevice) ||
validateDotLocalUrl(params.uuidOrDevice)
) {
const { DeviceAPI } = await import('../utils/device/api');
const deviceApi = new DeviceAPI(logger, params.uuidOrDevice);
logger.logDebug('Checking we can access device');
try {
await deviceApi.ping();
} catch (e) {
exitWithExpectedError(
new Error(
`Cannot access local mode device at address ${params.uuidOrDevice}`,
),
);
}
const logStream = await deviceApi.getLogStream();
displayDeviceLogs(
logStream,
logger,
options.system || false,
servicesToDisplay,
);
} else {
await exitIfNotLoggedIn();
if (options.tail) {
return balena.logs
.subscribe(params.uuidOrDevice, { count: 100 })
.then(function(logStream) {
logStream.on('line', displayCloudLog);
logStream.on('error', done);
})
.catch(done);
} else {
return balena.logs
.history(params.uuidOrDevice)
.each(displayCloudLog)
.catch(done);
}
}
},
};

View File

@ -1,5 +1,5 @@
###
Copyright 2016-2017 Resin.io
Copyright 2016-2017 Balena
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
###
{ normalizeUuidProp } = require('../utils/normalization')
exports.set =
signature: 'note <|note>'
description: 'set a device note'
@ -22,12 +24,12 @@ exports.set =
If note command isn't passed, the tool attempts to read from `stdin`.
To view the notes, use $ resin device <uuid>.
To view the notes, use $ balena device <uuid>.
Examples:
$ resin note "My useful note" --device 7cf02a6
$ cat note.txt | resin note --device 7cf02a6
$ balena note "My useful note" --device 7cf02a6
$ cat note.txt | balena note --device 7cf02a6
'''
options: [
signature: 'device'
@ -38,13 +40,16 @@ exports.set =
]
permission: 'user'
action: (params, options, done) ->
normalizeUuidProp(options, 'device')
Promise = require('bluebird')
_ = require('lodash')
resin = require('resin-sdk-preconfigured')
balena = require('balena-sdk').fromSharedOptions()
{ exitWithExpectedError } = require('../utils/patterns')
Promise.try ->
if _.isEmpty(params.note)
throw new Error('Missing note content')
exitWithExpectedError('Missing note content')
resin.models.device.note(options.device, params.note)
balena.models.device.note(options.device, params.note)
.nodeify(done)

View File

@ -1,5 +1,5 @@
###
Copyright 2016-2017 Resin.io
Copyright 2016-2019 Balena
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -30,9 +30,9 @@ resolveVersion = (deviceType, version) ->
return Promise.resolve(version)
form = require('resin-cli-form')
resin = require('resin-sdk-preconfigured')
balena = require('balena-sdk').fromSharedOptions()
resin.models.os.getSupportedVersions(deviceType)
balena.models.os.getSupportedVersions(deviceType)
.then ({ versions, recommended }) ->
choices = versions.map (v) ->
value: v
@ -46,19 +46,19 @@ resolveVersion = (deviceType, version) ->
exports.versions =
signature: 'os versions <type>'
description: 'show the available resinOS versions for the given device type'
description: 'show the available balenaOS versions for the given device type'
help: '''
Use this command to show the available resinOS versions for a certain device type.
Check available types with `resin devices supported`
Use this command to show the available balenaOS versions for a certain device type.
Check available types with `balena devices supported`
Example:
$ resin os versions raspberrypi3
$ balena os versions raspberrypi3
'''
action: (params, options, done) ->
resin = require('resin-sdk-preconfigured')
balena = require('balena-sdk').fromSharedOptions()
resin.models.os.getSupportedVersions(params.type)
balena.models.os.getSupportedVersions(params.type)
.then ({ versions, recommended }) ->
versions.forEach (v) ->
console.log(formatVersion(v, v is recommended))
@ -68,7 +68,7 @@ exports.download =
description: 'download an unconfigured os image'
help: '''
Use this command to download an unconfigured os image for a certain device type.
Check available types with `resin devices supported`
Check available types with `balena devices supported`
If version is not specified the newest stable (non-pre-release) version of OS
is downloaded if available, or the newest version otherwise (if all existing
@ -79,12 +79,12 @@ exports.download =
Examples:
$ resin os download raspberrypi3 -o ../foo/bar/raspberry-pi.img
$ resin os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version 1.24.1
$ resin os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version ^1.20.0
$ resin os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version latest
$ resin os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version default
$ resin os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version menu
$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img
$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version 1.24.1
$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version ^1.20.0
$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version latest
$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version default
$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version menu
'''
permission: 'user'
options: [
@ -95,14 +95,14 @@ exports.download =
alias: 'o'
required: 'You have to specify the output location'
}
commandOptions.osVersion
commandOptions.osVersionOrSemver
]
action: (params, options, done) ->
Promise = require('bluebird')
unzip = require('unzip2')
fs = require('fs')
rindle = require('rindle')
manager = require('resin-image-manager')
manager = require('balena-image-manager')
visuals = require('resin-cli-visuals')
console.info("Getting device operating system for #{params.type}")
@ -146,33 +146,39 @@ exports.download =
console.info('The image was downloaded successfully')
.nodeify(done)
buildConfig = (image, deviceType, advanced = false) ->
buildConfigForDeviceType = (deviceType, advanced = false) ->
form = require('resin-cli-form')
helpers = require('../utils/helpers')
helpers.getManifest(image, deviceType)
.get('options')
.then (questions) ->
if not advanced
advancedGroup = _.find questions,
name: 'advanced'
isGroup: true
questions = deviceType.options
if not advanced
advancedGroup = _.find questions,
name: 'advanced'
isGroup: true
if advancedGroup?
override = helpers.getGroupDefaults(advancedGroup)
if advancedGroup?
override = helpers.getGroupDefaults(advancedGroup)
return form.run(questions, { override })
return form.run(questions, { override })
buildConfig = (image, deviceTypeSlug, advanced = false) ->
Promise = require('bluebird')
helpers = require('../utils/helpers')
Promise.resolve(helpers.getManifest(image, deviceTypeSlug))
.then (deviceTypeManifest) ->
buildConfigForDeviceType(deviceTypeManifest, advanced)
exports.buildConfig =
signature: 'os build-config <image> <device-type>'
description: 'build the OS config and save it to the JSON file'
help: '''
Use this command to prebuild the OS config once and skip the interactive part of `resin os configure`.
Use this command to prebuild the OS config once and skip the interactive part of `balena os configure`.
Example:
$ resin os build-config ../path/rpi3.img raspberrypi3 --output rpi3-config.json
$ resin os configure ../path/rpi3.img 7cf02a6 --config "$(cat rpi3-config.json)"
$ balena os build-config ../path/rpi3.img raspberrypi3 --output rpi3-config.json
$ balena os configure ../path/rpi3.img --device 7cf02a6 --config rpi3-config.json
'''
permission: 'user'
options: [
@ -195,95 +201,6 @@ exports.buildConfig =
writeFileAsync(options.output, JSON.stringify(answers, null, 4))
.nodeify(done)
exports.configure =
signature: 'os configure <image> [uuid] [deviceApiKey]'
description: 'configure an os image'
help: '''
Use this command to configure a previously downloaded operating system image for
the specific device or for an application generally.
Note that device api keys are only supported on ResinOS 2.0.3+.
This comand still supports the *deprecated* format where the UUID and optionally device key
are passed directly on the command line, but the recommended way is to pass either an --app or
--device argument. The deprecated format will be remove in a future release.
Examples:
$ resin os configure ../path/rpi.img --device 7cf02a6
$ resin os configure ../path/rpi.img --device 7cf02a6 --deviceApiKey <existingDeviceKey>
$ resin os configure ../path/rpi.img --app MyApp
'''
permission: 'user'
options: [
commandOptions.advancedConfig
commandOptions.optionalApplication
commandOptions.optionalDevice
commandOptions.optionalDeviceApiKey
{
signature: 'config'
description: 'path to the config JSON file, see `resin os build-config`'
parameter: 'config'
}
]
action: (params, options, done) ->
fs = require('fs')
Promise = require('bluebird')
readFileAsync = Promise.promisify(fs.readFile)
resin = require('resin-sdk-preconfigured')
init = require('resin-device-init')
helpers = require('../utils/helpers')
patterns = require('../utils/patterns')
{ generateDeviceConfig, generateApplicationConfig } = require('../utils/config')
if _.filter([
options.device
options.application
params.uuid
]).length != 1
patterns.expectedError '''
To configure an image, you must provide exactly one of:
* A device, with --device <uuid>
* An application, with --app <appname>
* [Deprecated] A device, passing its uuid directly on the command line
See the help page for examples:
$ resin help os configure
'''
if params.uuid
console.warn(
'Directly passing a UUID to `resin os configure` is deprecated. Pass it with --uuid <uuid> instead.' +
if params.deviceApiKey
' Device api keys can be passed with --deviceApiKey.\n'
else '\n'
)
uuid = options.device || params.uuid
deviceApiKey = options.deviceApiKey || params.deviceApiKey
console.info('Configuring operating system image')
configurationResourceType = if uuid then 'device' else 'application'
resin.models[configurationResourceType].get(uuid || options.application)
.then (appOrDevice) ->
Promise.try ->
if options.config
return readFileAsync(options.config, 'utf8')
.then(JSON.parse)
return buildConfig(params.image, appOrDevice.device_type, options.advanced)
.then (answers) ->
(if configurationResourceType == 'device'
generateDeviceConfig(appOrDevice, deviceApiKey, answers)
else
generateApplicationConfig(appOrDevice, answers)
).then (config) ->
init.configure(params.image, appOrDevice.device_type, config, answers)
.then(helpers.osProgressHandler)
.nodeify(done)
INIT_WARNING_MESSAGE = '''
Note: Initializing the device may ask for administrative permissions
because we need to access the raw devices directly.
@ -299,14 +216,14 @@ exports.initialize =
Examples:
$ resin os initialize ../path/rpi.img --type 'raspberry-pi'
$ balena os initialize ../path/rpi.img --type 'raspberry-pi'
"""
permission: 'user'
options: [
commandOptions.yes
{
signature: 'type'
description: 'device type (Check available types with `resin devices supported`)'
description: 'device type (Check available types with `balena devices supported`)'
parameter: 'type'
alias: 't'
required: 'You have to specify a device type'
@ -325,7 +242,7 @@ exports.initialize =
#{INIT_WARNING_MESSAGE}
""")
helpers.getManifest(params.image, options.type)
Promise.resolve(helpers.getManifest(params.image, options.type))
.then (manifest) ->
return manifest.initialization?.options
.then (questions) ->
@ -338,6 +255,7 @@ exports.initialize =
options.yes
"This will erase #{answers.drive}. Are you sure?"
"Going to erase #{answers.drive}."
true
)
.return(answers.drive)
.then(umountAsync)
@ -352,7 +270,7 @@ exports.initialize =
.then (answers) ->
return if not answers.drive?
# TODO: resin local makes use of ejectAsync, see below
# TODO: balena local makes use of ejectAsync, see below
# DO we need this / should we do that here?
# getDrive = (drive) ->

View File

@ -1,5 +1,5 @@
###
Copyright 2016-2017 Resin.io
Copyright 2016-2017 Balena
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -14,42 +14,70 @@ See the License for the specific language governing permissions and
limitations under the License.
###
_ = require('lodash')
dockerUtils = require('../utils/docker')
LATEST = 'latest'
allDeviceTypes = undefined
isCurrent = (commit) ->
return commit == 'latest' or commit == 'current'
getDeviceTypes = ->
Bluebird = require('bluebird')
_ = require('lodash')
if allDeviceTypes != undefined
return Bluebird.resolve(allDeviceTypes)
balena = require('balena-sdk').fromSharedOptions()
balena.models.config.getDeviceTypes()
.then (deviceTypes) ->
_.sortBy(deviceTypes, 'name')
.tap (dt) ->
allDeviceTypes = dt
getDeviceTypesWithSameArch = (deviceTypeSlug) ->
_ = require('lodash')
getDeviceTypes()
.then (deviceTypes) ->
deviceType = _.find(deviceTypes, slug: deviceTypeSlug)
_(deviceTypes).filter(arch: deviceType.arch).map('slug').value()
getApplicationsWithSuccessfulBuilds = (deviceType) ->
preload = require('resin-preload')
resin = require('resin-sdk-preconfigured')
preload = require('balena-preload')
balena = require('balena-sdk').fromSharedOptions()
resin.pine.get
resource: 'my_application'
options:
filter:
device_type: deviceType
build:
$any:
$alias: 'b'
$expr:
b:
status: 'success'
expand: preload.applicationExpandOptions
select: [ 'id', 'app_name', 'device_type', 'commit' ]
orderby: 'app_name asc'
getDeviceTypesWithSameArch(deviceType)
.then (deviceTypes) ->
balena.pine.get
resource: 'my_application'
options:
$filter:
device_type:
$in: deviceTypes
owns__release:
$any:
$alias: 'r'
$expr:
r:
status: 'success'
$expand: preload.applicationExpandOptions
$select: [ 'id', 'app_name', 'device_type', 'commit', 'should_track_latest_release' ]
$orderby: 'app_name asc'
selectApplication = (deviceType) ->
visuals = require('resin-cli-visuals')
form = require('resin-cli-form')
{ expectedError } = require('../utils/patterns')
{ exitWithExpectedError } = require('../utils/patterns')
applicationInfoSpinner = new visuals.Spinner('Downloading list of applications and builds.')
applicationInfoSpinner = new visuals.Spinner('Downloading list of applications and releases.')
applicationInfoSpinner.start()
getApplicationsWithSuccessfulBuilds(deviceType)
.then (applications) ->
applicationInfoSpinner.stop()
if applications.length == 0
expectedError("You have no apps with successful builds for a '#{deviceType}' device type.")
exitWithExpectedError("You have no apps with successful releases for a '#{deviceType}' device type.")
form.ask
message: 'Select an application'
type: 'list'
@ -57,36 +85,41 @@ selectApplication = (deviceType) ->
name: app.app_name
value: app
selectApplicationCommit = (builds) ->
selectApplicationCommit = (releases) ->
form = require('resin-cli-form')
{ expectedError } = require('../utils/patterns')
{ exitWithExpectedError } = require('../utils/patterns')
if builds.length == 0
expectedError('This application has no successful builds.')
DEFAULT_CHOICE = {'name': LATEST, 'value': LATEST}
choices = [ DEFAULT_CHOICE ].concat builds.map (build) ->
name: "#{build.push_timestamp} - #{build.commit_hash}"
value: build.commit_hash
if releases.length == 0
exitWithExpectedError('This application has no successful releases.')
DEFAULT_CHOICE = { 'name': 'current', 'value': 'current' }
choices = [ DEFAULT_CHOICE ].concat releases.map (release) ->
name: "#{release.end_timestamp} - #{release.commit}"
value: release.commit
return form.ask
message: 'Select a build'
message: 'Select a release'
type: 'list'
default: LATEST
default: 'current'
choices: choices
offerToDisableAutomaticUpdates = (application, commit) ->
offerToDisableAutomaticUpdates = (application, commit, pinDevice) ->
Promise = require('bluebird')
resin = require('resin-sdk-preconfigured')
balena = require('balena-sdk').fromSharedOptions()
form = require('resin-cli-form')
if commit == LATEST or not application.should_track_latest_release
if isCurrent(commit) or not application.should_track_latest_release or pinDevice
return Promise.resolve()
message = '''
This application is set to automatically update all devices to the latest available version.
This application is set to automatically update all devices to the current version.
This might be unexpected behaviour: with this enabled, the preloaded device will still
download and install the latest build once it is online.
download and install the current release once it is online.
Do you want to disable automatic updates for this application?
Warning: To re-enable this requires direct api calls,
see https://balena.io/docs/reference/api/resources/device/#set-device-to-release
Alternatively you can pass the --pin-device-to-release flag to pin only this device to the selected release.
'''
form.ask
message: message,
@ -94,69 +127,90 @@ offerToDisableAutomaticUpdates = (application, commit) ->
.then (update) ->
if not update
return
resin.pine.patch
balena.pine.patch
resource: 'application'
id: application.id
body:
should_track_latest_release: false
preloadOptions = dockerUtils.appendConnectionOptions [
{
signature: 'app'
parameter: 'appId'
description: 'id of the application to preload'
alias: 'a'
}
{
signature: 'commit'
parameter: 'hash'
description: '''
The commit hash for a specific application release to preload, use "current" to specify the current
release (ignored if no appId is given). The current release is usually also the latest, but can be
manually pinned using https://github.com/balena-io-projects/staged-releases .
'''
alias: 'c'
}
{
signature: 'splash-image'
parameter: 'splashImage.png'
description: 'path to a png image to replace the splash screen'
alias: 's'
}
{
signature: 'dont-check-arch'
boolean: true
description: 'Disables check for matching architecture in image and application'
}
{
signature: 'pin-device-to-release'
boolean: true
description: 'Pin the preloaded device to the preloaded release on provision'
alias: 'p'
}
{
signature: 'add-certificate'
parameter: 'certificate.crt'
description: '''
Add the given certificate (in PEM format) to /etc/ssl/certs in the preloading container.
The file name must end with '.crt' and must not be already contained in the preloader's
/etc/ssl/certs folder.
Can be repeated to add multiple certificates.
'''
}
]
# Remove dockerPort `-p` alias as it conflicts with pin-device-to-release
delete _.find(preloadOptions, signature: 'dockerPort').alias
module.exports =
signature: 'preload <image>'
description: '(beta) preload an app on a disk image (or Edison zip archive)'
description: 'preload an app on a disk image (or Edison zip archive)'
help: '''
Warning: "resin preload" requires Docker to be correctly installed in
your shell environment. For more information (including Windows support)
please check the README here: https://github.com/resin-io/resin-cli .
Preload a balena application release (app images/containers), and optionally
a balenaOS splash screen, in a previously downloaded balenaOS image file (or
Edison zip archive) in the local disk. The balenaOS image file can then be
flashed to a device's SD card. When the device boots, it will not need to
download the application, as it was preloaded.
Use this command to preload an application to a local disk image (or
Edison zip archive) with a built commit from Resin.io.
This can be used with cloud builds, or images deployed with resin deploy.
Warning: "balena preload" requires Docker to be correctly installed in
your shell environment. For more information (including Windows support)
check: https://github.com/balena-io/balena-cli/blob/master/INSTALL.md
Examples:
$ resin preload resin.img --app 1234 --commit e1f2592fc6ee949e68756d4f4a48e49bff8d72a0 --splash-image some-image.png
$ resin preload resin.img
$ balena preload balena.img --app 1234 --commit e1f2592fc6ee949e68756d4f4a48e49bff8d72a0 --splash-image image.png
$ balena preload balena.img
'''
permission: 'user'
primary: true
options: dockerUtils.appendConnectionOptions [
{
signature: 'app'
parameter: 'appId'
description: 'id of the application to preload'
alias: 'a'
}
{
signature: 'commit'
parameter: 'hash'
description: '''
a specific application commit to preload, use "latest" to specify the latest commit
(ignored if no appId is given)
'''
alias: 'c'
}
{
signature: 'splash-image'
parameter: 'splashImage.png'
description: 'path to a png image to replace the splash screen'
alias: 's'
}
{
signature: 'dont-check-device-type'
boolean: true
description: 'Disables check for matching device types in image and application'
}
]
options: preloadOptions
action: (params, options, done) ->
_ = require('lodash')
Promise = require('bluebird')
resin = require('resin-sdk-preconfigured')
streamToPromise = require('stream-to-promise')
form = require('resin-cli-form')
preload = require('resin-preload')
errors = require('resin-errors')
balena = require('balena-sdk').fromSharedOptions()
preload = require('balena-preload')
visuals = require('resin-cli-visuals')
nodeCleanup = require('node-cleanup')
{ expectedError } = require('../utils/patterns')
{ exitWithExpectedError } = require('../utils/patterns')
progressBars = {}
@ -178,6 +232,7 @@ module.exports =
console.log()
spinner.stop()
options.commit = if isCurrent(options.commit) then 'latest' else options.commit
options.image = params.image
options.appId = options.app
delete options.app
@ -185,21 +240,39 @@ module.exports =
options.splashImage = options['splash-image']
delete options['splash-image']
if options['dont-check-device-type'] and not options.appId
expectedError('You need to specify an app id if you disable the device type check.')
options.dontCheckArch = options['dont-check-arch'] || false
delete options['dont-check-arch']
if options.dontCheckArch and not options.appId
exitWithExpectedError('You need to specify an app id if you disable the architecture check.')
options.pinDevice = options['pin-device-to-release'] || false
delete options['pin-device-to-release']
if _.isArray(options['add-certificate'])
certificates = options['add-certificate']
else if options['add-certificate'] == undefined
certificates = []
else
certificates = [ options['add-certificate'] ]
for certificate in certificates
if not certificate.endsWith('.crt')
exitWithExpectedError('Certificate file name must end with ".crt"')
# Get a configured dockerode instance
dockerUtils.getDocker(options)
.then (docker) ->
preloader = new preload.Preloader(
resin,
docker,
options.appId,
options.commit,
options.image,
options.splashImage,
options.proxy,
balena
docker
options.appId
options.commit
options.image
options.splashImage
options.proxy
options.dontCheckArch
options.pinDevice
certificates
)
gotSignal = false
@ -223,51 +296,39 @@ module.exports =
return new Promise (resolve, reject) ->
preloader.on('error', reject)
preloader.build()
preloader.prepare()
.then ->
preloader.prepare()
.then ->
preloader.getDeviceTypeAndPreloadedBuilds()
.then (info) ->
# If no appId was provided, show a list of matching apps
Promise.try ->
if options.appId
return preloader.fetchApplication()
.catch(errors.ResinApplicationNotFound, expectedError)
selectApplication(info.device_type)
.then (application) ->
preloader.setApplication(application)
# Check that the app device type and the image device type match
if not options['dont-check-device-type'] and info.device_type != application.device_type
expectedError(
"Image device type (#{info.device_type}) and application device type (#{application.device_type}) do not match"
)
if not preloader.appId
selectApplication(preloader.config.deviceType)
.then (application) ->
preloader.setApplication(application)
.then ->
# Use the commit given as --commit or show an interactive commit selection menu
Promise.try ->
if options.commit
if isCurrent(options.commit) and preloader.application.commit
# handle `--commit current` (and its `--commit latest` synonym)
return 'latest'
release = _.find preloader.application.owns__release, (release) ->
release.commit.startsWith(options.commit)
if not release
exitWithExpectedError('There is no release matching this commit')
return release.commit
selectApplicationCommit(preloader.application.owns__release)
.then (commit) ->
if isCurrent(commit)
preloader.commit = preloader.application.commit
else
preloader.commit = commit
# Use the commit given as --commit or show an interactive commit selection menu
Promise.try ->
if options.commit
if options.commit == LATEST and application.commit
# handle `--commit latest`
return LATEST
else if not _.find(application.build, commit_hash: options.commit)
expectedError('There is no build matching this commit')
return options.commit
selectApplicationCommit(application.build)
.then (commit) ->
if commit == LATEST
preloader.commit = application.commit
else
preloader.commit = commit
# Propose to disable automatic app updates if the commit is not the latest
offerToDisableAutomaticUpdates(application, commit)
.then ->
builds = info.preloaded_builds.map (build) ->
build.slice(-preload.BUILD_HASH_LENGTH)
if preloader.commit in builds
throw new preload.errors.ResinError('This build is already preloaded in this image.')
# All options are ready: preload the image.
preloader.preload()
.catch(preload.errors.ResinError, expectedError)
# Propose to disable automatic app updates if the commit is not the current release
offerToDisableAutomaticUpdates(preloader.application, commit, options.pinDevice)
.then ->
# All options are ready: preload the image.
preloader.preload()
.catch(balena.errors.BalenaError, exitWithExpectedError)
.then(resolve)
.catch(reject)
.then(done)

381
lib/actions/push.ts Normal file
View File

@ -0,0 +1,381 @@
/*
Copyright 2016-2019 Balena Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { BalenaSDK } from 'balena-sdk';
import { CommandDefinition } from 'capitano';
import { stripIndent } from 'common-tags';
import { registrySecretsHelp } from '../utils/messages';
import {
validateApplicationName,
validateDotLocalUrl,
validateIPAddress,
} from '../utils/validation';
enum BuildTarget {
Cloud,
Device,
}
function getBuildTarget(appOrDevice: string): BuildTarget | null {
// First try the application regex from the api
if (validateApplicationName(appOrDevice)) {
return BuildTarget.Cloud;
}
if (validateIPAddress(appOrDevice) || validateDotLocalUrl(appOrDevice)) {
return BuildTarget.Device;
}
return null;
}
async function getAppOwner(sdk: BalenaSDK, appName: string) {
const { exitWithExpectedError, selectFromList } = await import(
'../utils/patterns'
);
const _ = await import('lodash');
const applications = await sdk.models.application.getAll({
$expand: {
user: {
$select: ['username'],
},
},
$filter: {
$eq: [{ $tolower: { $: 'app_name' } }, appName.toLowerCase()],
},
$select: ['id'],
});
if (applications == null || applications.length === 0) {
exitWithExpectedError(
stripIndent`
No applications found with name: ${appName}.
This could mean that the application does not exist, or you do
not have the permissions required to access it.`,
);
}
if (applications.length === 1) {
return _.get(applications, '[0].user[0].username');
}
// If we got more than one application with the same name it means that the
// user has access to a collab app with the same name as a personal app. We
// present a list to the user which shows the fully qualified application
// name (user/appname) and allows them to select
const entries = _.map(applications, app => {
const username = _.get(app, 'user[0].username');
return {
name: `${username}/${appName}`,
extra: username,
};
});
const selected = await selectFromList(
`${
entries.length
} applications found with that name, please select the application you would like to push to`,
entries,
);
return selected.extra;
}
export const push: CommandDefinition<
{
// when Capitano converts a positional parameter (but not an option)
// to a number, the original value is preserved with the _raw suffix
applicationOrDevice: string;
applicationOrDevice_raw: string;
},
{
source?: string;
emulated?: boolean;
dockerfile?: string; // DeviceDeployOptions.dockerfilePath (alternative Dockerfile)
nocache?: boolean;
'registry-secrets'?: string;
nolive?: boolean;
detached?: boolean;
service?: string | string[];
system?: boolean;
env?: string | string[];
}
> = {
signature: 'push <applicationOrDevice>',
primary: true,
description:
'Start a remote build on the balena cloud build servers or a local mode device',
help: stripIndent`
This command can be used to start a build on the remote balena cloud builders,
or a local mode balena device.
When building on the balenaCloud servers, the given source directory will be
sent to the remote server. This can be used as a drop-in replacement for the
"git push" deployment method.
When building on a local mode device, the given source directory will be
built on the device, and the resulting containers will be run on the device.
Logs will be streamed back from the device as part of the same invocation.
The web dashboard can be used to switch a device to local mode:
https://www.balena.io/docs/learn/develop/local-mode/
Note that local mode requires a supervisor version of at least v7.21.0.
The logs from only a single service can be shown with the --service flag, and
showing only the system logs can be achieved with --system. Note that these
flags can be used together.
When pushing to a local device a live session will be started.
The project source folder is watched for filesystem events, and changes
to files and folders are automatically synchronized to the running
containers. The synchronisation is only in one direction, from this machine to
the device, and changes made on the device itself may be overwritten.
This feature requires a device running supervisor version v9.7.0 or greater.
${registrySecretsHelp.split('\n').join('\n\t\t')}
Examples:
$ balena push myApp
$ balena push myApp --source <source directory>
$ balena push myApp -s <source directory>
$ balena push 10.0.0.1
$ balena push 10.0.0.1 --source <source directory>
$ balena push 10.0.0.1 --service my-service
$ balena push 10.0.0.1 --env MY_ENV_VAR=value --env my-service:SERVICE_VAR=value
$ balena push 10.0.0.1 --nolive
$ balena push 23c73a1.local --system
$ balena push 23c73a1.local --system --service my-service
`,
options: [
{
signature: 'source',
alias: 's',
description:
'The source that should be sent to the balena builder to be built (defaults to the current directory)',
parameter: 'source',
},
{
signature: 'emulated',
alias: 'e',
description: 'Force an emulated build to occur on the remote builder',
boolean: true,
},
{
signature: 'dockerfile',
parameter: 'Dockerfile',
description:
'Alternative Dockerfile name/path, relative to the source folder',
},
{
signature: 'nocache',
alias: 'c',
description: "Don't use cache when building this project",
boolean: true,
},
{
signature: 'registry-secrets',
alias: 'R',
parameter: 'secrets.yml|.json',
description: stripIndent`
Path to a local YAML or JSON file containing Docker registry passwords used to pull base images.
Note that if registry-secrets are not provided on the command line, a secrets configuration
file from the balena directory will be used (usually $HOME/.balena/secrets.yml|.json)`,
},
{
signature: 'nolive',
boolean: true,
description: stripIndent`
Don't run a live session on this push. The filesystem will not be monitored, and changes
will not be synchronised to any running containers. Note that both this flag and --detached
and required to cause the process to end once the initial build has completed.`,
},
{
signature: 'detached',
alias: 'd',
description: stripIndent`
When pushing to the cloud, this option will cause the build to start, then return execution
back to the shell, with the status and release ID (if applicable).
When pushing to a local mode device, this option will cause the command to not tail application logs when the build
has completed.`,
boolean: true,
},
{
signature: 'service',
description: stripIndent`
Reject logs not originating from this service.
This can be used in combination with --system and other --service flags.
Only valid when pushing to a local mode device.`,
parameter: 'service',
},
{
signature: 'system',
description: stripIndent`
Only show system logs. This can be used in combination with --service.
Only valid when pushing to a local mode device.`,
boolean: true,
},
{
signature: 'env',
parameter: 'env',
description: stripIndent`
When performing a push to device, run the built containers with environment
variables provided with this argument. Environment variables can be applied
to individual services by adding their service name before the argument,
separated by a colon, e.g:
--env main:MY_ENV=value
Note that if the service name cannot be found in the composition, the entire
left hand side of the = character will be treated as the variable name.
`,
},
],
async action(params, options, done) {
const sdk = (await import('balena-sdk')).fromSharedOptions();
const Bluebird = await import('bluebird');
const isArray = await import('lodash/isArray');
const remote = await import('../utils/remote-build');
const deviceDeploy = await import('../utils/device/deploy');
const { exitIfNotLoggedIn, exitWithExpectedError } = await import(
'../utils/patterns'
);
const { validateSpecifiedDockerfile, getRegistrySecrets } = await import(
'../utils/compose_ts'
);
const { BuildError } = await import('../utils/device/errors');
const appOrDevice: string | null =
params.applicationOrDevice_raw || params.applicationOrDevice;
if (appOrDevice == null) {
exitWithExpectedError('You must specify an application or a device');
}
const source = options.source || '.';
if (process.env.DEBUG) {
console.error(`[debug] Using ${source} as build source`);
}
const dockerfilePath = validateSpecifiedDockerfile(
source,
options.dockerfile,
);
const registrySecrets = await getRegistrySecrets(
sdk,
options['registry-secrets'],
);
const buildTarget = getBuildTarget(appOrDevice);
switch (buildTarget) {
case BuildTarget.Cloud:
// Ensure that the live argument has not been passed to a cloud build
if (options.nolive != null) {
exitWithExpectedError(
'The --nolive flag is only valid when pushing to a local mode device',
);
}
if (options.service) {
exitWithExpectedError(
'The --service flag is only valid when pushing to a local mode device.',
);
}
if (options.system) {
exitWithExpectedError(
'The --system flag is only valid when pushing to a local mode device.',
);
}
if (options.env) {
exitWithExpectedError(
'The --env flag is only valid when pushing to a local mode device.',
);
}
const app = appOrDevice;
await exitIfNotLoggedIn();
await Bluebird.join(
sdk.auth.getToken(),
sdk.settings.get('balenaUrl'),
getAppOwner(sdk, app),
async (token, baseUrl, owner) => {
const opts = {
dockerfilePath,
emulated: options.emulated || false,
nocache: options.nocache || false,
registrySecrets,
headless: options.detached || false,
};
const args = {
app,
owner,
source,
auth: token,
baseUrl,
sdk,
opts,
};
return await remote.startRemoteBuild(args);
},
).nodeify(done);
break;
case BuildTarget.Device:
const device = appOrDevice;
const servicesToDisplay =
options.service != null
? isArray(options.service)
? options.service
: [options.service]
: undefined;
// TODO: Support passing a different port
await Bluebird.resolve(
deviceDeploy.deployToDevice({
source,
deviceHost: device,
dockerfilePath,
registrySecrets,
nocache: options.nocache || false,
nolive: options.nolive || false,
detached: options.detached || false,
services: servicesToDisplay,
system: options.system || false,
env:
typeof options.env === 'string'
? [options.env]
: options.env || [],
}),
)
.catch(BuildError, e => {
exitWithExpectedError(e.toString());
})
.nodeify(done);
break;
default:
exitWithExpectedError(
stripIndent`
Build target not recognised. Please provide either an application name or device address.
The only supported device addresses currently are IP addresses.
If you believe your build target should have been detected, and this is an error, please
create an issue.`,
);
break;
}
},
};

View File

@ -1,5 +1,5 @@
###
Copyright 2017 Resin.io
Copyright 2017 Balena
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -33,15 +33,15 @@ dockerVersionProperties = [
]
module.exports =
signature: 'local scan'
description: 'Scan for resinOS devices in your local network'
signature: 'scan'
description: 'Scan for balenaOS devices in your local network'
help: '''
Examples:
$ resin local scan
$ resin local scan --timeout 120
$ resin local scan --verbose
$ balena scan
$ balena scan --timeout 120
$ balena scan --verbose
'''
options: [
signature: 'verbose'
@ -60,30 +60,31 @@ module.exports =
Promise = require('bluebird')
_ = require('lodash')
prettyjson = require('prettyjson')
Docker = require('docker-toolbelt')
{ discover } = require('resin-sync')
{ discover } = require('balena-sync')
{ SpinnerPromise } = require('resin-cli-visuals')
{ dockerPort, dockerTimeout } = require('./common')
{ dockerPort, dockerTimeout } = require('./local/common')
dockerUtils = require('../utils/docker')
{ exitWithExpectedError } = require('../utils/patterns')
if options.timeout?
options.timeout *= 1000
Promise.try ->
new SpinnerPromise
promise: discover.discoverLocalResinOsDevices(options.timeout)
startMessage: 'Scanning for local resinOS devices..'
promise: discover.discoverLocalBalenaOsDevices(options.timeout)
startMessage: 'Scanning for local balenaOS devices..'
stopMessage: 'Reporting scan results'
.filter ({ address }) ->
Promise.try ->
docker = new Docker(host: address, port: dockerPort, timeout: dockerTimeout)
docker = dockerUtils.createClient(host: address, port: dockerPort, timeout: dockerTimeout)
docker.pingAsync()
.return(true)
.catchReturn(false)
.tap (devices) ->
if _.isEmpty(devices)
throw new Error('Could not find any resinOS devices in the local network')
exitWithExpectedError('Could not find any balenaOS devices in the local network')
.map ({ host, address }) ->
docker = new Docker(host: address, port: dockerPort, timeout: dockerTimeout)
docker = dockerUtils.createClient(host: address, port: dockerPort, timeout: dockerTimeout)
Promise.props
dockerInfo: docker.infoAsync().catchReturn('Could not get Docker info')
dockerVersion: docker.versionAsync().catchReturn('Could not get Docker version')

View File

@ -1,5 +1,5 @@
###
Copyright 2016-2017 Resin.io
/*
Copyright 2016-2017 Balena
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -12,23 +12,28 @@ 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.
###
*/
exports.list =
signature: 'settings'
description: 'print current settings'
help: '''
Use this command to display detected settings
import { CommandDefinition } from 'capitano';
Examples:
export const list: CommandDefinition = {
signature: 'settings',
description: 'print current settings',
help: `\
Use this command to display detected settings
$ resin settings
'''
action: (params, options, done) ->
resin = require('resin-sdk-preconfigured')
prettyjson = require('prettyjson')
Examples:
resin.settings.getAll()
$ balena settings\
`,
async action(_params, _options, done) {
const balena = (await import('balena-sdk')).fromSharedOptions();
const prettyjson = await import('prettyjson');
return balena.settings
.getAll()
.then(prettyjson.render)
.then(console.log)
.nodeify(done)
.nodeify(done);
},
};

View File

@ -1,130 +0,0 @@
###
Copyright 2016-2017 Resin.io
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.
###
module.exports =
signature: 'ssh [uuid]'
description: '(beta) get a shell into the running app container of a device'
help: '''
Warning: 'resin ssh' requires an openssh-compatible client to be correctly
installed in your shell environment. For more information (including Windows
support) please check the README here: https://github.com/resin-io/resin-cli
Use this command to get a shell into the running application container of
your device.
Examples:
$ resin ssh MyApp
$ resin ssh 7cf02a6
$ resin ssh 7cf02a6 --port 8080
$ resin ssh 7cf02a6 -v
'''
permission: 'user'
primary: true
options: [
signature: 'port'
parameter: 'port'
description: 'ssh gateway port'
alias: 'p'
,
signature: 'verbose'
boolean: true
description: 'increase verbosity'
alias: 'v'
,
signature: 'noproxy'
boolean: true
description: "don't use the proxy configuration for this connection.
Only makes sense if you've configured proxy globally."
]
action: (params, options, done) ->
child_process = require('child_process')
Promise = require('bluebird')
resin = require('resin-sdk-preconfigured')
_ = require('lodash')
bash = require('bash')
hasbin = require('hasbin')
{ getSubShellCommand } = require('../utils/helpers')
patterns = require('../utils/patterns')
options.port ?= 22
verbose = if options.verbose then '-vvv' else ''
proxyConfig = global.PROXY_CONFIG
useProxy = !!proxyConfig and not options.noproxy
getSshProxyCommand = (hasTunnelBin) ->
return '' if not useProxy
if not hasTunnelBin
console.warn('''
Proxy is enabled but the `proxytunnel` binary cannot be found.
Please install it if you want to route the `resin ssh` requests through the proxy.
Alternatively you can pass `--noproxy` param to the `resin ssh` command to ignore the proxy config
for the `ssh` requests.
Attemmpting the unproxied request for now.
''')
return ''
tunnelOptions =
proxy: "#{proxyConfig.host}:#{proxyConfig.port}"
dest: '%h:%p'
{ proxyAuth } = proxyConfig
if proxyAuth
i = proxyAuth.indexOf(':')
_.assign tunnelOptions,
user: proxyAuth.substring(0, i)
pass: proxyAuth.substring(i + 1)
proxyCommand = "proxytunnel #{bash.args(tunnelOptions, '--', '=')}"
return "-o #{bash.args({ ProxyCommand: proxyCommand }, '', '=')}"
Promise.try ->
return false if not params.uuid
return resin.models.device.has(params.uuid)
.then (uuidExists) ->
return params.uuid if uuidExists
return patterns.inferOrSelectDevice()
.then (uuid) ->
console.info("Connecting to: #{uuid}")
resin.models.device.get(uuid)
.then (device) ->
throw new Error('Device is not online') if not device.is_online
Promise.props
username: resin.auth.whoami()
uuid: device.uuid
# get full uuid
containerId: resin.models.device.getApplicationInfo(device.uuid).get('containerId')
proxyUrl: resin.settings.get('proxyUrl')
hasTunnelBin: if useProxy then hasbin('proxytunnel') else null
.then ({ username, uuid, containerId, proxyUrl, hasTunnelBin }) ->
throw new Error('Did not find running application container') if not containerId?
Promise.try ->
sshProxyCommand = getSshProxyCommand(hasTunnelBin)
command = "ssh #{verbose} -t \
-o LogLevel=ERROR \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
#{sshProxyCommand} \
-p #{options.port} #{username}@ssh.#{proxyUrl} enter #{uuid} #{containerId}"
subShellCommand = getSubShellCommand(command)
child_process.spawn subShellCommand.program, subShellCommand.args,
stdio: 'inherit'
.nodeify(done)

372
lib/actions/ssh.ts Normal file
View File

@ -0,0 +1,372 @@
/*
Copyright 2016-2019 Balena
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import * as BalenaSdk from 'balena-sdk';
import { CommandDefinition } from 'capitano';
import { stripIndent } from 'common-tags';
import { BalenaDeviceNotFound } from 'balena-errors';
import { validateDotLocalUrl, validateIPAddress } from '../utils/validation';
async function getContainerId(
sdk: BalenaSdk.BalenaSDK,
uuid: string,
serviceName: string,
sshOpts: {
port?: number;
proxyCommand?: string;
proxyUrl: string;
username: string;
},
version?: string,
id?: number,
): Promise<string> {
const semver = await import('resin-semver');
if (version == null || id == null) {
const device = await sdk.models.device.get(uuid, {
$select: ['id', 'supervisor_version'],
});
version = device.supervisor_version;
id = device.id;
}
let containerId: string | undefined;
if (semver.gte(version, '8.6.0')) {
const apiUrl = await sdk.settings.get('apiUrl');
// TODO: Move this into the SDKs device model
const request = await sdk.request.send({
method: 'POST',
url: '/supervisor/v2/containerId',
baseUrl: apiUrl,
body: {
method: 'GET',
deviceId: id,
},
});
if (request.status !== 200) {
throw new Error(
`There was an error connecting to device ${uuid}, HTTP response code: ${
request.status
}.`,
);
}
const body = request.body;
if (body.status !== 'success') {
throw new Error(
`There was an error communicating with device ${uuid}.\n\tError: ${
body.message
}`,
);
}
containerId = body.services[serviceName];
} else {
console.log(stripIndent`
Using legacy method to detect container ID. This will be slow.
To speed up this process, please update your device to an OS
which has a supervisor version of at least v8.6.0.
`);
// We need to execute a balena ps command on the device,
// and parse the output, looking for a specific
// container
const { child_process } = await import('mz');
const escapeRegex = await import('lodash/escapeRegExp');
const { getSubShellCommand } = await import('../utils/helpers');
const { deviceContainerEngineBinary } = await import('../utils/device/ssh');
const command = generateVpnSshCommand({
uuid,
verbose: false,
port: sshOpts.port,
command: `host ${uuid} '"${deviceContainerEngineBinary}" ps --format "{{.ID}} {{.Names}}"'`,
proxyCommand: sshOpts.proxyCommand,
proxyUrl: sshOpts.proxyUrl,
username: sshOpts.username,
});
const subShellCommand = getSubShellCommand(command);
const subprocess = child_process.spawn(
subShellCommand.program,
subShellCommand.args,
{
stdio: [null, 'pipe', null],
},
);
const containers = await new Promise<string>((resolve, reject) => {
let output = '';
subprocess.stdout.on('data', chunk => (output += chunk.toString()));
subprocess.on('close', (code: number) => {
if (code !== 0) {
reject(
new Error(
`Non-zero error code when looking for service container: ${code}`,
),
);
} else {
resolve(output);
}
});
});
const lines = containers.split('\n');
const regex = new RegExp(`\\/?${escapeRegex(serviceName)}_\\d+_\\d+`);
for (const container of lines) {
const [cId, name] = container.split(' ');
if (regex.test(name)) {
containerId = cId;
break;
}
}
}
if (containerId == null) {
throw new Error(
`Could not find a service ${serviceName} on device ${uuid}.`,
);
}
return containerId;
}
function generateVpnSshCommand(opts: {
uuid: string;
command: string;
verbose: boolean;
port?: number;
username: string;
proxyUrl: string;
proxyCommand?: string;
}) {
return (
`ssh ${
opts.verbose ? '-vvv' : ''
} -t -o LogLevel=ERROR -o StrictHostKeyChecking=no ` +
`-o UserKnownHostsFile=/dev/null ` +
`${opts.proxyCommand != null ? opts.proxyCommand : ''} ` +
`${opts.port != null ? `-p ${opts.port}` : ''} ` +
`${opts.username}@ssh.${opts.proxyUrl} ${opts.command}`
);
}
export const ssh: CommandDefinition<
{
applicationOrDevice: string;
// when Capitano converts a positional parameter (but not an option)
// to a number, the original value is preserved with the _raw suffix
applicationOrDevice_raw: string;
serviceName?: string;
},
{
port: string;
service: string;
verbose: true | undefined;
noProxy: boolean;
}
> = {
signature: 'ssh <applicationOrDevice> [serviceName]',
description: 'SSH into the host or application container of a device',
primary: true,
help: stripIndent`
This command can be used to start a shell on a local or remote device.
If a service name is not provided, a shell will be opened on the host OS.
If an application name is provided, all online devices in the application
will be presented, and the chosen device will then have a shell opened on
in it's service container or host OS.
For local devices, the IP address and .local domain name are supported.
If the device is referenced by IP or \`.local\` address, the connection
is initiated directly to balenaOS on port \`22222\` via an
openssh-compatible client. Otherwise, any connection initiated remotely
traverses the balenaCloud VPN.
Examples:
balena ssh MyApp
balena ssh f49cefd
balena ssh f49cefd my-service
balena ssh f49cefd --port <port>
balena ssh 192.168.0.1 --verbose
balena ssh f49cefd.local my-service
Warning: \`balena ssh\` requires an openssh-compatible client to be correctly
installed in your shell environment. For more information (including Windows
support) please check:
https://github.com/balena-io/balena-cli/blob/master/INSTALL.md#additional-dependencies`,
options: [
{
signature: 'port',
parameter: 'port',
description: 'SSH gateway port',
alias: 'p',
},
{
signature: 'verbose',
boolean: true,
description: 'Increase verbosity',
alias: 'v',
},
{
signature: 'noproxy',
boolean: true,
description: stripIndent`
Don't use the proxy configuration for this connection. This flag
only make sense if you've configured a proxy globally.`,
},
],
action: async (params, options) => {
const applicationOrDevice =
params.applicationOrDevice_raw || params.applicationOrDevice;
const bash = await import('bash');
// TODO: Make this typed
const hasbin = require('hasbin');
const { getSubShellCommand } = await import('../utils/helpers');
const { child_process } = await import('mz');
const {
exitIfNotLoggedIn,
exitWithExpectedError,
getOnlineTargetUuid,
} = await import('../utils/patterns');
const sdk = BalenaSdk.fromSharedOptions();
const verbose = options.verbose === true;
// ugh TODO: Fix this
const proxyConfig = (global as any).PROXY_CONFIG;
const useProxy = !!proxyConfig && !options.noProxy;
const port = options.port != null ? parseInt(options.port, 10) : undefined;
// if we're doing a direct SSH connection locally...
if (
validateDotLocalUrl(applicationOrDevice) ||
validateIPAddress(applicationOrDevice)
) {
const { performLocalDeviceSSH } = await import('../utils/device/ssh');
return await performLocalDeviceSSH({
address: applicationOrDevice,
port,
verbose,
service: params.serviceName,
});
}
// this will be a tunnelled SSH connection...
await exitIfNotLoggedIn();
const uuid = await getOnlineTargetUuid(sdk, applicationOrDevice);
let version: string | undefined;
let id: number | undefined;
try {
const device = await sdk.models.device.get(uuid, {
$select: ['id', 'supervisor_version', 'is_online'],
});
id = device.id;
version = device.supervisor_version;
} catch (e) {
if (e instanceof BalenaDeviceNotFound) {
exitWithExpectedError(`Could not find device: ${uuid}`);
}
}
const [hasTunnelBin, username, proxyUrl] = await Promise.all([
useProxy ? await hasbin('proxytunnel') : undefined,
sdk.auth.whoami(),
sdk.settings.get('proxyUrl'),
]);
const getSshProxyCommand = () => {
if (!useProxy) {
return '';
}
if (!hasTunnelBin) {
console.warn(stripIndent`
Proxy is enabled but the \`proxytunnel\` binary cannot be found.
Please install it if you want to route the \`balena ssh\` requests through the proxy.
Alternatively you can pass \`--noproxy\` param to the \`balena ssh\` command to ignore the proxy config
for the \`ssh\` requests.
Attempting the unproxied request for now.`);
return '';
}
let tunnelOptions: Dictionary<string> = {
proxy: `${proxyConfig.host}:${proxyConfig.port}`,
dest: '%h:%p',
};
const { proxyAuth } = proxyConfig;
if (proxyAuth) {
const i = proxyAuth.indexOf(':');
tunnelOptions = {
user: proxyAuth.substring(0, i),
pass: proxyAuth.substring(i + 1),
...tunnelOptions,
};
}
const ProxyCommand = `proxytunnel ${bash.args(tunnelOptions, '--', '=')}`;
return `-o ${bash.args({ ProxyCommand }, '', '=')}`;
};
const proxyCommand = getSshProxyCommand();
if (username == null) {
exitWithExpectedError(
`Opening an SSH connection to a remote device requires you to be logged in.`,
);
}
// At this point, we have a long uuid with a device
// that we know exists and is accessible
let containerId: string | undefined;
if (params.serviceName != null) {
containerId = await getContainerId(
sdk,
uuid,
params.serviceName,
{
port,
proxyCommand,
proxyUrl,
username: username!,
},
version,
id,
);
}
let accessCommand: string;
if (containerId != null) {
accessCommand = `enter ${uuid} ${containerId}`;
} else {
accessCommand = `host ${uuid}`;
}
const command = generateVpnSshCommand({
uuid,
command: accessCommand,
verbose,
port,
proxyCommand,
proxyUrl,
username: username!,
});
const subShellCommand = getSubShellCommand(command);
await child_process.spawn(subShellCommand.program, subShellCommand.args, {
stdio: 'inherit',
});
},
};

View File

@ -1,17 +0,0 @@
###
Copyright 2016-2017 Resin.io
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.
###
module.exports = require('resin-sync').capitano('resin-cli')

303
lib/actions/tags.ts Normal file
View File

@ -0,0 +1,303 @@
/*
Copyright 2016-2018 Balena
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { ApplicationTag, DeviceTag, ReleaseTag } from 'balena-sdk';
import { CommandDefinition } from 'capitano';
import { stripIndent } from 'common-tags';
import {
disambiguateReleaseParam,
normalizeUuidProp,
} from '../utils/normalization';
import * as commandOptions from './command-options';
export const list: CommandDefinition<
{},
{
application?: string;
device?: string;
release?: number | string;
release_raw?: string;
}
> = {
signature: 'tags',
description: 'list all resource tags',
help: stripIndent`
Use this command to list all tags for
a particular application, device or release.
This command lists all application/device/release tags.
Example:
$ balena tags --application MyApp
$ balena tags --device 7cf02a6
$ balena tags --release 1234
$ balena tags --release b376b0e544e9429483b656490e5b9443b4349bd6
`,
options: [
commandOptions.optionalApplication,
commandOptions.optionalDevice,
commandOptions.optionalRelease,
],
permission: 'user',
async action(_params, options, done) {
normalizeUuidProp(options, 'device');
const Bluebird = await import('bluebird');
const _ = await import('lodash');
const balena = (await import('balena-sdk')).fromSharedOptions();
const visuals = await import('resin-cli-visuals');
const { exitWithExpectedError } = await import('../utils/patterns');
return Bluebird.try<ApplicationTag[] | DeviceTag[] | ReleaseTag[]>(
async () => {
const wrongParametersError = stripIndent`
To list resource tags, you must provide exactly one of:
* An application, with --application <appname>
* A device, with --device <uuid>
* A release, with --release <id or commit>
See the help page for examples:
$ balena help tags
`;
if (
_.filter([options.application, options.device, options.release])
.length !== 1
) {
return exitWithExpectedError(wrongParametersError);
}
if (options.application) {
return balena.models.application.tags.getAllByApplication(
options.application,
);
}
if (options.device) {
return balena.models.device.tags.getAllByDevice(options.device);
}
if (options.release) {
const releaseParam = await disambiguateReleaseParam(
balena,
options.release,
options.release_raw,
);
return balena.models.release.tags.getAllByRelease(releaseParam);
}
// return never, so that TS typings are happy
return exitWithExpectedError(wrongParametersError);
},
)
.tap(function(environmentVariables) {
if (_.isEmpty(environmentVariables)) {
exitWithExpectedError('No tags found');
}
console.log(
visuals.table.horizontal(environmentVariables, [
'id',
'tag_key',
'value',
]),
);
})
.nodeify(done);
},
};
export const set: CommandDefinition<
{
tagKey: string;
value?: string;
},
{
application?: string;
device?: string;
release?: number | string;
release_raw: string;
}
> = {
signature: 'tag set <tagKey> [value]',
description: 'set a resource tag',
help: stripIndent`
Use this command to set a tag to an application, device or release.
You can optionally provide a value to be associated with the created
tag, as an extra argument after the tag key. When the value isn't
provided, a tag with an empty value is created.
Examples:
$ balena tag set mySimpleTag --application MyApp
$ balena tag set myCompositeTag myTagValue --application MyApp
$ balena tag set myCompositeTag myTagValue --device 7cf02a6
$ balena tag set myCompositeTag "my tag value with whitespaces" --device 7cf02a6
$ balena tag set myCompositeTag myTagValue --release 1234
$ balena tag set myCompositeTag --release 1234
$ balena tag set myCompositeTag --release b376b0e544e9429483b656490e5b9443b4349bd6
`,
options: [
commandOptions.optionalApplication,
commandOptions.optionalDevice,
commandOptions.optionalRelease,
],
permission: 'user',
async action(params, options, done) {
normalizeUuidProp(options, 'device');
const Bluebird = await import('bluebird');
const _ = await import('lodash');
const balena = (await import('balena-sdk')).fromSharedOptions();
const { exitWithExpectedError } = await import('../utils/patterns');
return Bluebird.try(async () => {
if (_.isEmpty(params.tagKey)) {
return exitWithExpectedError('No tag key was provided');
}
if (
_.filter([options.application, options.device, options.release])
.length !== 1
) {
return exitWithExpectedError(stripIndent`
To set a resource tag, you must provide exactly one of:
* An application, with --application <appname>
* A device, with --device <uuid>
* A release, with --release <id or commit>
See the help page for examples:
$ balena help tag set
`);
}
if (params.value == null) {
params.value = '';
}
if (options.application) {
return balena.models.application.tags.set(
options.application,
params.tagKey,
params.value,
);
}
if (options.device) {
return balena.models.device.tags.set(
options.device,
params.tagKey,
params.value,
);
}
if (options.release) {
const releaseParam = await disambiguateReleaseParam(
balena,
options.release,
options.release_raw,
);
return balena.models.release.tags.set(
releaseParam,
params.tagKey,
params.value,
);
}
}).nodeify(done);
},
};
export const remove: CommandDefinition<
{
tagKey: string;
},
{
application?: string;
device?: string;
release?: number | string;
release_raw?: string;
}
> = {
signature: 'tag rm <tagKey>',
description: 'remove a resource tag',
help: stripIndent`
Use this command to remove a tag from an application, device or release.
Examples:
$ balena tag rm myTagKey --application MyApp
$ balena tag rm myTagKey --device 7cf02a6
$ balena tag rm myTagKey --release 1234
$ balena tag rm myTagKey --release b376b0e544e9429483b656490e5b9443b4349bd6
`,
options: [
commandOptions.optionalApplication,
commandOptions.optionalDevice,
commandOptions.optionalRelease,
],
permission: 'user',
async action(params, options, done) {
const Bluebird = await import('bluebird');
const _ = await import('lodash');
const balena = (await import('balena-sdk')).fromSharedOptions();
const { exitWithExpectedError } = await import('../utils/patterns');
return Bluebird.try(async () => {
if (_.isEmpty(params.tagKey)) {
return exitWithExpectedError('No tag key was provided');
}
if (
_.filter([options.application, options.device, options.release])
.length !== 1
) {
return exitWithExpectedError(stripIndent`
To remove a resource tag, you must provide exactly one of:
* An application, with --application <appname>
* A device, with --device <uuid>
* A release, with --release <id or commit>
See the help page for examples:
$ balena help tag rm
`);
}
if (options.application) {
return balena.models.application.tags.remove(
options.application,
params.tagKey,
);
}
if (options.device) {
return balena.models.device.tags.remove(options.device, params.tagKey);
}
if (options.release) {
const releaseParam = await disambiguateReleaseParam(
balena,
options.release,
options.release_raw,
);
return balena.models.release.tags.remove(releaseParam, params.tagKey);
}
}).nodeify(done);
},
};

235
lib/actions/tunnel.ts Normal file
View File

@ -0,0 +1,235 @@
/*
Copyright 2019 Balena
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import * as Bluebird from 'bluebird';
import { CommandDefinition } from 'capitano';
import { stripIndent } from 'common-tags';
import * as _ from 'lodash';
import { createServer, Server, Socket } from 'net';
import { isArray } from 'util';
import { getOnlineTargetUuid } from '../utils/patterns';
import { tunnelConnectionToDevice } from '../utils/tunnel';
interface Args {
deviceOrApplication: string;
// when Capitano converts a positional parameter (but not an option)
// to a number, the original value is preserved with the _raw suffix
deviceOrApplication_raw: string;
}
interface Options {
port: string | string[];
}
class InvalidPortMappingError extends Error {
constructor(mapping: string) {
super(`'${mapping}' is not a valid port mapping.`);
}
}
class NoPortsDefinedError extends Error {
constructor() {
super('No ports have been provided.');
}
}
const isValidPort = (port: number) => {
const MAX_PORT_VALUE = Math.pow(2, 16) - 1;
return port > 0 && port <= MAX_PORT_VALUE;
};
export const tunnel: CommandDefinition<Args, Options> = {
signature: 'tunnel <deviceOrApplication>',
description: 'Tunnel local ports to your balenaOS device',
help: stripIndent`
Use this command to open local ports which tunnel to listening ports on your balenaOS device.
For example, you could open port 8080 on your local machine to connect to your managed balenaOS
device running a web server listening on port 3000.
You can tunnel multiple ports at any given time.
Examples:
# map remote port 22222 to localhost:22222
$ balena tunnel abcde12345 -p 22222
# map remote port 22222 to localhost:222
$ balena tunnel abcde12345 -p 22222:222
# map remote port 22222 to any address on your host machine, port 22222
$ balena tunnel abcde12345 -p 22222:0.0.0.0
# map remote port 22222 to any address on your host machine, port 222
$ balena tunnel abcde12345 -p 22222:0.0.0.0:222
# multiple port tunnels can be specified at any one time
$ balena tunnel abcde12345 -p 8080:3000 -p 8081:9000
`,
options: [
{
signature: 'port',
parameter: 'port',
alias: 'p',
description: 'The mapping of remote to local ports.',
},
],
primary: true,
action: async (params, options) => {
const deviceOrApplication =
params.deviceOrApplication_raw || params.deviceOrApplication;
const Logger = await import('../utils/logger');
const logger = Logger.getLogger();
const balena = await import('balena-sdk');
const sdk = balena.fromSharedOptions();
const logConnection = (
fromHost: string,
fromPort: number,
localAddress: string,
localPort: number,
deviceAddress: string,
devicePort: number,
err?: Error,
) => {
const logMessage = `${fromHost}:${fromPort} => ${localAddress}:${localPort} ===> ${deviceAddress}:${devicePort}`;
if (err) {
logger.logError(`${logMessage} :: ${err.message}`);
} else {
logger.logLogs(logMessage);
}
};
if (options.port === undefined) {
throw new NoPortsDefinedError();
}
const ports =
typeof options.port !== 'string' && isArray(options.port)
? (options.port as string[])
: [options.port as string];
const uuid = await getOnlineTargetUuid(sdk, deviceOrApplication);
const device = await sdk.models.device.get(uuid);
logger.logInfo(`Opening a tunnel to ${device.uuid}...`);
const localListeners = _.chain(ports)
.map(mapping => {
const regexResult = /^([0-9]+)(?:$|\:(?:([\w\:\.]+)\:|)([0-9]+))$/.exec(
mapping,
);
if (regexResult === null) {
throw new InvalidPortMappingError(mapping);
}
// grab the groups
// tslint:disable-next-line:prefer-const
let [, remotePort, localAddress, localPort] = regexResult;
if (
!isValidPort(parseInt(localPort, undefined)) ||
!isValidPort(parseInt(remotePort, undefined))
) {
throw new InvalidPortMappingError(mapping);
}
// default bind to localAddress
if (localAddress == null) {
localAddress = 'localhost';
}
// default use same port number locally as remote
if (localPort == null) {
localPort = remotePort;
}
return {
localPort: parseInt(localPort, undefined),
localAddress,
remotePort: parseInt(remotePort, undefined),
};
})
.map(({ localPort, localAddress, remotePort }) => {
return tunnelConnectionToDevice(device.uuid, remotePort, sdk)
.then(handler =>
createServer((client: Socket) => {
return handler(client)
.then(() => {
logConnection(
client.remoteAddress || '',
client.remotePort || 0,
client.localAddress,
client.localPort,
device.vpn_address || '',
remotePort,
);
})
.catch(err =>
logConnection(
client.remoteAddress || '',
client.remotePort || 0,
client.localAddress,
client.localPort,
device.vpn_address || '',
remotePort,
err,
),
);
}),
)
.then(
server =>
new Bluebird.Promise<Server>((resolve, reject) => {
server.on('error', reject);
server.listen(localPort, localAddress, () => {
resolve(server);
});
}),
)
.then(() => {
logger.logInfo(
` - tunnelling ${localAddress}:${localPort} to ${
device.uuid
}:${remotePort}`,
);
return true;
})
.catch((err: Error) => {
logger.logWarn(
` - not tunnelling ${localAddress}:${localPort} to ${
device.uuid
}:${remotePort}, failed ${JSON.stringify(err.message)}`,
);
return false;
});
})
.value();
const results = await Promise.all(localListeners);
if (!results.includes(true)) {
throw new Error('No ports are valid for tunnelling');
}
logger.logInfo('Waiting for connections...');
},
};

View File

@ -1,56 +0,0 @@
###
Copyright 2016-2017 Resin.io
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.
###
_ = require('lodash')
exports.availableDrives =
# TODO: dedupe with https://github.com/resin-io-modules/resin-cli-visuals/blob/master/lib/widgets/drive/index.coffee
signature: 'util available-drives'
description: 'list available drives'
help: """
Use this command to list your machine's drives usable for writing the OS image to.
Skips the system drives.
"""
action: ->
Promise = require('bluebird')
drivelist = require('drivelist')
driveListAsync = Promise.promisify(drivelist.list)
chalk = require('chalk')
visuals = require('resin-cli-visuals')
formatDrive = (drive) ->
size = drive.size / 1000000000
return {
device: drive.device
size: "#{size.toFixed(1)} GB"
description: drive.description
}
getDrives = ->
driveListAsync().then (drives) ->
return _.reject(drives, system: true)
getDrives()
.then (drives) ->
if not drives.length
console.error("#{chalk.red('x')} No available drives were detected, plug one in!")
return
console.log visuals.table.horizontal drives.map(formatDrive), [
'device'
'size'
'description'
]

65
lib/actions/util.ts Normal file
View File

@ -0,0 +1,65 @@
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
/*
Copyright 2016-2017 Balena
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { CommandDefinition } from 'capitano';
import chalk from 'chalk';
import { stripIndent } from 'common-tags';
export const availableDrives: CommandDefinition<{}, {}> = {
signature: 'util available-drives',
description: 'list available drives',
help: stripIndent`
Use this command to list your machine's drives usable for writing the OS image to.
Skips the system drives.
`,
async action() {
const sdk = await import('etcher-sdk');
const visuals = await import('resin-cli-visuals');
const adapter = new sdk.scanner.adapters.BlockDeviceAdapter(() => false);
const scanner = new sdk.scanner.Scanner([adapter]);
await scanner.start();
function formatDrive(drive: any) {
const size = drive.size / 1000000000;
return {
device: drive.device,
size: `${size.toFixed(1)} GB`,
description: drive.description,
};
}
if (scanner.drives.size === 0) {
console.error(
`${chalk.red('x')} No available drives were detected, plug one in!`,
);
} else {
console.log(
visuals.table.horizontal(Array.from(scanner.drives).map(formatDrive), [
'device',
'size',
'description',
]),
);
}
scanner.stop();
},
};

View File

@ -1,75 +0,0 @@
###
Copyright 2016-2017 Resin.io
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.
###
exports.wizard =
signature: 'quickstart [name]'
description: 'getting started with resin.io'
help: '''
Use this command to run a friendly wizard to get started with resin.io.
The wizard will guide you through:
- Create an application.
- Initialise an SDCard with the resin.io operating system.
- Associate an existing project directory with your resin.io application.
- Push your project to your devices.
Examples:
$ resin quickstart
$ resin quickstart MyApp
'''
primary: true
action: (params, options, done) ->
Promise = require('bluebird')
capitanoRunAsync = Promise.promisify(require('capitano').run)
resin = require('resin-sdk-preconfigured')
patterns = require('../utils/patterns')
resin.auth.isLoggedIn().then (isLoggedIn) ->
return if isLoggedIn
console.info('Looks like you\'re not logged in yet!')
console.info('Lets go through a quick wizard to get you started.\n')
return capitanoRunAsync('login')
.then ->
return if params.name?
patterns.selectOrCreateApplication().tap (applicationName) ->
resin.models.application.has(applicationName).then (hasApplication) ->
return applicationName if hasApplication
capitanoRunAsync("app create #{applicationName}")
.then (applicationName) ->
params.name = applicationName
.then ->
return capitanoRunAsync("device init --application #{params.name}")
.tap(patterns.awaitDevice)
.then (uuid) ->
return capitanoRunAsync("device #{uuid}")
.then ->
return resin.models.application.get(params.name)
.then (application) ->
console.log """
Your device is ready to start pushing some code!
Check our official documentation for more information:
http://docs.resin.io/#/pages/introduction/introduction.md
Clone an example or go to an existing application directory and run:
$ git remote add resin #{application.git_repository}
$ git push resin master
"""
.nodeify(done)

165
lib/app-capitano.coffee Normal file
View File

@ -0,0 +1,165 @@
###
Copyright 2016-2019 Balena
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
###
Promise = require('bluebird')
capitano = require('capitano')
actions = require('./actions')
events = require('./events')
capitano.permission 'user', (done) ->
require('./utils/patterns').exitIfNotLoggedIn()
.then(done, done)
capitano.command
signature: '*'
action: (params, options, done) ->
capitano.execute(command: 'help', done)
process.exitCode = process.exitCode || 1
capitano.globalOption
signature: 'help'
boolean: true
alias: 'h'
capitano.globalOption
signature: 'version'
boolean: true
alias: 'v'
# ---------- Help Module ----------
capitano.command(actions.help.help)
# ---------- Api key module ----------
capitano.command(actions.apiKey.generate)
# ---------- App Module ----------
capitano.command(actions.app.create)
capitano.command(actions.app.list)
capitano.command(actions.app.remove)
capitano.command(actions.app.restart)
capitano.command(actions.app.info)
# ---------- Auth Module ----------
capitano.command(actions.auth.login)
capitano.command(actions.auth.logout)
capitano.command(actions.auth.whoami)
# ---------- Device Module ----------
capitano.command(actions.device.list)
capitano.command(actions.device.supported)
capitano.command(actions.device.rename)
capitano.command(actions.device.init)
capitano.command(actions.device.remove)
capitano.command(actions.device.identify)
capitano.command(actions.device.reboot)
capitano.command(actions.device.shutdown)
capitano.command(actions.device.enableDeviceUrl)
capitano.command(actions.device.disableDeviceUrl)
capitano.command(actions.device.getDeviceUrl)
capitano.command(actions.device.hasDeviceUrl)
capitano.command(actions.device.register)
capitano.command(actions.device.move)
capitano.command(actions.device.osUpdate)
capitano.command(actions.device.info)
# ---------- Notes Module ----------
capitano.command(actions.notes.set)
# ---------- Keys Module ----------
capitano.command(actions.keys.list)
capitano.command(actions.keys.add)
capitano.command(actions.keys.info)
capitano.command(actions.keys.remove)
# ---------- Tags Module ----------
capitano.command(actions.tags.list)
capitano.command(actions.tags.set)
capitano.command(actions.tags.remove)
# ---------- OS Module ----------
capitano.command(actions.os.versions)
capitano.command(actions.os.download)
capitano.command(actions.os.buildConfig)
capitano.command(actions.os.initialize)
# ---------- Config Module ----------
capitano.command(actions.config.read)
capitano.command(actions.config.write)
capitano.command(actions.config.inject)
capitano.command(actions.config.reconfigure)
capitano.command(actions.config.generate)
# ---------- Settings Module ----------
capitano.command(actions.settings.list)
# ---------- Logs Module ----------
capitano.command(actions.logs.logs)
# ---------- Tunnel Module ----------
capitano.command(actions.tunnel.tunnel)
# ---------- Preload Module ----------
capitano.command(actions.preload)
# ---------- SSH Module ----------
capitano.command(actions.ssh.ssh)
# ---------- Local balenaOS Module ----------
capitano.command(actions.local.configure)
capitano.command(actions.local.flash)
capitano.command(actions.scan)
# ---------- Public utils ----------
capitano.command(actions.util.availableDrives)
# ---------- Internal utils ----------
capitano.command(actions.internal.osInit)
capitano.command(actions.internal.scanDevices)
#------------ Local build and deploy -------
capitano.command(actions.build)
capitano.command(actions.deploy)
#------------ Push/remote builds -------
capitano.command(actions.push.push)
#------------ Join/Leave -------
capitano.command(actions.join.join)
capitano.command(actions.leave.leave)
exports.run = (argv) ->
cli = capitano.parse(argv.slice(2))
runCommand = ->
capitanoExecuteAsync = Promise.promisify(capitano.execute)
if cli.global?.help
capitanoExecuteAsync(command: "help #{cli.command ? ''}")
else
capitanoExecuteAsync(cli)
trackCommand = ->
getMatchCommandAsync = Promise.promisify(capitano.state.getMatchCommand)
getMatchCommandAsync(cli.command)
.then (command) ->
# cmdSignature is literally a string like, for example:
# "push <applicationOrDevice>"
# ("applicationOrDevice" is NOT replaced with its actual value)
# In case of failures like an inexistent or invalid command,
# command.signature.toString() returns '*'
cmdSignature = command.signature.toString()
events.trackCommand(cmdSignature)
Promise.all([trackCommand(), runCommand()])
.catch(require('./errors').handleError)

18
lib/app-capitano.d.ts vendored Normal file
View File

@ -0,0 +1,18 @@
/**
* @license
* Copyright 2019 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.
*/
export async function run(argv: string[]);

124
lib/app-common.ts Normal file
View File

@ -0,0 +1,124 @@
/**
* @license
* Copyright 2019 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.
*/
/**
* Sentry.io setup
* @see https://docs.sentry.io/clients/node/
*/
function setupRaven() {
const Raven = require('raven');
Raven.disableConsoleAlerts();
Raven.config(require('./config').sentryDsn, {
captureUnhandledRejections: true,
autoBreadcrumbs: true,
release: require('../package.json').version,
}).install(function(_logged: any, error: Error) {
console.error(error);
return process.exit(1);
});
Raven.setContext({
extra: {
args: process.argv,
node_version: process.version,
},
});
}
function checkNodeVersion() {
const validNodeVersions = require('../package.json').engines.node;
if (!require('semver').satisfies(process.version, validNodeVersions)) {
const { stripIndent } = require('common-tags');
console.warn(stripIndent`
------------------------------------------------------------------------------
Warning: Node version "${
process.version
}" does not match required versions "${validNodeVersions}".
This may cause unexpected behaviour. To upgrade Node, visit:
https://nodejs.org/en/download/
------------------------------------------------------------------------------
`);
}
}
function setupGlobalHttpProxy() {
// Doing this before requiring any other modules,
// including the 'balena-sdk', to prevent any module from reading the http proxy config
// before us
const globalTunnel = require('global-tunnel-ng');
const settings = require('balena-settings-client');
let proxy;
try {
proxy = settings.get('proxy') || null;
} catch (error1) {
proxy = null;
}
// Init the tunnel even if the proxy is not configured
// because it can also get the proxy from the http(s)_proxy env var
// If that is not set as well the initialize will do nothing
globalTunnel.initialize(proxy);
// TODO: make this a feature of capitano https://github.com/balena-io/capitano/issues/48
(global as any).PROXY_CONFIG = globalTunnel.proxyConfig;
}
function setupBalenaSdkSharedOptions() {
// We don't yet use balena-sdk directly everywhere, but we set up shared
// options correctly so we can do safely in submodules
const BalenaSdk = require('balena-sdk');
const settings = require('balena-settings-client');
BalenaSdk.setSharedOptions({
apiUrl: settings.get('apiUrl'),
imageMakerUrl: settings.get('imageMakerUrl'),
dataDirectory: settings.get('dataDirectory'),
retries: 2,
});
}
let BluebirdConfigured = false;
/**
* Configure Bluebird and assign it as the global promise library.
* Modules like `stream-to-promise` will otherwise produce native promises,
* which leads to errors as much of the CLI CoffeeScript code expects Bluebird
* promises.
*/
export function configureBluebird() {
if (BluebirdConfigured) {
return;
}
BluebirdConfigured = true;
const Bluebird = require('bluebird');
Bluebird.config({
longStackTraces: process.env.DEBUG ? true : false,
});
if (!(global as any)['@@any-promise/REGISTRATION']) {
require('any-promise/register/bluebird');
}
}
export function globalInit() {
setupRaven();
checkNodeVersion();
configureBluebird();
setupGlobalHttpProxy();
setupBalenaSdkSharedOptions();
// check for CLI updates once a day
require('./utils/update').notify();
}

60
lib/app-oclif.ts Normal file
View File

@ -0,0 +1,60 @@
/**
* @license
* Copyright 2019 Balena Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Main } from '@oclif/command';
import { ExitError } from '@oclif/errors';
import { trackPromise } from './hooks/prerun/track';
class CustomMain extends Main {
protected _helpOverride(): boolean {
// Disable oclif's default handler for the 'version' command
if (['-v', '--version', 'version'].includes(this.argv[0])) {
return false;
} else {
return super._helpOverride();
}
}
}
type AppOptions = import('./preparser').AppOptions;
/**
* oclif CLI entrypoint
*/
export async function run(command: string[], options: AppOptions) {
const runPromise = CustomMain.run(command).then(
() => {
if (!options.noFlush) {
return require('@oclif/command/flush');
}
},
(error: Error) => {
// oclif sometimes exits with ExitError code 0 (not an error)
if (error instanceof ExitError && error.oclif.exit === 0) {
return;
} else {
throw error;
}
},
);
try {
await Promise.all([trackPromise, runPromise]);
} catch (err) {
await (await import('./errors')).handleError(err);
}
}

View File

@ -1,224 +0,0 @@
###
Copyright 2016-2017 Resin.io
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.
###
Raven = require('raven')
Raven.disableConsoleAlerts()
Raven.config require('./config').sentryDsn,
captureUnhandledRejections: true
release: require('../package.json').version
.install (logged, error) ->
console.error(error)
process.exit(1)
Raven.setContext
extra:
args: process.argv
node_version: process.version
validNodeVersions = require('../package.json').engines.node
if not require('semver').satisfies(process.version, validNodeVersions)
console.warn """
Warning: this version of Node does not match the requirements of this package.
This package expects #{validNodeVersions}, but you're using #{process.version}.
This may cause unexpected behaviour.
To upgrade your Node, visit https://nodejs.org/en/download/
"""
# Doing this before requiring any other modules,
# including the 'resin-sdk', to prevent any module from reading the http proxy config
# before us
globalTunnel = require('global-tunnel-ng')
settings = require('resin-settings-client')
try
proxy = settings.get('proxy') or null
catch
proxy = null
# Init the tunnel even if the proxy is not configured
# because it can also get the proxy from the http(s)_proxy env var
# If that is not set as well the initialize will do nothing
globalTunnel.initialize(proxy)
# TODO: make this a feature of capitano https://github.com/resin-io/capitano/issues/48
global.PROXY_CONFIG = globalTunnel.proxyConfig
_ = require('lodash')
Promise = require('bluebird')
capitano = require('capitano')
capitanoExecuteAsync = Promise.promisify(capitano.execute)
# We don't yet use resin-sdk directly everywhere, but we set up shared
# options correctly so we can do safely in submodules
require('resin-sdk').setSharedOptions(
apiUrl: settings.get('apiUrl')
imageMakerUrl: settings.get('imageMakerUrl')
dataDirectory: settings.get('dataDirectory')
retries: 2
)
# Keep using sdk-preconfigured for now, but only temporarily
resin = require('resin-sdk-preconfigured')
actions = require('./actions')
errors = require('./errors')
events = require('./events')
plugins = require('./utils/plugins')
update = require('./utils/update')
# Assign bluebird as the global promise library
# stream-to-promise will produce native promises if not
# for this module, which could wreak havoc in this
# bluebird-only codebase.
require('any-promise/register/bluebird')
capitano.permission 'user', (done) ->
resin.auth.isLoggedIn().then (isLoggedIn) ->
if not isLoggedIn
throw new Error '''
You have to log in to continue
Run the following command to go through the login wizard:
$ resin login
'''
.nodeify(done)
capitano.command
signature: '*'
action: ->
capitano.execute(command: 'help')
capitano.globalOption
signature: 'help'
boolean: true
alias: 'h'
# ---------- Info Module ----------
capitano.command(actions.info.version)
# ---------- Help Module ----------
capitano.command(actions.help.help)
# ---------- Wizard Module ----------
capitano.command(actions.wizard.wizard)
# ---------- Auth Module ----------
capitano.command(actions.auth.login)
capitano.command(actions.auth.logout)
capitano.command(actions.auth.signup)
capitano.command(actions.auth.whoami)
# ---------- App Module ----------
capitano.command(actions.app.create)
capitano.command(actions.app.list)
capitano.command(actions.app.remove)
capitano.command(actions.app.restart)
capitano.command(actions.app.info)
# ---------- Device Module ----------
capitano.command(actions.device.list)
capitano.command(actions.device.supported)
capitano.command(actions.device.rename)
capitano.command(actions.device.init)
capitano.command(actions.device.remove)
capitano.command(actions.device.identify)
capitano.command(actions.device.reboot)
capitano.command(actions.device.shutdown)
capitano.command(actions.device.enableDeviceUrl)
capitano.command(actions.device.disableDeviceUrl)
capitano.command(actions.device.getDeviceUrl)
capitano.command(actions.device.hasDeviceUrl)
capitano.command(actions.device.register)
capitano.command(actions.device.move)
capitano.command(actions.device.info)
# ---------- Notes Module ----------
capitano.command(actions.notes.set)
# ---------- Keys Module ----------
capitano.command(actions.keys.list)
capitano.command(actions.keys.add)
capitano.command(actions.keys.info)
capitano.command(actions.keys.remove)
# ---------- Env Module ----------
capitano.command(actions.env.list)
capitano.command(actions.env.add)
capitano.command(actions.env.rename)
capitano.command(actions.env.remove)
# ---------- OS Module ----------
capitano.command(actions.os.versions)
capitano.command(actions.os.download)
capitano.command(actions.os.buildConfig)
capitano.command(actions.os.configure)
capitano.command(actions.os.initialize)
# ---------- Config Module ----------
capitano.command(actions.config.read)
capitano.command(actions.config.write)
capitano.command(actions.config.inject)
capitano.command(actions.config.reconfigure)
capitano.command(actions.config.generate)
# ---------- Settings Module ----------
capitano.command(actions.settings.list)
# ---------- Logs Module ----------
capitano.command(actions.logs)
# ---------- Sync Module ----------
capitano.command(actions.sync)
# ---------- Preload Module ----------
capitano.command(actions.preload)
# ---------- SSH Module ----------
capitano.command(actions.ssh)
# ---------- Local ResinOS Module ----------
capitano.command(actions.local.configure)
capitano.command(actions.local.flash)
capitano.command(actions.local.logs)
capitano.command(actions.local.push)
capitano.command(actions.local.ssh)
capitano.command(actions.local.scan)
capitano.command(actions.local.stop)
# ---------- Public utils ----------
capitano.command(actions.util.availableDrives)
# ---------- Internal utils ----------
capitano.command(actions.internal.osInit)
#------------ Local build and deploy -------
capitano.command(actions.build)
capitano.command(actions.deploy)
update.notify()
plugins.register(/^resin-plugin-(.+)$/).then ->
cli = capitano.parse(process.argv)
runCommand = ->
if cli.global?.help
capitanoExecuteAsync(command: "help #{cli.command ? ''}")
else
capitanoExecuteAsync(cli)
Promise.all([events.trackCommand(cli), runCommand()])
.catch(errors.handle)

77
lib/app.ts Normal file
View File

@ -0,0 +1,77 @@
/**
* @license
* Copyright 2019 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.
*/
/**
* CLI entrypoint, but see also `bin/balena` and `bin/balena-dev` which
* call this function.
*/
export async function run(
cliArgs = process.argv,
options: import('./preparser').AppOptions = {},
) {
// The 'pkgExec' special/internal command provides a Node.js interpreter
// for use of the standalone zip package. See pkgExec function.
if (cliArgs.length > 3 && cliArgs[2] === 'pkgExec') {
return pkgExec(cliArgs[3], cliArgs.slice(4));
}
const { globalInit } = await import('./app-common');
const { routeCliFramework } = await import('./preparser');
// globalInit() must be called very early on (before other imports) because
// it sets up Sentry error reporting, global HTTP proxy settings, balena-sdk
// shared options, and performs node version requirement checks.
globalInit();
await routeCliFramework(cliArgs, options);
}
/**
* Implements the 'pkgExec' command, used as a way to provide a Node.js
* interpreter for child_process.spawn()-like operations when the CLI is
* executing as a standalone zip package (built-in Node interpreter) and
* the system may not have a separate Node.js installation. A present use
* case is a patched version of the 'windosu' package that requires a
* Node.js interpreter to spawn a privileged child process.
*
* @param modFunc Path to a JS module that will be executed via require().
* The modFunc argument may optionally contain a function name separated
* by '::', for example '::main' in:
* 'C:\\snapshot\\balena-cli\\node_modules\\windosu\\lib\\pipe.js::main'
* in which case that function is executed in the require'd module.
* @param args Optional arguments to passed through process.argv and as
* arguments to the function specified via modFunc.
*/
async function pkgExec(modFunc: string, args: string[]) {
const [modPath, funcName] = modFunc.split('::');
let replacedModPath = modPath;
const match = modPath
.replace(/\\/g, '/')
.match(/\/snapshot\/balena-cli\/(.+)/);
if (match) {
replacedModPath = `../${match[1]}`;
}
process.argv = [process.argv[0], process.argv[1], ...args];
try {
const mod: any = await import(replacedModPath);
if (funcName) {
await mod[funcName](...args);
}
} catch (err) {
console.error(`Error executing pkgExec "${modFunc}" [${args.join()}]`);
console.error(err);
}
}

View File

@ -1,5 +1,5 @@
###
Copyright 2016 Resin.io
Copyright 2016 Balena
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -18,19 +18,19 @@ limitations under the License.
# @module auth
###
open = require('open')
resin = require('resin-sdk-preconfigured')
open = require('opn')
balena = require('balena-sdk').fromSharedOptions()
server = require('./server')
utils = require('./utils')
###*
# @summary Login to the Resin CLI using the web dashboard
# @summary Login to the balena CLI using the web dashboard
# @function
# @public
#
# @description
# This function opens the user's default browser and points it
# to the Resin.io dashboard where the session token exchange will
# to the balena dashboard where the session token exchange will
# take place.
#
# Once the the token is retrieved, it's automatically persisted.
@ -56,8 +56,8 @@ exports.login = ->
# Leave a bit of time for the
# server to get up and runing
setTimeout ->
open(loginUrl)
open(loginUrl, { wait: false })
, 1000
return server.awaitForToken(options)
.tap(resin.auth.loginWithToken)
.tap(balena.auth.loginWithToken)

View File

@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Resin CLI - Error</title>
<title>Balena CLI - Error</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="./static/style.css" inline>
@ -12,10 +12,10 @@
<div class="center">
<img class="icon" src="./static/images/sad.png" inline>
<h1>Something went wrong</h1>
<p>You couldn't login to the Resin CLI for some reason</p>
<p>You couldn't login to the balena CLI for some reason</p>
<br>
<br>
<a href="https://forums.resin.io/" class="button danger">Get help in our forums</a>
<a href="https://forums.balena.io/" class="button danger">Get help in our forums</a>
</div>
</body>
</html>

View File

@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Resin CLI - Success</title>
<title>Balena CLI - Success</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="./static/style.css" inline>
@ -12,7 +12,7 @@
<div class="center">
<img class="icon" src="./static/images/happy.png" inline>
<h1>Success!</h1>
<p>You successfully logged in the Resin CLI</p>
<p>You successfully logged in the balena CLI</p>
<br>
<br>
<a href="<%= dashboardUrl %>" class="button normal">Go to the dashboard</a>

View File

@ -1,5 +1,5 @@
###
Copyright 2016 Resin.io
Copyright 2016 Balena
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -18,7 +18,7 @@ express = require('express')
path = require('path')
bodyParser = require('body-parser')
Promise = require('bluebird')
resin = require('resin-sdk-preconfigured')
balena = require('balena-sdk').fromSharedOptions()
utils = require('./utils')
createServer = ({ port, isDev } = {}) ->
@ -77,9 +77,9 @@ exports.awaitForToken = (options) ->
Promise.try ->
if not token
throw new Error('No token')
return utils.isTokenValid(token)
.tap (isValid) ->
if not isValid
return utils.loginIfTokenValid(token)
.tap (loggedIn) ->
if not loggedIn
throw new Error('Invalid token')
.then ->
renderAndDone({ request, response, viewName: 'success', token })
@ -96,6 +96,6 @@ exports.awaitForToken = (options) ->
exports.getContext = getContext = (viewName) ->
if viewName is 'success'
return Promise.props
dashboardUrl: resin.settings.get('dashboardUrl')
dashboardUrl: balena.settings.get('dashboardUrl')
return Promise.resolve({})

View File

@ -1,5 +1,5 @@
###
Copyright 2016 Resin.io
Copyright 2016 Balena
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
###
resin = require('resin-sdk-preconfigured')
balena = require('balena-sdk').fromSharedOptions()
_ = require('lodash')
url = require('url')
Promise = require('bluebird')
@ -38,11 +38,11 @@ exports.getDashboardLoginURL = (callbackUrl) ->
# characters to avoid angular getting confused.
callbackUrl = encodeURIComponent(callbackUrl).replace(/%/g, '%25')
resin.settings.get('dashboardUrl').then (dashboardUrl) ->
balena.settings.get('dashboardUrl').then (dashboardUrl) ->
return url.resolve(dashboardUrl, "/login/cli/#{callbackUrl}")
###*
# @summary Check if a token is valid
# @summary Log in using a token, but only if the token is valid
# @function
# @protected
#
@ -50,26 +50,31 @@ exports.getDashboardLoginURL = (callbackUrl) ->
# This function checks that the token is not only well-structured
# but that it also authenticates with the server successfully.
#
# @param {String} sessionToken - token
# @fulfil {Boolean} - whether is valid or not
# If authenticated, the token is persisted, if not then the previous
# login state is restored.
#
# @param {String} token - session token or api key
# @fulfil {Boolean} - whether the login was successful or not
# @returns {Promise}
#
# utils.isTokenValid('...').then (isValid) ->
# if isValid
# utils.loginIfTokenValid('...').then (loggedIn) ->
# if loggedIn
# console.log('Token is valid!')
###
exports.isTokenValid = (sessionToken) ->
if not sessionToken? or _.isEmpty(sessionToken.trim())
exports.loginIfTokenValid = (token) ->
if not token? or _.isEmpty(token.trim())
return Promise.resolve(false)
return resin.token.get().then (currentToken) ->
resin.auth.loginWithToken(sessionToken)
.return(sessionToken)
.then(resin.auth.isLoggedIn)
return balena.auth.getToken()
.catchReturn(undefined)
.then (currentToken) ->
balena.auth.loginWithToken(token)
.return(token)
.then(balena.auth.isLoggedIn)
.tap (isLoggedIn) ->
return if isLoggedIn
if currentToken?
return resin.auth.loginWithToken(currentToken)
return balena.auth.loginWithToken(currentToken)
else
return resin.auth.logout()
return balena.auth.logout()

View File

@ -1 +1,21 @@
exports.sentryDsn = 'https://56d2a46124614b01b0f4086897e96110:6e175465accc41b595a96947155f61fb@sentry.io/149239'
/**
* @license
* Copyright 2017-2019 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.
*/
export const BALENA_ENGINE_TMP_PATH = '/var/lib/docker/tmp';
export const sentryDsn =
'https://56d2a46124614b01b0f4086897e96110:6e175465accc41b595a96947155f61fb@sentry.io/149239';

View File

@ -1,38 +0,0 @@
###
Copyright 2016-2017 Resin.io
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.
###
chalk = require('chalk')
errors = require('resin-cli-errors')
patterns = require('./utils/patterns')
Raven = require('raven')
Promise = require('bluebird')
captureException = Promise.promisify(Raven.captureException.bind(Raven))
exports.handle = (error) ->
message = errors.interpret(error)
return if not message?
if process.env.DEBUG
message = error.stack
patterns.printErrorMessage(message)
captureException(error)
.timeout(1000)
.catch(-> # Ignore any errors (from error logging, or timeouts)
).finally ->
process.exit(error.exitCode or 1)

137
lib/errors.ts Normal file
View File

@ -0,0 +1,137 @@
/*
Copyright 2016-2019 Balena
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import * as Bluebird from 'bluebird';
import { stripIndent } from 'common-tags';
import * as _ from 'lodash';
import * as os from 'os';
import * as Raven from 'raven';
export class ExpectedError extends Error {}
export class NotLoggedInError extends ExpectedError {}
const captureException = Bluebird.promisify<string, Error>(
Raven.captureException,
{ context: Raven },
);
function hasCode(error: any): error is Error & { code: string } {
return error.code != null;
}
function treatFailedBindingAsMissingModule(error: any): void {
if (error.message.startsWith('Could not locate the bindings file.')) {
error.code = 'MODULE_NOT_FOUND';
}
}
function interpret(error: Error): string {
treatFailedBindingAsMissingModule(error);
if (hasCode(error)) {
const errorCodeHandler = messages[error.code];
const message = errorCodeHandler && errorCodeHandler(error);
if (message) {
return message;
}
if (!_.isEmpty(error.message)) {
return `${error.code}: ${error.message}`;
}
}
return error.message;
}
const messages: {
[key: string]: (error: Error & { path?: string }) => string;
} = {
EISDIR: error => `File is a directory: ${error.path}`,
ENOENT: error => `No such file or directory: ${error.path}`,
ENOGIT: () => stripIndent`
Git is not installed on this system.
Head over to http://git-scm.com to install it and run this command again.`,
EPERM: () => stripIndent`
You don't have sufficient privileges to run this operation.
${
os.platform() === 'win32'
? 'Run a new Command Prompt as administrator and try running this command again.'
: 'Try running this command again prefixing it with `sudo`.'
}
If this is not the case, and you're trying to burn an SDCard, check that the write lock is not set.`,
EACCES: e => messages.EPERM(e),
ETIMEDOUT: () =>
'Oops something went wrong, please check your connection and try again.',
MODULE_NOT_FOUND: () => stripIndent`
Part of the CLI could not be loaded. This typically means your CLI install is in a broken state.
${
os.arch() === 'x64'
? 'You can normally fix this by uninstalling and reinstalling the CLI.'
: stripIndent`
You're using an unsupported architecture (${os.arch()}), so this is typically caused by missing native modules.
Reinstalling may help, but pay attention to errors in native module build steps en route.
`
}
`,
BalenaExpiredToken: () => stripIndent`
Looks like your session token is expired.
Please try logging in again with:
$ balena login`,
};
export async function handleError(error: any) {
const { printErrorMessage } = await import('./utils/patterns');
process.exitCode =
error.exitCode === 0
? 0
: parseInt(error.exitCode, 10) || process.exitCode || 1;
if (!(error instanceof Error)) {
printErrorMessage(String(error));
return;
}
const message = [interpret(error)];
if (process.env.DEBUG && error.stack) {
message.push(error.stack);
}
printErrorMessage(message.join('\n'));
if (error instanceof ExpectedError) {
return;
}
// Report "unexpected" errors via Sentry.io
await captureException(error)
.timeout(1000)
.catch(function() {
// Ignore any errors (from error logging, or timeouts)
})
// exit with the process.exitCode set earlier
.finally(() => process.exit());
}

View File

@ -1,34 +0,0 @@
_ = require('lodash')
Mixpanel = require('mixpanel')
Raven = require('raven')
Promise = require('bluebird')
resin = require('resin-sdk-preconfigured')
packageJSON = require('../package.json')
exports.getLoggerInstance = _.memoize ->
return resin.models.config.getMixpanelToken().then(Mixpanel.init)
exports.trackCommand = (capitanoCommand) ->
capitanoStateGetMatchCommandAsync = Promise.promisify(require('capitano').state.getMatchCommand)
return Promise.props
resinUrl: resin.settings.get('resinUrl')
username: resin.auth.whoami().catchReturn(undefined)
mixpanel: exports.getLoggerInstance()
.then ({ username, resinUrl, mixpanel }) ->
return capitanoStateGetMatchCommandAsync(capitanoCommand.command).then (command) ->
Raven.mergeContext(user: {
id: username,
username
})
mixpanel.track "[CLI] #{command.signature.toString()}",
distinct_id: username
argv: process.argv.join(' ')
version: packageJSON.version
node: process.version
arch: process.arch
resinUrl: resinUrl
platform: process.platform
command: capitanoCommand
.timeout(100)
.catchReturn()

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