mirror of
https://github.com/balena-io/balena-cli.git
synced 2025-06-24 18:45:07 +00:00
Compare commits
485 Commits
Author | SHA1 | Date | |
---|---|---|---|
8fa6ad0735 | |||
db32d89f1f | |||
bb2365b3fd | |||
a9db392580 | |||
d86fcfcda4 | |||
7fb03b8511 | |||
3b52f7ba4e | |||
69396904c6 | |||
71e8448fbf | |||
41ff793372 | |||
e4432d1a90 | |||
bd6cb04a2b | |||
001c8f9601 | |||
f106b95be2 | |||
6fbe602b77 | |||
dc549a665b | |||
46ca62db3e | |||
eb68bb1a1a | |||
93d1e3a4a1 | |||
ff2ee59dae | |||
6217b4a6b5 | |||
67fcc6791c | |||
49d78c56fa | |||
e38a0c0047 | |||
eef0d9cdbe | |||
08c40195e5 | |||
7e306fbce8 | |||
656656bec1 | |||
87f46cb957 | |||
f7075d7db9 | |||
2a2d621d6a | |||
2db6cdd063 | |||
1fafe64579 | |||
6562eb544c | |||
3763bf0712 | |||
890a02e2c8 | |||
427664c729 | |||
727a245715 | |||
a2635f47ee | |||
d316f67367 | |||
ebd1d7e370 | |||
eef192ff68 | |||
36d3c92006 | |||
68e31468cc | |||
dfd8b6717d | |||
10d688c02d | |||
737e961979 | |||
3bca36c277 | |||
24b2c83e92 | |||
266870018a | |||
80bc044415 | |||
563628a5a9 | |||
385e2c7f7a | |||
19ce4c4cdb | |||
0b26fda89c | |||
3f692ecbb0 | |||
2d43e47610 | |||
a8f1d16b26 | |||
8e95757f47 | |||
3fd4f328ab | |||
97eaf174ec | |||
2ef56a9a3f | |||
93818b1a98 | |||
ce70102378 | |||
0e4c6c459c | |||
6b96fe37ba | |||
e9c7e0e924 | |||
119fa78927 | |||
dad655c9ec | |||
8af392029f | |||
82888de036 | |||
b4a56e1541 | |||
19b0ec7f8b | |||
3df7bfe700 | |||
d1fd5a6bff | |||
43a7e3ddf4 | |||
10976bed43 | |||
c187c113d9 | |||
f7c0258145 | |||
eb729d149e | |||
39ac28d4ef | |||
989877d541 | |||
3f3af216fd | |||
9aef632afd | |||
ef6e00bcea | |||
55a7dccc74 | |||
62035fac83 | |||
950201973b | |||
e431083e84 | |||
1ff9cf02d7 | |||
bd3b9e32a9 | |||
5a620d6c9e | |||
492e35e5c2 | |||
8980bc704a | |||
8b9e78d645 | |||
8f0131cf50 | |||
47407a84fb | |||
d858f3fd90 | |||
a36f765f1b | |||
fd308a5131 | |||
3052100973 | |||
cfdd4d3d69 | |||
0a3123a9cf | |||
5474666f9e | |||
2bbd45e867 | |||
e8c4a9abfd | |||
710a938b3f | |||
be7c1d278e | |||
dfcac4a532 | |||
a5128cd49e | |||
223432406d | |||
cafde01886 | |||
0158d1da48 | |||
e0d661a1da | |||
b152482133 | |||
4cdf3acf42 | |||
314fcd3919 | |||
b07a394592 | |||
14c5d938a6 | |||
c6c2f0bedc | |||
6882f4bbe8 | |||
74d6cfb8d2 | |||
3b30a7c4b0 | |||
115e46573b | |||
2ad789457f | |||
9beb6de7d8 | |||
74743745c4 | |||
6fae5a2dd9 | |||
4320f33d8e | |||
435fedfa07 | |||
999f269e36 | |||
224dfce4a8 | |||
60634a5ebd | |||
f8f1f52662 | |||
e204707ee0 | |||
b28a4a5f99 | |||
340b2d5572 | |||
4dec846256 | |||
dc1b3c3239 | |||
2fecb8d3e9 | |||
4665a72baf | |||
b208d601c9 | |||
22b3c39b2b | |||
30cca93283 | |||
f0d9b615a7 | |||
b7e2c2571f | |||
25538a9afb | |||
5daa682183 | |||
bad4493867 | |||
9e6dd57a5c | |||
1b86741fa2 | |||
6cca43a09e | |||
b622995a5a | |||
4329857a16 | |||
d803cfab3a | |||
e7d7ca807f | |||
22e0b4b9dc | |||
759baf3eda | |||
e6dc7f8075 | |||
1f0bec39d9 | |||
64b6549fde | |||
fc7e08c886 | |||
4aadfe9326 | |||
a65868cbbf | |||
8ab528aae4 | |||
d93b82a269 | |||
3095938b0e | |||
91b3442fc9 | |||
e660c6ae90 | |||
e2a165ce80 | |||
ce5685551d | |||
15e677e9f1 | |||
5ccde3db8e | |||
cb550f65bb | |||
8d3987fc70 | |||
4fa8d86f02 | |||
6b22166887 | |||
9e555b3dba | |||
bea7b2035a | |||
abebf6b4f4 | |||
6182e7c98a | |||
410390a9ae | |||
11079caf26 | |||
0c6545218a | |||
7fc806c75f | |||
18533de3da | |||
c5afe5f2c2 | |||
2875bd672e | |||
26d123b33d | |||
5000febf72 | |||
378f894da3 | |||
c891d690ec | |||
8efaec63ff | |||
b756f2a597 | |||
c986e142e2 | |||
8bdacbb11e | |||
d2a9aee685 | |||
77a4c6fdc2 | |||
4d935d62fc | |||
e8b44d7250 | |||
5c5cfde49f | |||
97480d3aa4 | |||
4ac8cb1003 | |||
2e7e033bb9 | |||
9fb5b52069 | |||
ad940824a6 | |||
ed83514a2f | |||
8d91a5732a | |||
1cfe64e4a7 | |||
24388811ad | |||
f465e74a87 | |||
c8d51d92e7 | |||
5a28d4c92f | |||
66a4faeea5 | |||
2fce0e964b | |||
a29b40eefa | |||
cf7bf2cb7d | |||
df3c5ca07f | |||
e584dc43f7 | |||
90a5b15dbc | |||
390332cd6c | |||
37ec11bf25 | |||
08ce1abd20 | |||
f2862f7fe2 | |||
e0673c98fc | |||
4ab67ed71d | |||
5ea263ef2e | |||
31419b399e | |||
9e8b09010f | |||
974be5cc13 | |||
ec386b807f | |||
abc183a729 | |||
e5ed6fab85 | |||
eb9152255e | |||
78ab47b584 | |||
5b651c7821 | |||
95fc5d7785 | |||
4d18e92686 | |||
2d729a82a0 | |||
4b5240d8cd | |||
6ae59654a0 | |||
b88f7a993c | |||
880fb43fd9 | |||
fa71df7c70 | |||
bc79832e1d | |||
1d06bc1b4f | |||
88d5ec0c94 | |||
ebd9e92b10 | |||
a5b535753f | |||
6e5e4bd8a6 | |||
6e034acf23 | |||
3a44782c38 | |||
654ec75598 | |||
66876a2c85 | |||
8cb862359b | |||
172fa37bd4 | |||
fc5640c79d | |||
db9225f00a | |||
c12b59b978 | |||
7eeafa935a | |||
404348f92e | |||
4d2af251b2 | |||
d249ac168a | |||
65eaad2ed5 | |||
3ff5880ae3 | |||
511d2abe1d | |||
029b7c7164 | |||
afafa22694 | |||
2df4422748 | |||
e099f0b8b3 | |||
8866f47805 | |||
1d8382e91d | |||
110e18bc88 | |||
3df30c8b5a | |||
ebfd842ae5 | |||
39b171fd2a | |||
04c2333a54 | |||
65884c81a4 | |||
f50ae65560 | |||
be0d6d5a99 | |||
4fa1a9c1c6 | |||
284030d83d | |||
9050cb1975 | |||
d55ce853a7 | |||
d3772386bf | |||
2e042499af | |||
225d3acf9e | |||
75d10286ad | |||
d6e0616c58 | |||
380a94f0f8 | |||
9da8c5f09a | |||
89b88315e4 | |||
11e8ca178c | |||
e583e6f04c | |||
0cce2a7ab7 | |||
965aa7e4d4 | |||
0ca64b18a2 | |||
63e1313f44 | |||
96a7a24738 | |||
dede4bb329 | |||
77b30409bb | |||
137473353c | |||
08b3db717e | |||
6cf32e445a | |||
857c5204b9 | |||
169609620a | |||
8149172eb0 | |||
cba105a41b | |||
69dff0c603 | |||
6ffa4070ff | |||
f05b04a6a1 | |||
88d8112402 | |||
f940d7428c | |||
697779868e | |||
d90874dbef | |||
65b2347b57 | |||
b25034978b | |||
a817bb2135 | |||
b629c3601e | |||
3619b2f117 | |||
4231f50c4c | |||
95fff4b7c4 | |||
b3aa3d35f7 | |||
9f3108c5e7 | |||
c95a01e34e | |||
19c51929a9 | |||
73dd625ede | |||
08db3ace03 | |||
2125cf9649 | |||
e5a7fa5617 | |||
3324ff4dee | |||
7ad468dc54 | |||
8474ee726c | |||
29b2f4afa8 | |||
7aee4d6d7f | |||
7af004501a | |||
2d09c18d6b | |||
53bf314820 | |||
1ae1a15259 | |||
20ed8c9169 | |||
977e3fb0ff | |||
c5df32f952 | |||
f5cd3375f2 | |||
3b4c8f2a01 | |||
356042557e | |||
00753a5776 | |||
eea9a2f723 | |||
5ebdf4a8ac | |||
fb06249b08 | |||
9214cb0d90 | |||
a04c3b9c7b | |||
2fde6241c2 | |||
ffa645f85c | |||
b629ee6164 | |||
7a4de5357e | |||
5bbb055cd9 | |||
553b96e48f | |||
7a0e8beb07 | |||
f17cbb1205 | |||
b690060bc4 | |||
72b2058d27 | |||
bbd617ea76 | |||
f9a4f8c375 | |||
5c289a6e00 | |||
3b439282ae | |||
d473509675 | |||
49664b815d | |||
06cc863f02 | |||
099cf997cb | |||
e8183d4031 | |||
6954da4a24 | |||
c18e8f1dbd | |||
741b53ad7e | |||
8282785b2a | |||
e013986dba | |||
3083ff0640 | |||
01cad3c048 | |||
c9919a90a8 | |||
0bf6bcc430 | |||
9cf42462c0 | |||
0f4eca2ff0 | |||
d414d1c5e3 | |||
afe98ff37d | |||
ce026ea387 | |||
25f8f3d740 | |||
f719f5c948 | |||
e2cb79edb1 | |||
c6e669fa6b | |||
7953f6f690 | |||
baf367b276 | |||
7fecb53cdf | |||
b4edb7ed7f | |||
16a1741374 | |||
e0a2217b94 | |||
7bd8a683b2 | |||
6b00bbc73a | |||
42d0b52df7 | |||
97c768edcd | |||
10a0924cd7 | |||
fdb8bf6967 | |||
81d8974213 | |||
af8d20ea3f | |||
7d606568f6 | |||
de13337655 | |||
8b485b5ad5 | |||
fbccf8a465 | |||
ce50d8b73d | |||
2088cbe896 | |||
870ce974e0 | |||
5f8c261288 | |||
cb386d15aa | |||
5ed26250d2 | |||
7b0415a270 | |||
3adb8f19bd | |||
d81fbad6f3 | |||
a70e38ef12 | |||
aeba64b1ee | |||
e6e44474a4 | |||
ea44c0571b | |||
f68364b695 | |||
81a6843c93 | |||
b672ff1fa1 | |||
b4c89e812c | |||
68808e760e | |||
69c98f4afb | |||
509fef754e | |||
6d1d4dc173 | |||
0d7d6de7cd | |||
c5452f9304 | |||
12854db923 | |||
51f9e18f6a | |||
29c20e32f6 | |||
995194fe2c | |||
7c784909fd | |||
a90d568d5c | |||
a265063fa1 | |||
1c5945d3ae | |||
6062c7a306 | |||
b061644b19 | |||
38b97baf02 | |||
8315bcb7db | |||
e8fa76266f | |||
3db3baeb7c | |||
17550f9bc9 | |||
44f80f7a39 | |||
372fa01cf3 | |||
9a515ef4e3 | |||
6beb6ff44e | |||
c2cd10f6a7 | |||
4d3769def8 | |||
42bfb3b0cc | |||
d9c2717fe4 | |||
8e93577f90 | |||
5b7e1b656e | |||
4a05ce3f53 | |||
61574a8522 | |||
9400d4027a | |||
b5ec49dda1 | |||
1c66efb4fa | |||
68fca3d030 | |||
325304aebe | |||
a50cf5b198 | |||
1b7aeeafc1 | |||
6e3c2ef168 | |||
d9b4753690 | |||
ca40d7ca65 | |||
4aa8362be9 | |||
d68b61a913 | |||
20969ef249 | |||
469d35fcc1 | |||
e9b8c38eeb | |||
d4c44bf350 | |||
2d8cf7c479 | |||
ca6e715bfa | |||
3a839c947e | |||
9896de3c34 | |||
03d7520de2 | |||
4fc8b130f8 | |||
c5692f8b13 | |||
85561b5d50 | |||
88c0833ae2 | |||
7df78a0c9e | |||
30663b0301 | |||
4ffba0ed56 | |||
a522c70f92 |
@ -9,4 +9,8 @@ trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[package.json]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
2
.github/ISSUE_TEMPLATE.md
vendored
Normal file
2
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
- **resin-cli version:**
|
||||
- **Operating system and architecture:**
|
17
.gitignore
vendored
17
.gitignore
vendored
@ -13,9 +13,6 @@ lib-cov
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
@ -27,10 +24,16 @@ build/Release
|
||||
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
|
||||
node_modules
|
||||
|
||||
bin/node
|
||||
|
||||
release/build
|
||||
|
||||
npm-shrinkwrap.json
|
||||
package-lock.json
|
||||
.resinconf
|
||||
resinrc.yml
|
||||
|
||||
.idea
|
||||
.vscode
|
||||
.DS_Store
|
||||
|
||||
/tmp
|
||||
build/
|
||||
build-bin/
|
||||
resin-cli-*.zip
|
||||
|
@ -1,4 +0,0 @@
|
||||
tests
|
||||
doc
|
||||
lib
|
||||
extras
|
28
.travis.yml
Normal file
28
.travis.yml
Normal file
@ -0,0 +1,28 @@
|
||||
language: node_js
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
node_js:
|
||||
- "6"
|
||||
before_install:
|
||||
- npm -g install npm@4
|
||||
script: npm run ci
|
||||
notifications:
|
||||
email: false
|
||||
deploy:
|
||||
- provider: script
|
||||
script: npm run release
|
||||
skip_cleanup: true
|
||||
on:
|
||||
tags: true
|
||||
condition: "$TRAVIS_TAG =~ ^v?[[:digit:]]+\\.[[:digit:]]+\\.[[:digit:]]+"
|
||||
repo: resin-io/resin-cli
|
||||
- provider: npm
|
||||
email: accounts@resin.io
|
||||
api_key:
|
||||
secure: phet6Du13hc1bzStbmpwy2ODNL5BFwjAmnpJ5wMcbWfI7fl0OtQ61s2+vW5hJAvm9fiRLOfiGAEiqOOtoupShZ1X8BNkC708d8+V+iZMoFh3+j6wAEz+N1sVq471PywlOuLAscOcqQNp92giCVt+4VPx2WQYh06nLsunvysGmUM=
|
||||
skip_cleanup: true
|
||||
on:
|
||||
tags: true
|
||||
condition: "$TRAVIS_TAG =~ ^v?[[:digit:]]+\\.[[:digit:]]+\\.[[:digit:]]+"
|
||||
repo: resin-io/resin-cli
|
557
CHANGELOG.md
557
CHANGELOG.md
@ -1,22 +1,540 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
All notable changes to this project will be documented in this file
|
||||
automatically by Versionist. DO NOT EDIT THIS FILE MANUALLY!
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [2.7.0] - 2016-03-07
|
||||
## v6.10.2 - 2017-11-27
|
||||
|
||||
* Inline the entire resin-cli-auth module #721 [Tim Perry]
|
||||
|
||||
## v6.10.1 - 2017-11-27
|
||||
|
||||
* Set up TypeScript compilation, and make a small start on converting the CLI #720 [Tim Perry]
|
||||
* Don't commit raw JS build output #720 [Tim Perry]
|
||||
|
||||
## v6.10.0 - 2017-11-17
|
||||
|
||||
* Allow `os configure` to configure for an app, not just a specific device #718 [Tim Perry]
|
||||
* Print help even for expected errors #718 [Tim Perry]
|
||||
|
||||
## v6.9.0 - 2017-11-16
|
||||
|
||||
* Allow non-interactice config generate for simple network settings #714 [Tim Perry]
|
||||
* Fix issue where network settings were not used by `config generate` #714 [Tim Perry]
|
||||
|
||||
## v6.8.3 - 2017-11-16
|
||||
|
||||
* Remove resin promote command (which has never worked) to wait for larger resinOS provisioning updates #717 [Tim Perry]
|
||||
|
||||
## v6.8.2 - 2017-11-14
|
||||
|
||||
* Fix 'cannot read property R_OK of undefined' error in Node >=6 <6.3 #713 [Tim Perry]
|
||||
|
||||
## v6.8.1 - 2017-11-09
|
||||
|
||||
* Avoid AmbiguousApplication errors in device register when an id is used #711 [Tim Perry]
|
||||
|
||||
## v6.8.0 - 2017-10-27
|
||||
|
||||
* Allow preloading jetson-tx2 images, improve flasher images detection and remove the --dont-detect-flasher-type-images option. #706 [Alexis Svinartchouk]
|
||||
|
||||
## v6.7.4 - 2017-10-25
|
||||
|
||||
* Add preload to the CLI docs #702 [Tim Perry]
|
||||
|
||||
## v6.7.3 - 2017-10-25
|
||||
|
||||
* Allow specifying `--commit=latest` for `resin preload` #701 [Alexis Svinartchouk]
|
||||
|
||||
## v6.7.2 - 2017-10-24
|
||||
|
||||
* Make update-notifier more resilient and ensure it obeys NO_UPDATE_NOTIFIER, by updating it #699 [Tim Perry]
|
||||
|
||||
## v6.7.1 - 2017-10-24
|
||||
|
||||
* Respect the -dont-check-device-type option, fix error message #697 [Alexis Svinartchouk]
|
||||
|
||||
## v6.7.0 - 2017-10-18
|
||||
|
||||
* Added a device api key parameter to the `os configure` command. #487 [Pagan Gazzard]
|
||||
* Added a `--device-api-key` option to the `config generate` command. #487 [Pagan Gazzard]
|
||||
* Added a `--device-api-key` option to the `device register` command. #487 [Pagan Gazzard]
|
||||
|
||||
## v6.6.13 - 2017-10-18
|
||||
|
||||
* Fix issue where `os download` would always download prod images #689 [Tim Perry]
|
||||
|
||||
## v6.6.12 - 2017-10-16
|
||||
|
||||
* Update resin-preload to 4.0.2 to support preloading Edison images #687 [Alexis Svinartchouk]
|
||||
|
||||
## v6.6.11 - 2017-10-13
|
||||
|
||||
* Document how to `resin deploy` to an app as a collaborator #685 [Tim Perry]
|
||||
|
||||
## v6.6.10 - 2017-10-09
|
||||
|
||||
* Ensure hostname truly is optional when configuring device images #676 [Tim Perry]
|
||||
|
||||
## v6.6.9 - 2017-10-06
|
||||
|
||||
* Fix resin preload --splash-image argument handling #678 [Alexis Svinartchouk]
|
||||
|
||||
## v6.6.8 - 2017-10-06
|
||||
|
||||
* Ensure analytics failures (e.g. from broken tokens) at startup don't break commands #675 [Tim Perry]
|
||||
|
||||
## v6.6.7 - 2017-09-22
|
||||
|
||||
* Update to resin-sync, which fixes local push on windows #666 [Tim Perry]
|
||||
* Add windows instructions to fix node-gyp installs #666 [Tim Perry]
|
||||
|
||||
## v6.6.6 - 2017-09-11
|
||||
|
||||
* Create ISSUE_TEMPLATE.md #655 [Kostas Lekkas]
|
||||
|
||||
## v6.6.5 - 2017-08-31
|
||||
|
||||
* Fix lodash bugs in device move & quickstart #643 [Tim Perry]
|
||||
|
||||
## v6.6.4 - 2017-08-31
|
||||
|
||||
* Catch uncommitted build output automatically in Travis #644 [Tim Perry]
|
||||
|
||||
## v6.6.3 - 2017-08-31
|
||||
|
||||
* Update README to link to the full CLI command documentation #641 [Tim Perry]
|
||||
|
||||
## v6.6.2 - 2017-08-31
|
||||
|
||||
* Use DOCKER_HOST from env if possible, and no connection options are available #642 [Tim Perry]
|
||||
|
||||
## v6.6.1 - 2017-08-28
|
||||
|
||||
* Update resin-preload to 3.1.4 #650 [Alexis Svinartchouk]
|
||||
|
||||
## v6.6.0 - 2017-08-28
|
||||
|
||||
* Add a --dont-check-device-type option for `resin preload` #647 [Alexis Svinartchouk]
|
||||
|
||||
## v6.5.3 - 2017-08-24
|
||||
|
||||
* Remove resin-preload build filtering workaround. #645 [Alexis Svinartchouk]
|
||||
|
||||
## v6.5.2 - 2017-08-22
|
||||
|
||||
### Changed
|
||||
|
||||
- Progress bar when preload builds its docker image or fetches the build size;
|
||||
- Preload will do nothing if you try to preload a build that is already preloaded in the image.
|
||||
|
||||
## v6.5.1 - 2017-08-21
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix lodash upgrade bug that broke `resin help`
|
||||
|
||||
## v6.5.0 - 2017-08-18
|
||||
|
||||
### Changed
|
||||
|
||||
- Set up Travis npm autodeploy
|
||||
- Upgrade to Lodash v4, drastically reducing install size (due to dedupes)
|
||||
- Updated npm package description
|
||||
- Added support for looking up shared apps via [owner]/[appname] strings
|
||||
|
||||
### Added
|
||||
|
||||
- Use forked global-tunnel-ng that doesn't proxy connections to socket files
|
||||
- Preload support
|
||||
|
||||
## v6.4.0 - 2017-08-11
|
||||
|
||||
### Changed
|
||||
|
||||
- Support overlay2 in resin local push
|
||||
- Support Docker versions greater than 1.X in resin sync
|
||||
- Provide a helpful warning when Docker is not installed (or is unaccessible)
|
||||
- Added a link to the Node download page in the warning for users with old Node versions
|
||||
- Remove inconsistent (and now unneccesary) 'Tagging image as' message from local build output
|
||||
|
||||
## v6.3.1 - 2017-08-08
|
||||
|
||||
- Updated resin-cli-auth to point to the correct resin dashboard ued for authentication (for example, staging)
|
||||
|
||||
## v6.3.0 - 2017-08-03
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed compatibility of `resin local push` with docker >= 1.12
|
||||
|
||||
## v6.2.0 - 2017-07-27
|
||||
|
||||
### Changed
|
||||
|
||||
- Support the new resinOS versions where the sample connection file is called `resin-sample.ignore`
|
||||
|
||||
## v6.1.1 - 2017-07-18
|
||||
|
||||
### Changed
|
||||
|
||||
- Hide the intro quickstart message for now (until it gets renovated)
|
||||
|
||||
## v6.1.0 - 2017-06-30
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix issue where emulated builds broke Docker `ARG` commands
|
||||
- Fix issue when using resin deploy with non-standard stdin (e.g. git bash on windows)
|
||||
|
||||
### Added
|
||||
|
||||
- Bump resin-sync@8.0.1
|
||||
- Permit resin sync to collaborators
|
||||
- Fix "'cwd' must be a string" error in Node 8
|
||||
- Do not explicitly disable ControlMaster option for device SSH connections
|
||||
|
||||
## v6.0.0 - 2017-06-26
|
||||
|
||||
### Added
|
||||
|
||||
- Added support for `--squash` parameter for `resin build`
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Breaking** Remove Buffer polyfill, require Node v6+, and print warnings in older versions
|
||||
|
||||
## v5.11.1 - 2017-06-22
|
||||
|
||||
### Added
|
||||
|
||||
- Include Node version in Sentry logging
|
||||
|
||||
### Fixed
|
||||
|
||||
- Ensure to send .pem file contents rather than filename to docker daemon
|
||||
- Add a polyfill to fix `local configure` in older (<6) Node versions
|
||||
|
||||
## v5.11.0 - 2017-06-19
|
||||
|
||||
### Added
|
||||
|
||||
- `package-lock.json` for `npm5` users
|
||||
- Added ability to run an emulated build silently with `resin build`
|
||||
- Gzip images when uploading in `resin deploy`
|
||||
- Show a clear message immediately as the deploy starts, if we're deploying an image.
|
||||
- Forced update to the newest resin-sdk (^6.4.1)
|
||||
- Allow OS version selection when doing `resin device init`
|
||||
- Actually tolerate the `--yes` param to `resin device init`
|
||||
- Allows passing `--drive` to `resin device init`
|
||||
- List detected drives with `resin util available-drives`
|
||||
- Add the `resin os build-config` method to pass the interactive config step once
|
||||
and reuse the built file for subsequent `resin os configure` calls (added the new `--config` param to it),
|
||||
and for `resin device init` (same `--config` param)
|
||||
- Improve the supported device types listing
|
||||
|
||||
### Fixed
|
||||
|
||||
- Ensure emulated builds use the correct relative path to qemu when called from any location
|
||||
- Make emulated builds reliable in the presence for WORKDIR comands
|
||||
|
||||
## v5.10.2 - 2017-05-31
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed command line arguments for `resin build`
|
||||
|
||||
## v5.10.1 - 2017-05-22
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed breaking bug in `resin local ssh`
|
||||
|
||||
## v5.10.0 - 2017-05-22
|
||||
|
||||
### Added
|
||||
|
||||
- Reduce granularity of update checking to one day
|
||||
- Include extra usage metadata in error logging to help debugging
|
||||
- Add uploading of build logs when present with resin deploy
|
||||
- Highlight cache usage in a local build
|
||||
- Show a progress bar for upload progress
|
||||
- Add ability to specify build-time variables for local builds
|
||||
- Added the proxy support for the `resin ssh` command
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed the not enough unicorns bug in resin build
|
||||
- Removed the install-time warning for the `valid-email` package
|
||||
|
||||
## v5.9.1 - 2017-05-01
|
||||
|
||||
### Fixed
|
||||
|
||||
- Technical release because v5.9.0 was published earlier (erroneously)
|
||||
|
||||
## v5.9.0 - 2017-05-01
|
||||
|
||||
### Added
|
||||
|
||||
- HTTP(S) proxy support
|
||||
|
||||
## v5.8.1 - 2017-04-27
|
||||
|
||||
### Fixed
|
||||
|
||||
- The `ssh` command was broken
|
||||
|
||||
## v5.8.0 - 2017-04-26
|
||||
|
||||
### Added
|
||||
|
||||
- Add cloud builder output to local build
|
||||
- Add nocache and tag options to resin deploy
|
||||
- Add ability to build and deploy an image to resin's infrastructure
|
||||
|
||||
### Fixed
|
||||
|
||||
- Capture and report errors happening during the program initialization, like parsing invalid YAML config
|
||||
|
||||
## v5.7.2 - 2017-04-18
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed warning on NPM install due to dependency conflicts
|
||||
- Improved node v4 support (some operations are fixed, some will run faster)
|
||||
|
||||
## v5.7.1 - 2017-04-03
|
||||
|
||||
### Fixed
|
||||
|
||||
- Add basic support for the new ResinOS version format
|
||||
|
||||
## v5.7.0 - 2017-03-28
|
||||
|
||||
### Fixed
|
||||
|
||||
- The OS init issues:
|
||||
* failing to get the superuser resin auth (tried to look into `/root/.resin`)
|
||||
* failing to write to the drive
|
||||
|
||||
## v5.6.1 - 2017-03-23
|
||||
|
||||
### Added
|
||||
|
||||
- Add Sentry error tracking
|
||||
|
||||
### Fixed
|
||||
|
||||
- The unneeded warning about the default OS version download from the `device init` command.
|
||||
- Changed the help references from gitter to forums.
|
||||
|
||||
## v5.6.0 - 2017-03-23
|
||||
|
||||
### Added
|
||||
|
||||
- The `--version` option to the `os download` command. Check `resin help os download` for details.
|
||||
|
||||
## v5.5.0 - 2017-03-10
|
||||
|
||||
### Added
|
||||
|
||||
- Require superuser for scan commands, also introduce docker timeout
|
||||
- Bump resin-sync@7.0.0: use experimental rds which requires superuser permissions
|
||||
|
||||
## v5.4.0 - 2017-03-09
|
||||
|
||||
### Added
|
||||
|
||||
- Implement 'resin local stop'
|
||||
- Implement 'resin local'
|
||||
- Implement 'resin local push'
|
||||
- Implement 'resin local ssh'
|
||||
- Implement 'resin local scan'
|
||||
- Implement 'resin local promote'
|
||||
- Implement 'resin local logs'
|
||||
- Implement 'resin local flash'
|
||||
- Implement 'resin local configure'
|
||||
|
||||
### Changed
|
||||
|
||||
- Remove app create from primary commands
|
||||
|
||||
## v5.3.0 - 2017-03-03
|
||||
|
||||
### Added
|
||||
|
||||
- `resin sync` AUFS device support
|
||||
|
||||
### Changed
|
||||
|
||||
- Moved to the new version of `resin-sdk` (via `resin-sdk-preconfigured`)
|
||||
|
||||
## v5.2.4 - 2017-01-18
|
||||
|
||||
### Changed
|
||||
|
||||
- Fix documented requirements for resin ssh and resin sync
|
||||
|
||||
## v5.2.3 - 2017-01-04
|
||||
|
||||
### Changed
|
||||
|
||||
- Add missing `js-yaml` dependency.
|
||||
|
||||
## v5.2.2 - 2016-11-01
|
||||
|
||||
### Changed
|
||||
|
||||
- Fix `shutdown` command not being available.
|
||||
|
||||
## v5.2.1 - 2016-10-28
|
||||
|
||||
### Changed
|
||||
|
||||
- Fix `Boolean options can't have parameters` error in every command.
|
||||
|
||||
## v5.2.0 - 2016-10-27
|
||||
|
||||
### Added
|
||||
|
||||
- Add `shutdown` command.
|
||||
- Add `--force` option to `device reboot` command.
|
||||
|
||||
## v5.1.0 - 2016-09-25
|
||||
|
||||
### Added
|
||||
|
||||
- Add `devices supported` command.
|
||||
|
||||
## v5.0.0 - 2016-09-15
|
||||
|
||||
### Added
|
||||
|
||||
- Automatically parse '.gitignore' for file inclusions/exclusions from resin sync by default. Skip parsing with `--skip-gitignore`.
|
||||
- Automatically save options to `<sourceDirectory>/.resin-sync.yml` after every run.
|
||||
- Support user-specified destination directories with `--destination/-d` option.
|
||||
- Implement `--after` option to perform actions local (e.g. cleanup) after resin sync has finished.
|
||||
- Implement interactive dialog for destination directory, with `/usr/src/app` being the default choice.
|
||||
|
||||
### Changed
|
||||
|
||||
- Require `resin sync` `--source/-s` option if a `.resin-sync.yml` file is not found in the current directory.
|
||||
- Require `uuid` as an argument in `resin sync/ssh` (`appName` has been removed).
|
||||
- Always display interactive device selection dialog when uuid is not passed as an argument.
|
||||
- Disable ControlMaster ssh option (as reported in support).
|
||||
|
||||
## v4.5.0 - 2016-09-14
|
||||
|
||||
### Added
|
||||
|
||||
- Attempt to retrieve device type from the image's first partition.
|
||||
|
||||
## v4.4.0 - 2016-08-11
|
||||
|
||||
### Changed
|
||||
|
||||
- Display OS and Supervisor version in `devices` and `device` commands.
|
||||
|
||||
## v4.3.0 - 2016-08-11
|
||||
|
||||
### Added
|
||||
|
||||
- Implement `device public-url enable` command.
|
||||
- Implement `device public-url disable` command.
|
||||
- Implement `device public-url status` command.
|
||||
- Implement `device public-url` command.
|
||||
- Add global `--help` option.
|
||||
|
||||
## v4.2.1 - 2016-07-26
|
||||
|
||||
### Changed
|
||||
|
||||
- Fix log messages being `undefined`.
|
||||
|
||||
## v4.2.0 - 2016-06-22
|
||||
|
||||
### Added
|
||||
|
||||
- Add `verbose` option to `resin sync`.
|
||||
|
||||
## v4.1.0 - 2016-06-22
|
||||
|
||||
### Added
|
||||
|
||||
- Add `verbose` option to `resin ssh`.
|
||||
|
||||
## v4.0.3 - 2016-05-17
|
||||
|
||||
### Changed
|
||||
|
||||
- Fix `resin ssh` errors when running in `cmd.exe`.
|
||||
|
||||
## v4.0.2 - 2016-04-27
|
||||
|
||||
### Changed
|
||||
|
||||
- Upgrade `resin-sync` to v2.0.2.
|
||||
|
||||
## v4.0.1 - 2016-04-26
|
||||
|
||||
### Changed
|
||||
|
||||
- Fix unhandled exceptions in the `resin ssh` command.
|
||||
|
||||
## v4.0.0 - 2016-04-26
|
||||
|
||||
### Added
|
||||
|
||||
- Implement `resin ssh` command
|
||||
|
||||
### Changed
|
||||
|
||||
- Remove `resin sync` `exec` option.
|
||||
- Upgrade `resin-sync` to v2.0.1.
|
||||
- Upgrade `resin-sdk` to v5.3.0.
|
||||
|
||||
## v3.0.2 - 2016-04-08
|
||||
|
||||
### Changed
|
||||
|
||||
- Fix `os configure` command not working with shorter uuids.
|
||||
|
||||
## v3.0.1 - 2016-03-29
|
||||
|
||||
### Changed
|
||||
|
||||
- Log Mixpanel events based on the matching command signature.
|
||||
|
||||
## v3.0.0 - 2016-03-28
|
||||
|
||||
### Added
|
||||
|
||||
- Implement `config inject` command.
|
||||
- Document the case where `EACCES` is thrown during login because of an expired token.
|
||||
- Integrate `resin-plugin-sync` as a build-in command.
|
||||
|
||||
### Changed
|
||||
|
||||
- Allow `config generate` to generate a `config.json` for an application.
|
||||
- Force update alert to always be shown.
|
||||
- Only throw "Invalid 2FA code" if we're sure that's the cause during `login`.
|
||||
|
||||
## v2.7.0 - 2016-03-07
|
||||
|
||||
### Added
|
||||
|
||||
- Implement `config generate` command.
|
||||
- Implement `device reboot` command.
|
||||
|
||||
## [2.6.2] - 2016-02-19
|
||||
## v2.6.2 - 2016-02-19
|
||||
|
||||
### Removed
|
||||
|
||||
- Remove debugging statement in `quickstart`.
|
||||
|
||||
## [2.6.1] - 2016-02-12
|
||||
## v2.6.1 - 2016-02-12
|
||||
|
||||
### Added
|
||||
|
||||
@ -31,7 +549,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
- Improve `quickstart` messages.
|
||||
- Fix `device` example.
|
||||
|
||||
## [2.6.0] - 2016-01-21
|
||||
## v2.6.0 - 2016-01-21
|
||||
|
||||
### Added
|
||||
|
||||
@ -55,7 +573,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
- Upgrade Resin Image Manager to v3.2.6.
|
||||
- Make `devices` output shorter uuids.
|
||||
|
||||
## [2.5.0] - 2015-12-11
|
||||
## v2.5.0 - 2015-12-11
|
||||
|
||||
### Added
|
||||
|
||||
@ -67,7 +585,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
- Lazy load command actions dependencies for performance reasons.
|
||||
|
||||
## [2.4.0] - 2015-12-01
|
||||
## v2.4.0 - 2015-12-01
|
||||
|
||||
### Added
|
||||
|
||||
@ -78,7 +596,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
- Simplify download output messages.
|
||||
|
||||
## [2.3.0] - 2015-11-20
|
||||
## v2.3.0 - 2015-11-20
|
||||
|
||||
### Added
|
||||
|
||||
@ -90,13 +608,13 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
- Show uuids in `devices` command.
|
||||
- Clarify resin url in `login` and `whoami`.
|
||||
|
||||
## [2.2.0] - 2015-11-12
|
||||
## v2.2.0 - 2015-11-12
|
||||
|
||||
### Added
|
||||
|
||||
- Implement `device move` command.
|
||||
|
||||
## [2.1.0] - 2015-11-11
|
||||
## v2.1.0 - 2015-11-11
|
||||
|
||||
### Added
|
||||
|
||||
@ -107,13 +625,13 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
- Clarify the need of computer password during `sudo` in `os initialize`.
|
||||
|
||||
## [2.0.1] - 2015-10-26
|
||||
## v2.0.1 - 2015-10-26
|
||||
|
||||
### Changed
|
||||
|
||||
- Fix critical error when elevating permissions.
|
||||
|
||||
## [2.0.0] - 2015-10-26
|
||||
## v2.0.0 - 2015-10-26
|
||||
|
||||
### Added
|
||||
|
||||
@ -139,7 +657,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
- Remove project directory creation logic in `device init`.
|
||||
- Remove `app associate` command.
|
||||
|
||||
## [1.1.0] - 2015-10-13
|
||||
## v1.1.0 - 2015-10-13
|
||||
|
||||
### Added
|
||||
|
||||
@ -172,16 +690,3 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
### Removed
|
||||
|
||||
- Remove outdated information from README.
|
||||
|
||||
[2.7.0]: https://github.com/resin-io/resin-cli/compare/v2.6.2...v2.7.0
|
||||
[2.6.2]: https://github.com/resin-io/resin-cli/compare/v2.6.1...v2.6.2
|
||||
[2.6.1]: https://github.com/resin-io/resin-cli/compare/v2.6.0...v2.6.1
|
||||
[2.6.0]: https://github.com/resin-io/resin-cli/compare/v2.5.0...v2.6.0
|
||||
[2.5.0]: https://github.com/resin-io/resin-cli/compare/v2.4.0...v2.5.0
|
||||
[2.4.0]: https://github.com/resin-io/resin-cli/compare/v2.3.0...v2.4.0
|
||||
[2.3.0]: https://github.com/resin-io/resin-cli/compare/v2.2.0...v2.3.0
|
||||
[2.2.0]: https://github.com/resin-io/resin-cli/compare/v2.1.0...v2.2.0
|
||||
[2.1.0]: https://github.com/resin-io/resin-cli/compare/v2.0.1...v2.1.0
|
||||
[2.0.1]: https://github.com/resin-io/resin-cli/compare/v2.0.0...v2.0.1
|
||||
[2.0.0]: https://github.com/resin-io/resin-cli/compare/v1.1.0...v2.0.0
|
||||
[1.1.0]: https://github.com/resin-io/resin-cli/compare/v1.0.0...v1.1.0
|
||||
|
76
README.md
76
README.md
@ -1,7 +1,7 @@
|
||||
Resin CLI
|
||||
=========
|
||||
|
||||
> The official Resin CLI tool.
|
||||
> The official resin.io CLI tool.
|
||||
|
||||
[](http://badge.fury.io/js/resin-cli)
|
||||
[](https://david-dm.org/resin-io/resin-cli)
|
||||
@ -10,13 +10,37 @@ Resin CLI
|
||||
Requisites
|
||||
----------
|
||||
|
||||
- [NodeJS](https://nodejs.org) (at least v0.10)
|
||||
If you want to install the CLI directly through npm, you'll need the below. If this looks difficult,
|
||||
we do now have an experimental standalone binary release available, see ['Standalone install'](#standalone-install) below.
|
||||
|
||||
- [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`)
|
||||
|
||||
##### Windows Support
|
||||
|
||||
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+).
|
||||
|
||||
`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/).
|
||||
|
||||
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:
|
||||
|
||||
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/)
|
||||
|
||||
Getting Started
|
||||
---------------
|
||||
|
||||
### Installing
|
||||
### NPM install
|
||||
|
||||
If you've got all the requirements above, you should be able to install the CLI directly from npm. If not,
|
||||
or if you have any trouble with this, please try the new standalone install steps just below.
|
||||
|
||||
This might require elevated privileges in some environments.
|
||||
|
||||
@ -24,17 +48,45 @@ This might require elevated privileges in some environments.
|
||||
$ npm install --global --production resin-cli
|
||||
```
|
||||
|
||||
### List available commands
|
||||
### Standalone install
|
||||
|
||||
If you don't have node or a working pre-gyp environment, you can still install the CLI as a standalone
|
||||
binary. **This is in experimental and may not work perfectly yet in all environments**, but it seems to work
|
||||
well in initial cross-platform testing, so it may be useful, and we'd love your feedback if you hit any issues.
|
||||
|
||||
To install the CLI as a standalone binary:
|
||||
|
||||
* Download the latest zip for your OS from https://github.com/resin-io/resin-cli/releases.
|
||||
* Extract the contents, putting the `resin-cli` folder somewhere appropriate for your system (e.g. `C:/resin-cli`, `/usr/local/lib/resin-cli`, etc).
|
||||
* Add the `resin-cli` folder to your `PATH`. (
|
||||
[Windows instructions](https://www.computerhope.com/issues/ch000549.htm),
|
||||
[Linux instructions](https://stackoverflow.com/questions/14637979/how-to-permanently-set-path-on-linux-unix),
|
||||
[OSX instructions](https://stackoverflow.com/questions/22465332/setting-path-environment-variable-in-osx-permanently))
|
||||
* Running `resin` in a fresh command line should print the resin CLI help.
|
||||
|
||||
To update in future, simply download a new release and replace the extracted folder.
|
||||
|
||||
Have any problems, or see any unexpected behaviour? Please file an issue!
|
||||
|
||||
### Login
|
||||
|
||||
```sh
|
||||
$ resin help
|
||||
$ resin login
|
||||
```
|
||||
|
||||
### Run the quickstart wizard
|
||||
_(Typically useful, but not strictly required for all commands)_
|
||||
|
||||
```sh
|
||||
$ resin quickstart
|
||||
```
|
||||
### Run commands
|
||||
|
||||
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`.
|
||||
|
||||
---
|
||||
|
||||
Plugins
|
||||
-------
|
||||
|
||||
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!
|
||||
|
||||
FAQ
|
||||
---
|
||||
@ -55,16 +107,16 @@ Alternatively, you can edit your configuration file and set `resinUrl: resinstag
|
||||
|
||||
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 Resin OS, which erases all data after a restart.
|
||||
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 problem, 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.
|
||||
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 at our public [Gitter chat channel](https://gitter.im/resin-io/chat).
|
||||
You can also get in touch with us in the resin.io [forums](https://forums.resin.io/).
|
||||
|
||||
License
|
||||
-------
|
||||
|
@ -47,3 +47,13 @@ Or in Windows:
|
||||
```sh
|
||||
> del /s /q %UserProfile%\_resin\cache
|
||||
```
|
||||
|
||||
### 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.
|
||||
|
||||
Try resetting the ownership by running:
|
||||
|
||||
```sh
|
||||
$ sudo chown -R <user> $HOME/.resin
|
||||
```
|
||||
|
34
appveyor.yml
Normal file
34
appveyor.yml
Normal file
@ -0,0 +1,34 @@
|
||||
# appveyor file
|
||||
# http://www.appveyor.com/docs/appveyor-yml
|
||||
|
||||
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: 6
|
||||
|
||||
install:
|
||||
- ps: Install-Product node $env:nodejs_version x64
|
||||
- npm install -g npm@4
|
||||
- set PATH=%APPDATA%\npm;%PATH%
|
||||
- npm install
|
||||
|
||||
build: off
|
||||
|
||||
test_script:
|
||||
- node --version
|
||||
- npm --version
|
||||
- cmd: npm test
|
||||
|
||||
deploy_script:
|
||||
- IF "%APPVEYOR_REPO_TAG%" == "true" (npm run release)
|
||||
- IF NOT "%APPVEYOR_REPO_TAG%" == "true" (echo 'Not tagged, skipping deploy')
|
34
automation/build-bin.ts
Executable file
34
automation/build-bin.ts
Executable file
@ -0,0 +1,34 @@
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs-extra';
|
||||
import * as filehound from 'filehound';
|
||||
import { exec as execPkg } from 'pkg';
|
||||
|
||||
const ROOT = path.join(__dirname, '..');
|
||||
|
||||
console.log('Building package...\n');
|
||||
|
||||
execPkg([
|
||||
'--target', 'host',
|
||||
'--output', 'build-bin/resin',
|
||||
'package.json'
|
||||
]).then(() => fs.copy(
|
||||
path.join(ROOT, 'node_modules', 'opn', 'xdg-open'),
|
||||
path.join(ROOT, 'build-bin', 'xdg-open')
|
||||
)).then(() => {
|
||||
return filehound.create()
|
||||
.paths(path.join(ROOT, 'node_modules'))
|
||||
.ext(['node', 'dll'])
|
||||
.find();
|
||||
}).then((nativeExtensions) => {
|
||||
console.log(`\nCopying to build-bin:\n${nativeExtensions.join('\n')}`);
|
||||
|
||||
return nativeExtensions.map((extPath) => {
|
||||
return fs.copy(
|
||||
extPath,
|
||||
extPath.replace(
|
||||
path.join(ROOT, 'node_modules'),
|
||||
path.join(ROOT, 'build-bin')
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
35
automation/custom-types.d.ts
vendored
Normal file
35
automation/custom-types.d.ts
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
declare module 'pkg' {
|
||||
export function exec(args: string[]): Promise<void>;
|
||||
}
|
||||
|
||||
declare module 'filehound' {
|
||||
export function create(): FileHound;
|
||||
|
||||
export interface FileHound {
|
||||
paths(paths: string[]): FileHound;
|
||||
paths(...paths: string[]): FileHound;
|
||||
ext(extensions: string[]): FileHound;
|
||||
ext(...extensions: string[]): FileHound;
|
||||
find(): Promise<string[]>;
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'publish-release' {
|
||||
interface PublishOptions {
|
||||
token: string,
|
||||
owner: string,
|
||||
repo: string,
|
||||
tag: string,
|
||||
name: string,
|
||||
reuseRelease?: boolean
|
||||
assets: string[]
|
||||
}
|
||||
|
||||
interface Release {
|
||||
html_url: string;
|
||||
}
|
||||
|
||||
let publishRelease: (args: PublishOptions, callback: (e: Error, release: Release) => void) => void;
|
||||
|
||||
export = publishRelease;
|
||||
}
|
53
automation/deploy-bin.ts
Normal file
53
automation/deploy-bin.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import * as Promise from 'bluebird';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import * as fs from 'fs-extra';
|
||||
import * as publishRelease from 'publish-release';
|
||||
import * as archiver from 'archiver';
|
||||
|
||||
const publishReleaseAsync = Promise.promisify(publishRelease);
|
||||
|
||||
const { GITHUB_TOKEN } = process.env;
|
||||
const ROOT = path.join(__dirname, '..');
|
||||
|
||||
const version = 'v' + require('../package.json').version;
|
||||
const outputFile = path.join(ROOT, `resin-cli-${version}-${os.platform()}-${os.arch()}.zip`);
|
||||
|
||||
new Promise((resolve, reject) => {
|
||||
console.log('Zipping build...');
|
||||
|
||||
let archive = archiver('zip', {
|
||||
zlib: { level: 7 }
|
||||
});
|
||||
archive.directory(path.join(ROOT, 'build-bin'), 'resin-cli');
|
||||
|
||||
let 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();
|
||||
}).then(() => {
|
||||
console.log('Build zipped');
|
||||
console.log('Publishing build...');
|
||||
|
||||
return publishReleaseAsync({
|
||||
token: GITHUB_TOKEN,
|
||||
owner: 'resin-io',
|
||||
repo: 'resin-cli',
|
||||
tag: version,
|
||||
name: `Resin-CLI ${version}`,
|
||||
reuseRelease: true,
|
||||
assets: [outputFile]
|
||||
});
|
||||
}).then((release) => {
|
||||
console.log(`Release ${version} successful: ${release.html_url}`);
|
||||
}).catch((err) => {
|
||||
console.error('Release failed');
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
15
automation/tsconfig.json
Normal file
15
automation/tsconfig.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es2015",
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"preserveConstEnums": true,
|
||||
"removeComments": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": [
|
||||
"./**/*.ts"
|
||||
]
|
||||
}
|
@ -1,115 +0,0 @@
|
||||
|
||||
/*
|
||||
Copyright 2016 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.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
var commandOptions;
|
||||
|
||||
commandOptions = require('./command-options');
|
||||
|
||||
exports.create = {
|
||||
signature: 'app create <name>',
|
||||
description: 'create an application',
|
||||
help: 'Use this command to create a new resin.io application.\n\nYou can specify the application type with the `--type` option.\nOtherwise, an interactive dropdown will be shown for you to select from.\n\nYou can see a list of supported device types with\n\n $ resin devices supported\n\nExamples:\n\n $ resin app create MyApp\n $ resin app create MyApp --type raspberry-pi',
|
||||
options: [
|
||||
{
|
||||
signature: 'type',
|
||||
parameter: 'type',
|
||||
description: 'application type',
|
||||
alias: 't'
|
||||
}
|
||||
],
|
||||
permission: 'user',
|
||||
primary: true,
|
||||
action: function(params, options, done) {
|
||||
var patterns, resin;
|
||||
resin = require('resin-sdk');
|
||||
patterns = require('../utils/patterns');
|
||||
return resin.models.application.has(params.name).then(function(hasApplication) {
|
||||
if (hasApplication) {
|
||||
throw new Error('You already have an application with that name!');
|
||||
}
|
||||
}).then(function() {
|
||||
return options.type || patterns.selectDeviceType();
|
||||
}).then(function(deviceType) {
|
||||
return resin.models.application.create(params.name, deviceType);
|
||||
}).then(function(application) {
|
||||
return console.info("Application created: " + application.app_name + " (" + application.device_type + ", id " + application.id + ")");
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
exports.list = {
|
||||
signature: 'apps',
|
||||
description: 'list all applications',
|
||||
help: 'Use this command to list all your applications.\n\nNotice this command only shows the most important bits of information for each app.\nIf you want detailed information, use resin app <name> instead.\n\nExamples:\n\n $ resin apps',
|
||||
permission: 'user',
|
||||
primary: true,
|
||||
action: function(params, options, done) {
|
||||
var resin, visuals;
|
||||
resin = require('resin-sdk');
|
||||
visuals = require('resin-cli-visuals');
|
||||
return resin.models.application.getAll().then(function(applications) {
|
||||
return console.log(visuals.table.horizontal(applications, ['id', 'app_name', 'device_type', 'online_devices', 'devices_length']));
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
exports.info = {
|
||||
signature: 'app <name>',
|
||||
description: 'list a single application',
|
||||
help: 'Use this command to show detailed information for a single application.\n\nExamples:\n\n $ resin app MyApp',
|
||||
permission: 'user',
|
||||
primary: true,
|
||||
action: function(params, options, done) {
|
||||
var resin, visuals;
|
||||
resin = require('resin-sdk');
|
||||
visuals = require('resin-cli-visuals');
|
||||
return resin.models.application.get(params.name).then(function(application) {
|
||||
return console.log(visuals.table.vertical(application, ["$" + application.app_name + "$", 'id', 'device_type', 'git_repository', 'commit']));
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
exports.restart = {
|
||||
signature: 'app restart <name>',
|
||||
description: 'restart an application',
|
||||
help: 'Use this command to restart all devices that belongs to a certain application.\n\nExamples:\n\n $ resin app restart MyApp',
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
var resin;
|
||||
resin = require('resin-sdk');
|
||||
return resin.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.\n\nNotice this command asks for confirmation interactively.\nYou can avoid this by passing the `--yes` boolean option.\n\nExamples:\n\n $ resin app rm MyApp\n $ resin app rm MyApp --yes',
|
||||
options: [commandOptions.yes],
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
var patterns, resin;
|
||||
resin = require('resin-sdk');
|
||||
patterns = require('../utils/patterns');
|
||||
return patterns.confirm(options.yes, 'Are you sure you want to delete the application?').then(function() {
|
||||
return resin.models.application.remove(params.name);
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
}).call(this);
|
@ -1,163 +0,0 @@
|
||||
|
||||
/*
|
||||
Copyright 2016 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.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
exports.login = {
|
||||
signature: 'login',
|
||||
description: 'login to resin.io',
|
||||
help: 'Use this command to login to your resin.io account.\n\nThis command will prompt you to login using the following login types:\n\n- Web authorization: open your web browser and prompt you to authorize the CLI\nfrom the dashboard.\n\n- Credentials: using email/password and 2FA.\n\n- Token: using the authentication token from the preferences page.\n\nExamples:\n\n $ resin login\n $ resin login --web\n $ resin login --token "..."\n $ resin login --credentials\n $ resin login --credentials --email johndoe@gmail.com --password secret',
|
||||
options: [
|
||||
{
|
||||
signature: 'token',
|
||||
description: 'auth token',
|
||||
parameter: 'token',
|
||||
alias: 't'
|
||||
}, {
|
||||
signature: 'web',
|
||||
description: 'web-based login',
|
||||
boolean: true,
|
||||
alias: 'w'
|
||||
}, {
|
||||
signature: 'credentials',
|
||||
description: 'credential-based login',
|
||||
boolean: true,
|
||||
alias: 'c'
|
||||
}, {
|
||||
signature: 'email',
|
||||
parameter: 'email',
|
||||
description: 'email',
|
||||
alias: ['e', 'u']
|
||||
}, {
|
||||
signature: 'password',
|
||||
parameter: 'password',
|
||||
description: 'password',
|
||||
alias: 'p'
|
||||
}
|
||||
],
|
||||
primary: true,
|
||||
action: function(params, options, done) {
|
||||
var Promise, _, auth, capitano, form, login, messages, patterns, resin;
|
||||
_ = require('lodash');
|
||||
Promise = require('bluebird');
|
||||
capitano = Promise.promisifyAll(require('capitano'));
|
||||
resin = require('resin-sdk');
|
||||
auth = require('resin-cli-auth');
|
||||
form = require('resin-cli-form');
|
||||
patterns = require('../utils/patterns');
|
||||
messages = require('../utils/messages');
|
||||
login = function(options) {
|
||||
if (options.token != null) {
|
||||
return Promise["try"](function() {
|
||||
if (_.isString(options.token)) {
|
||||
return options.token;
|
||||
}
|
||||
return form.ask({
|
||||
message: 'Token (from the preferences page)',
|
||||
name: 'token',
|
||||
type: 'input'
|
||||
});
|
||||
}).then(resin.auth.loginWithToken);
|
||||
} else if (options.credentials) {
|
||||
return patterns.authenticate(options);
|
||||
} else if (options.web) {
|
||||
console.info('Connecting to the web dashboard');
|
||||
return auth.login();
|
||||
}
|
||||
return patterns.askLoginType().then(function(loginType) {
|
||||
if (loginType === 'register') {
|
||||
return capitano.runAsync('signup');
|
||||
}
|
||||
options[loginType] = true;
|
||||
return login(options);
|
||||
});
|
||||
};
|
||||
return resin.settings.get('resinUrl').then(function(resinUrl) {
|
||||
console.log(messages.resinAsciiArt);
|
||||
console.log("\nLogging in to " + resinUrl);
|
||||
return login(options);
|
||||
}).then(resin.auth.whoami).tap(function(username) {
|
||||
console.info("Successfully logged in as: " + username);
|
||||
return console.info("\nNow what?\n\n" + messages.gettingStarted + "\n\nFind out about more super powers by running:\n\n $ resin help\n\n" + messages.reachingOut);
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
exports.logout = {
|
||||
signature: 'logout',
|
||||
description: 'logout from resin.io',
|
||||
help: 'Use this command to logout from your resin.io account.o\n\nExamples:\n\n $ resin logout',
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
var resin;
|
||||
resin = require('resin-sdk');
|
||||
return 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.\n\nIf signup is successful, you\'ll be logged in to your new user automatically.\n\nExamples:\n\n $ resin signup\n Email: me@mycompany.com\n Username: johndoe\n Password: ***********\n\n $ resin whoami\n johndoe',
|
||||
action: function(params, options, done) {
|
||||
var form, resin, validation;
|
||||
resin = require('resin-sdk');
|
||||
form = require('resin-cli-form');
|
||||
validation = require('../utils/validation');
|
||||
return resin.settings.get('resinUrl').then(function(resinUrl) {
|
||||
console.log("\nRegistering to " + resinUrl);
|
||||
return form.run([
|
||||
{
|
||||
message: 'Email:',
|
||||
name: 'email',
|
||||
type: 'input',
|
||||
validate: validation.validateEmail
|
||||
}, {
|
||||
message: 'Username:',
|
||||
name: 'username',
|
||||
type: 'input'
|
||||
}, {
|
||||
message: 'Password:',
|
||||
name: 'password',
|
||||
type: 'password',
|
||||
validate: validation.validatePassword
|
||||
}
|
||||
]);
|
||||
}).then(resin.auth.register).then(resin.auth.loginWithToken).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
exports.whoami = {
|
||||
signature: 'whoami',
|
||||
description: 'get current username and email address',
|
||||
help: 'Use this command to find out the current logged in username and email address.\n\nExamples:\n\n $ resin whoami',
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
var Promise, resin, visuals;
|
||||
Promise = require('bluebird');
|
||||
resin = require('resin-sdk');
|
||||
visuals = require('resin-cli-visuals');
|
||||
return Promise.props({
|
||||
username: resin.auth.whoami(),
|
||||
email: resin.auth.getEmail(),
|
||||
url: resin.settings.get('resinUrl')
|
||||
}).then(function(results) {
|
||||
return console.log(visuals.table.vertical(results, ['$account information$', 'username', 'email', 'url']));
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
}).call(this);
|
@ -1,76 +0,0 @@
|
||||
|
||||
/*
|
||||
Copyright 2016 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.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
var _;
|
||||
|
||||
_ = 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.booleanDevice = {
|
||||
signature: 'device',
|
||||
description: 'device',
|
||||
boolean: true,
|
||||
alias: 'd'
|
||||
};
|
||||
|
||||
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'
|
||||
};
|
||||
|
||||
}).call(this);
|
@ -1,185 +0,0 @@
|
||||
|
||||
/*
|
||||
Copyright 2016 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.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
exports.read = {
|
||||
signature: 'config read',
|
||||
description: 'read a device configuration',
|
||||
help: 'Use this command to read the config.json file from a provisioned device\n\nExamples:\n\n $ resin config read --type raspberry-pi\n $ resin config read --type raspberry-pi --drive /dev/disk2',
|
||||
options: [
|
||||
{
|
||||
signature: 'type',
|
||||
description: 'device type',
|
||||
parameter: 'type',
|
||||
alias: 't',
|
||||
required: 'You have to specify a device type'
|
||||
}, {
|
||||
signature: 'drive',
|
||||
description: 'drive',
|
||||
parameter: 'drive',
|
||||
alias: 'd'
|
||||
}
|
||||
],
|
||||
permission: 'user',
|
||||
root: true,
|
||||
action: function(params, options, done) {
|
||||
var Promise, config, prettyjson, umount, visuals;
|
||||
Promise = require('bluebird');
|
||||
config = require('resin-config-json');
|
||||
visuals = require('resin-cli-visuals');
|
||||
umount = Promise.promisifyAll(require('umount'));
|
||||
prettyjson = require('prettyjson');
|
||||
return Promise["try"](function() {
|
||||
return options.drive || visuals.drive('Select the device drive');
|
||||
}).tap(umount.umountAsync).then(function(drive) {
|
||||
return config.read(drive, options.type);
|
||||
}).tap(function(configJSON) {
|
||||
return console.info(prettyjson.render(configJSON));
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
exports.write = {
|
||||
signature: 'config write <key> <value>',
|
||||
description: 'write a device configuration',
|
||||
help: 'Use this command to write the config.json file of a provisioned device\n\nExamples:\n\n $ resin config write --type raspberry-pi username johndoe\n $ resin config write --type raspberry-pi --drive /dev/disk2 username johndoe\n $ resin config write --type raspberry-pi files.network/settings "..."',
|
||||
options: [
|
||||
{
|
||||
signature: 'type',
|
||||
description: 'device type',
|
||||
parameter: 'type',
|
||||
alias: 't',
|
||||
required: 'You have to specify a device type'
|
||||
}, {
|
||||
signature: 'drive',
|
||||
description: 'drive',
|
||||
parameter: 'drive',
|
||||
alias: 'd'
|
||||
}
|
||||
],
|
||||
permission: 'user',
|
||||
root: true,
|
||||
action: function(params, options, done) {
|
||||
var Promise, _, config, umount, visuals;
|
||||
Promise = require('bluebird');
|
||||
_ = require('lodash');
|
||||
config = require('resin-config-json');
|
||||
visuals = require('resin-cli-visuals');
|
||||
umount = Promise.promisifyAll(require('umount'));
|
||||
return Promise["try"](function() {
|
||||
return options.drive || visuals.drive('Select the device drive');
|
||||
}).tap(umount.umountAsync).then(function(drive) {
|
||||
return config.read(drive, options.type).then(function(configJSON) {
|
||||
console.info("Setting " + params.key + " to " + params.value);
|
||||
_.set(configJSON, params.key, params.value);
|
||||
return configJSON;
|
||||
}).tap(function() {
|
||||
return umount.umountAsync(drive);
|
||||
}).then(function(configJSON) {
|
||||
return config.write(drive, options.type, configJSON);
|
||||
});
|
||||
}).tap(function() {
|
||||
return console.info('Done');
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
exports.reconfigure = {
|
||||
signature: 'config reconfigure',
|
||||
description: 'reconfigure a provisioned device',
|
||||
help: 'Use this command to reconfigure a provisioned device\n\nExamples:\n\n $ resin config reconfigure --type raspberry-pi\n $ resin config reconfigure --type raspberry-pi --advanced\n $ resin config reconfigure --type raspberry-pi --drive /dev/disk2',
|
||||
options: [
|
||||
{
|
||||
signature: 'type',
|
||||
description: 'device type',
|
||||
parameter: 'type',
|
||||
alias: 't',
|
||||
required: 'You have to specify a device type'
|
||||
}, {
|
||||
signature: 'drive',
|
||||
description: 'drive',
|
||||
parameter: 'drive',
|
||||
alias: 'd'
|
||||
}, {
|
||||
signature: 'advanced',
|
||||
description: 'show advanced commands',
|
||||
boolean: true,
|
||||
alias: 'v'
|
||||
}
|
||||
],
|
||||
permission: 'user',
|
||||
root: true,
|
||||
action: function(params, options, done) {
|
||||
var Promise, capitano, config, umount, visuals;
|
||||
Promise = require('bluebird');
|
||||
config = require('resin-config-json');
|
||||
visuals = require('resin-cli-visuals');
|
||||
capitano = Promise.promisifyAll(require('capitano'));
|
||||
umount = Promise.promisifyAll(require('umount'));
|
||||
return Promise["try"](function() {
|
||||
return options.drive || visuals.drive('Select the device drive');
|
||||
}).tap(umount.umountAsync).then(function(drive) {
|
||||
return config.read(drive, options.type).get('uuid').tap(function() {
|
||||
return umount.umountAsync(drive);
|
||||
}).then(function(uuid) {
|
||||
var configureCommand;
|
||||
configureCommand = "os configure " + drive + " " + uuid;
|
||||
if (options.advanced) {
|
||||
configureCommand += ' --advanced';
|
||||
}
|
||||
return capitano.runAsync(configureCommand);
|
||||
});
|
||||
}).then(function() {
|
||||
return console.info('Done');
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
exports.generate = {
|
||||
signature: 'config generate <uuid>',
|
||||
description: 'generate a config.json file',
|
||||
help: 'Use this command to generate a config.json for a device\n\nExamples:\n\n $ resin config generate 7cf02a6\n $ resin config generate 7cf02a6 --output config.json',
|
||||
options: [
|
||||
{
|
||||
signature: 'output',
|
||||
description: 'output',
|
||||
parameter: 'output',
|
||||
alias: 'o'
|
||||
}
|
||||
],
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
var Promise, _, deviceConfig, form, fs, prettyjson, resin;
|
||||
Promise = require('bluebird');
|
||||
fs = Promise.promisifyAll(require('fs'));
|
||||
resin = require('resin-sdk');
|
||||
_ = require('lodash');
|
||||
form = require('resin-cli-form');
|
||||
deviceConfig = require('resin-device-config');
|
||||
prettyjson = require('prettyjson');
|
||||
return resin.models.device.get(params.uuid).then(function(device) {
|
||||
return resin.models.device.getManifestBySlug(device.device_type).get('options').then(form.run).then(_.partial(deviceConfig.get, device.uuid));
|
||||
}).then(function(config) {
|
||||
if (options.output != null) {
|
||||
return fs.writeFileAsync(options.output, JSON.stringify(config));
|
||||
}
|
||||
return console.log(prettyjson.render(config));
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
}).call(this);
|
@ -1,246 +0,0 @@
|
||||
|
||||
/*
|
||||
Copyright 2016 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.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
var commandOptions;
|
||||
|
||||
commandOptions = require('./command-options');
|
||||
|
||||
exports.list = {
|
||||
signature: 'devices',
|
||||
description: 'list all devices',
|
||||
help: 'Use this command to list all devices that belong to you.\n\nYou can filter the devices by application by using the `--application` option.\n\nExamples:\n\n $ resin devices\n $ resin devices --application MyApp\n $ resin devices --app MyApp\n $ resin devices -a MyApp',
|
||||
options: [commandOptions.optionalApplication],
|
||||
permission: 'user',
|
||||
primary: true,
|
||||
action: function(params, options, done) {
|
||||
var Promise, _, resin, visuals;
|
||||
Promise = require('bluebird');
|
||||
_ = require('lodash');
|
||||
resin = require('resin-sdk');
|
||||
visuals = require('resin-cli-visuals');
|
||||
return Promise["try"](function() {
|
||||
if (options.application != null) {
|
||||
return resin.models.device.getAllByApplication(options.application);
|
||||
}
|
||||
return resin.models.device.getAll();
|
||||
}).tap(function(devices) {
|
||||
devices = _.map(devices, function(device) {
|
||||
device.uuid = device.uuid.slice(0, 7);
|
||||
return device;
|
||||
});
|
||||
return console.log(visuals.table.horizontal(devices, ['id', 'uuid', 'name', 'device_type', 'application_name', 'status', 'is_online']));
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
exports.info = {
|
||||
signature: 'device <uuid>',
|
||||
description: 'list a single device',
|
||||
help: 'Use this command to show information about a single device.\n\nExamples:\n\n $ resin device 7cf02a6',
|
||||
permission: 'user',
|
||||
primary: true,
|
||||
action: function(params, options, done) {
|
||||
var resin, visuals;
|
||||
resin = require('resin-sdk');
|
||||
visuals = require('resin-cli-visuals');
|
||||
return resin.models.device.get(params.uuid).then(function(device) {
|
||||
return resin.models.device.getStatus(device).then(function(status) {
|
||||
device.status = status;
|
||||
return console.log(visuals.table.vertical(device, ["$" + device.name + "$", 'id', 'device_type', 'status', 'is_online', 'ip_address', 'application_name', 'last_seen', 'uuid', 'commit', 'supervisor_version', 'is_web_accessible', 'note']));
|
||||
});
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
exports.register = {
|
||||
signature: 'device register <application>',
|
||||
description: 'register a device',
|
||||
help: 'Use this command to register a device to an application.\n\nExamples:\n\n $ resin device register MyApp',
|
||||
permission: 'user',
|
||||
options: [
|
||||
{
|
||||
signature: 'uuid',
|
||||
description: 'custom uuid',
|
||||
parameter: 'uuid',
|
||||
alias: 'u'
|
||||
}
|
||||
],
|
||||
action: function(params, options, done) {
|
||||
var Promise, resin;
|
||||
Promise = require('bluebird');
|
||||
resin = require('resin-sdk');
|
||||
return resin.models.application.get(params.application).then(function(application) {
|
||||
return Promise["try"](function() {
|
||||
return options.uuid || resin.models.device.generateUUID();
|
||||
}).then(function(uuid) {
|
||||
console.info("Registering to " + application.app_name + ": " + uuid);
|
||||
return resin.models.device.register(application.app_name, uuid);
|
||||
});
|
||||
}).get('uuid').nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
exports.remove = {
|
||||
signature: 'device rm <uuid>',
|
||||
description: 'remove a device',
|
||||
help: 'Use this command to remove a device from resin.io.\n\nNotice this command asks for confirmation interactively.\nYou can avoid this by passing the `--yes` boolean option.\n\nExamples:\n\n $ resin device rm 7cf02a6\n $ resin device rm 7cf02a6 --yes',
|
||||
options: [commandOptions.yes],
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
var patterns, resin;
|
||||
resin = require('resin-sdk');
|
||||
patterns = require('../utils/patterns');
|
||||
return patterns.confirm(options.yes, 'Are you sure you want to delete the device?').then(function() {
|
||||
return resin.models.device.remove(params.uuid);
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
exports.identify = {
|
||||
signature: 'device identify <uuid>',
|
||||
description: 'identify a device with a UUID',
|
||||
help: 'Use this command to identify a device.\n\nIn the Raspberry Pi, the ACT led is blinked several times.\n\nExamples:\n\n $ resin device identify 23c73a1',
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
var resin;
|
||||
resin = require('resin-sdk');
|
||||
return resin.models.device.identify(params.uuid).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
exports.reboot = {
|
||||
signature: 'device reboot <uuid>',
|
||||
description: 'restart a device',
|
||||
help: 'Use this command to remotely reboot a device\n\nExamples:\n\n $ resin device reboot 23c73a1',
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
var resin;
|
||||
resin = require('resin-sdk');
|
||||
return resin.models.device.reboot(params.uuid).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
exports.rename = {
|
||||
signature: 'device rename <uuid> [newName]',
|
||||
description: 'rename a resin device',
|
||||
help: 'Use this command to rename a device.\n\nIf you omit the name, you\'ll get asked for it interactively.\n\nExamples:\n\n $ resin device rename 7cf02a6\n $ resin device rename 7cf02a6 MyPi',
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
var Promise, _, form, resin;
|
||||
Promise = require('bluebird');
|
||||
_ = require('lodash');
|
||||
resin = require('resin-sdk');
|
||||
form = require('resin-cli-form');
|
||||
return Promise["try"](function() {
|
||||
if (!_.isEmpty(params.newName)) {
|
||||
return params.newName;
|
||||
}
|
||||
return form.ask({
|
||||
message: 'How do you want to name this device?',
|
||||
type: 'input'
|
||||
});
|
||||
}).then(_.partial(resin.models.device.rename, params.uuid)).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
exports.move = {
|
||||
signature: 'device move <uuid>',
|
||||
description: 'move a device to another application',
|
||||
help: 'Use this command to move a device to another application you own.\n\nIf you omit the application, you\'ll get asked for it interactively.\n\nExamples:\n\n $ resin device move 7cf02a6\n $ resin device move 7cf02a6 --application MyNewApp',
|
||||
permission: 'user',
|
||||
options: [commandOptions.optionalApplication],
|
||||
action: function(params, options, done) {
|
||||
var _, patterns, resin;
|
||||
resin = require('resin-sdk');
|
||||
_ = require('lodash');
|
||||
patterns = require('../utils/patterns');
|
||||
return resin.models.device.get(params.uuid).then(function(device) {
|
||||
return options.application || patterns.selectApplication(function(application) {
|
||||
return _.all([application.device_type === device.device_type, device.application_name !== application.app_name]);
|
||||
});
|
||||
}).tap(function(application) {
|
||||
return resin.models.device.move(params.uuid, application);
|
||||
}).then(function(application) {
|
||||
return console.info(params.uuid + " was moved to " + application);
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
exports.init = {
|
||||
signature: 'device init',
|
||||
description: 'initialise a device with resin os',
|
||||
help: 'Use this command to download the OS image of a certain application and write it to an SD Card.\n\nNotice this command may ask for confirmation interactively.\nYou can avoid this by passing the `--yes` boolean option.\n\nExamples:\n\n $ resin device init\n $ resin device init --application MyApp',
|
||||
options: [
|
||||
commandOptions.optionalApplication, commandOptions.yes, {
|
||||
signature: 'advanced',
|
||||
description: 'enable advanced configuration',
|
||||
boolean: true,
|
||||
alias: 'v'
|
||||
}
|
||||
],
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
var Promise, capitano, helpers, patterns, resin, rimraf, tmp;
|
||||
Promise = require('bluebird');
|
||||
capitano = Promise.promisifyAll(require('capitano'));
|
||||
rimraf = Promise.promisify(require('rimraf'));
|
||||
tmp = Promise.promisifyAll(require('tmp'));
|
||||
tmp.setGracefulCleanup();
|
||||
resin = require('resin-sdk');
|
||||
helpers = require('../utils/helpers');
|
||||
patterns = require('../utils/patterns');
|
||||
return Promise["try"](function() {
|
||||
if (options.application != null) {
|
||||
return options.application;
|
||||
}
|
||||
return patterns.selectApplication();
|
||||
}).then(resin.models.application.get).then(function(application) {
|
||||
var download;
|
||||
download = function() {
|
||||
return tmp.tmpNameAsync().then(function(temporalPath) {
|
||||
return capitano.runAsync("os download " + application.device_type + " --output " + temporalPath);
|
||||
}).disposer(function(temporalPath) {
|
||||
return rimraf(temporalPath);
|
||||
});
|
||||
};
|
||||
return Promise.using(download(), function(temporalPath) {
|
||||
return capitano.runAsync("device register " + application.app_name).then(resin.models.device.get).tap(function(device) {
|
||||
var configure;
|
||||
configure = "os configure " + temporalPath + " " + device.uuid;
|
||||
if (options.advanced) {
|
||||
configure += ' --advanced';
|
||||
}
|
||||
return capitano.runAsync(configure).then(function() {
|
||||
var message;
|
||||
message = 'Initializing a device requires administrative permissions\ngiven that we need to access raw devices directly.\n';
|
||||
return helpers.sudo(['os', 'initialize', temporalPath, '--type', application.device_type], message);
|
||||
})["catch"](function(error) {
|
||||
return resin.models.device.remove(device.uuid)["finally"](function() {
|
||||
throw error;
|
||||
});
|
||||
});
|
||||
});
|
||||
}).then(function(device) {
|
||||
console.log('Done');
|
||||
return device.uuid;
|
||||
});
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
}).call(this);
|
@ -1,134 +0,0 @@
|
||||
|
||||
/*
|
||||
Copyright 2016 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.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
var commandOptions;
|
||||
|
||||
commandOptions = require('./command-options');
|
||||
|
||||
exports.list = {
|
||||
signature: 'envs',
|
||||
description: 'list all environment variables',
|
||||
help: 'Use this command to list all environment variables for\na particular application or device.\n\nThis command lists all custom environment variables.\nIf you want to see all environment variables, including private\nones used by resin, use the verbose option.\n\nExample:\n\n $ resin envs --application MyApp\n $ resin envs --application MyApp --verbose\n $ resin envs --device 7cf02a6',
|
||||
options: [
|
||||
commandOptions.optionalApplication, commandOptions.optionalDevice, {
|
||||
signature: 'verbose',
|
||||
description: 'show private environment variables',
|
||||
boolean: true,
|
||||
alias: 'v'
|
||||
}
|
||||
],
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
var Promise, _, resin, visuals;
|
||||
Promise = require('bluebird');
|
||||
_ = require('lodash');
|
||||
resin = require('resin-sdk');
|
||||
visuals = require('resin-cli-visuals');
|
||||
return Promise["try"](function() {
|
||||
if (options.application != null) {
|
||||
return resin.models.environmentVariables.getAllByApplication(options.application);
|
||||
} else if (options.device != null) {
|
||||
return resin.models.environmentVariables.device.getAll(options.device);
|
||||
} else {
|
||||
throw new Error('You must specify an application or device');
|
||||
}
|
||||
}).tap(function(environmentVariables) {
|
||||
var isSystemVariable;
|
||||
if (_.isEmpty(environmentVariables)) {
|
||||
throw new Error('No environment variables found');
|
||||
}
|
||||
if (!options.verbose) {
|
||||
isSystemVariable = resin.models.environmentVariables.isSystemVariable;
|
||||
environmentVariables = _.reject(environmentVariables, isSystemVariable);
|
||||
}
|
||||
return 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.\n\nDon\'t remove resin specific variables, as things might not work as expected.\n\nNotice this command asks for confirmation interactively.\nYou can avoid this by passing the `--yes` boolean option.\n\nIf you want to eliminate a device environment variable, pass the `--device` boolean option.\n\nExamples:\n\n $ resin env rm 215\n $ resin env rm 215 --yes\n $ resin env rm 215 --device',
|
||||
options: [commandOptions.yes, commandOptions.booleanDevice],
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
var patterns, resin;
|
||||
resin = require('resin-sdk');
|
||||
patterns = require('../utils/patterns');
|
||||
return patterns.confirm(options.yes, 'Are you sure you want to delete the environment variable?').then(function() {
|
||||
if (options.device) {
|
||||
return resin.models.environmentVariables.device.remove(params.id);
|
||||
} else {
|
||||
return 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.\n\nIf value is omitted, the tool will attempt to use the variable\'s value\nas defined in your host machine.\n\nUse the `--device` option if you want to assign the environment variable\nto a specific device.\n\nIf the value is grabbed from the environment, a warning message will be printed.\nUse `--quiet` to remove it.\n\nExamples:\n\n $ resin env add EDITOR vim --application MyApp\n $ resin env add TERM --application MyApp\n $ resin env add EDITOR vim --device 7cf02a6',
|
||||
options: [commandOptions.optionalApplication, commandOptions.optionalDevice],
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
var Promise, resin;
|
||||
Promise = require('bluebird');
|
||||
resin = require('resin-sdk');
|
||||
return Promise["try"](function() {
|
||||
if (params.value == null) {
|
||||
params.value = process.env[params.key];
|
||||
if (params.value == null) {
|
||||
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 != null) {
|
||||
return resin.models.environmentVariables.create(options.application, params.key, params.value);
|
||||
} else if (options.device != null) {
|
||||
return 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.\n\nPass the `--device` boolean option if you want to rename a device environment variable.\n\nExamples:\n\n $ resin env rename 376 emacs\n $ resin env rename 376 emacs --device',
|
||||
permission: 'user',
|
||||
options: [commandOptions.booleanDevice],
|
||||
action: function(params, options, done) {
|
||||
var Promise, resin;
|
||||
Promise = require('bluebird');
|
||||
resin = require('resin-sdk');
|
||||
return Promise["try"](function() {
|
||||
if (options.device) {
|
||||
return resin.models.environmentVariables.device.update(params.id, params.value);
|
||||
} else {
|
||||
return resin.models.environmentVariables.update(params.id, params.value);
|
||||
}
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
}).call(this);
|
@ -1,136 +0,0 @@
|
||||
|
||||
/*
|
||||
Copyright 2016 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.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
var _, capitano, columnify, command, general, indent, messages, parse, print;
|
||||
|
||||
_ = require('lodash');
|
||||
|
||||
_.str = require('underscore.string');
|
||||
|
||||
capitano = require('capitano');
|
||||
|
||||
columnify = require('columnify');
|
||||
|
||||
messages = require('../utils/messages');
|
||||
|
||||
parse = function(object) {
|
||||
return _.object(_.map(object, function(item) {
|
||||
var signature;
|
||||
if (item.alias != null) {
|
||||
signature = item.toString();
|
||||
} else {
|
||||
signature = item.signature.toString();
|
||||
}
|
||||
return [signature, item.description];
|
||||
}));
|
||||
};
|
||||
|
||||
indent = function(text) {
|
||||
text = _.map(_.str.lines(text), function(line) {
|
||||
return ' ' + line;
|
||||
});
|
||||
return text.join('\n');
|
||||
};
|
||||
|
||||
print = function(data) {
|
||||
return console.log(indent(columnify(data, {
|
||||
showHeaders: false,
|
||||
minWidth: 35
|
||||
})));
|
||||
};
|
||||
|
||||
general = function(params, options, done) {
|
||||
var commands, groupedCommands;
|
||||
console.log('Usage: resin [COMMAND] [OPTIONS]\n');
|
||||
console.log(messages.gettingStarted + "\n");
|
||||
console.log(messages.reachingOut);
|
||||
console.log('\nPrimary commands:\n');
|
||||
commands = _.reject(capitano.state.commands, function(command) {
|
||||
return command.isWildcard();
|
||||
});
|
||||
groupedCommands = _.groupBy(commands, function(command) {
|
||||
if (command.plugin) {
|
||||
return 'plugins';
|
||||
} else if (command.primary) {
|
||||
return 'primary';
|
||||
}
|
||||
return 'secondary';
|
||||
});
|
||||
print(parse(groupedCommands.primary));
|
||||
if (options.verbose) {
|
||||
if (!_.isEmpty(groupedCommands.plugins)) {
|
||||
console.log('\nInstalled plugins:\n');
|
||||
print(parse(groupedCommands.plugins));
|
||||
}
|
||||
console.log('\nAdditional commands:\n');
|
||||
print(parse(groupedCommands.secondary));
|
||||
} else {
|
||||
console.log('\nRun `resin help --verbose` to list additional commands');
|
||||
}
|
||||
if (!_.isEmpty(capitano.state.globalOptions)) {
|
||||
console.log('\nGlobal Options:\n');
|
||||
print(parse(capitano.state.globalOptions));
|
||||
}
|
||||
return done();
|
||||
};
|
||||
|
||||
command = function(params, options, done) {
|
||||
return capitano.state.getMatchCommand(params.command, function(error, command) {
|
||||
if (error != null) {
|
||||
return done(error);
|
||||
}
|
||||
if ((command == null) || command.isWildcard()) {
|
||||
return done(new Error("Command not found: " + params.command));
|
||||
}
|
||||
console.log("Usage: " + command.signature);
|
||||
if (command.help != null) {
|
||||
console.log("\n" + command.help);
|
||||
} else if (command.description != null) {
|
||||
console.log("\n" + (_.str.humanize(command.description)));
|
||||
}
|
||||
if (!_.isEmpty(command.options)) {
|
||||
console.log('\nOptions:\n');
|
||||
print(parse(command.options));
|
||||
}
|
||||
return done();
|
||||
});
|
||||
};
|
||||
|
||||
exports.help = {
|
||||
signature: 'help [command...]',
|
||||
description: 'show help',
|
||||
help: 'Get detailed help for an specific command.\n\nExamples:\n\n $ resin help apps\n $ resin help os download',
|
||||
primary: true,
|
||||
options: [
|
||||
{
|
||||
signature: 'verbose',
|
||||
description: 'show additional commands',
|
||||
boolean: true,
|
||||
alias: 'v'
|
||||
}
|
||||
],
|
||||
action: function(params, options, done) {
|
||||
if (params.command != null) {
|
||||
return command(params, options, done);
|
||||
} else {
|
||||
return general(params, options, done);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}).call(this);
|
@ -1,97 +0,0 @@
|
||||
|
||||
/*
|
||||
Copyright 2016 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.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
var commandOptions;
|
||||
|
||||
commandOptions = require('./command-options');
|
||||
|
||||
exports.list = {
|
||||
signature: 'keys',
|
||||
description: 'list all ssh keys',
|
||||
help: 'Use this command to list all your SSH keys.\n\nExamples:\n\n $ resin keys',
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
var resin, visuals;
|
||||
resin = require('resin-sdk');
|
||||
visuals = require('resin-cli-visuals');
|
||||
return resin.models.key.getAll().then(function(keys) {
|
||||
return console.log(visuals.table.horizontal(keys, ['id', 'title']));
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
exports.info = {
|
||||
signature: 'key <id>',
|
||||
description: 'list a single ssh key',
|
||||
help: 'Use this command to show information about a single SSH key.\n\nExamples:\n\n $ resin key 17',
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
var resin, visuals;
|
||||
resin = require('resin-sdk');
|
||||
visuals = require('resin-cli-visuals');
|
||||
return resin.models.key.get(params.id).then(function(key) {
|
||||
console.log(visuals.table.vertical(key, ['id', 'title']));
|
||||
return console.log('\n' + key.public_key);
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
exports.remove = {
|
||||
signature: 'key rm <id>',
|
||||
description: 'remove a ssh key',
|
||||
help: 'Use this command to remove a SSH key from resin.io.\n\nNotice this command asks for confirmation interactively.\nYou can avoid this by passing the `--yes` boolean option.\n\nExamples:\n\n $ resin key rm 17\n $ resin key rm 17 --yes',
|
||||
options: [commandOptions.yes],
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
var patterns, resin;
|
||||
resin = require('resin-sdk');
|
||||
patterns = require('../utils/patterns');
|
||||
return patterns.confirm(options.yes, 'Are you sure you want to delete the key?').then(function() {
|
||||
return resin.models.key.remove(params.id);
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
exports.add = {
|
||||
signature: 'key add <name> [path]',
|
||||
description: 'add a SSH key to resin.io',
|
||||
help: 'Use this command to associate a new SSH key with your account.\n\nIf `path` is omitted, the command will attempt\nto read the SSH key from stdin.\n\nExamples:\n\n $ resin key add Main ~/.ssh/id_rsa.pub\n $ cat ~/.ssh/id_rsa.pub | resin key add Main',
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
var Promise, _, capitano, fs, resin;
|
||||
_ = require('lodash');
|
||||
Promise = require('bluebird');
|
||||
fs = Promise.promisifyAll(require('fs'));
|
||||
capitano = require('capitano');
|
||||
resin = require('resin-sdk');
|
||||
return Promise["try"](function() {
|
||||
if (params.path != null) {
|
||||
return fs.readFileAsync(params.path, {
|
||||
encoding: 'utf8'
|
||||
});
|
||||
}
|
||||
return Promise.fromNode(function(callback) {
|
||||
return capitano.utils.getStdin(function(data) {
|
||||
return callback(null, data);
|
||||
});
|
||||
});
|
||||
}).then(_.partial(resin.models.key.create, params.name)).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
}).call(this);
|
@ -1,58 +0,0 @@
|
||||
|
||||
/*
|
||||
Copyright 2016 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.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
module.exports = {
|
||||
signature: 'logs <uuid>',
|
||||
description: 'show device logs',
|
||||
help: 'Use this command to show logs for a specific device.\n\nBy default, the command prints all log messages and exit.\n\nTo continuously stream output, and see new logs in real time, use the `--tail` option.\n\nNote that for now you need to provide the whole UUID for this command to work correctly.\n\nThis is due to some technical limitations that we plan to address soon.\n\nExamples:\n\n $ resin logs 23c73a1\n $ resin logs 23c73a1',
|
||||
options: [
|
||||
{
|
||||
signature: 'tail',
|
||||
description: 'continuously stream output',
|
||||
boolean: true,
|
||||
alias: 't'
|
||||
}
|
||||
],
|
||||
permission: 'user',
|
||||
primary: true,
|
||||
action: function(params, options, done) {
|
||||
var _, moment, printLine, promise, resin;
|
||||
_ = require('lodash');
|
||||
resin = require('resin-sdk');
|
||||
moment = require('moment');
|
||||
printLine = function(line) {
|
||||
var timestamp;
|
||||
timestamp = moment(line.timestamp).format('DD.MM.YY HH:mm:ss (ZZ)');
|
||||
return console.log(timestamp + " " + line.message);
|
||||
};
|
||||
promise = resin.logs.history(params.uuid).each(printLine);
|
||||
if (!options.tail) {
|
||||
return promise["catch"](done)["finally"](function() {
|
||||
return process.exit(0);
|
||||
});
|
||||
}
|
||||
return promise.then(function() {
|
||||
return resin.logs.subscribe(params.uuid).then(function(logs) {
|
||||
logs.on('line', printLine);
|
||||
return logs.on('error', done);
|
||||
});
|
||||
})["catch"](done);
|
||||
}
|
||||
};
|
||||
|
||||
}).call(this);
|
@ -1,47 +0,0 @@
|
||||
|
||||
/*
|
||||
Copyright 2016 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.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
exports.set = {
|
||||
signature: 'note <|note>',
|
||||
description: 'set a device note',
|
||||
help: 'Use this command to set or update a device note.\n\nIf note command isn\'t passed, the tool attempts to read from `stdin`.\n\nTo view the notes, use $ resin device <uuid>.\n\nExamples:\n\n $ resin note "My useful note" --device 7cf02a6\n $ cat note.txt | resin note --device 7cf02a6',
|
||||
options: [
|
||||
{
|
||||
signature: 'device',
|
||||
parameter: 'device',
|
||||
description: 'device uuid',
|
||||
alias: ['d', 'dev'],
|
||||
required: 'You have to specify a device'
|
||||
}
|
||||
],
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
var Promise, _, resin;
|
||||
Promise = require('bluebird');
|
||||
_ = require('lodash');
|
||||
resin = require('resin-sdk');
|
||||
return Promise["try"](function() {
|
||||
if (_.isEmpty(params.note)) {
|
||||
throw new Error('Missing note content');
|
||||
}
|
||||
return resin.models.device.note(options.device, params.note);
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
}).call(this);
|
@ -1,191 +0,0 @@
|
||||
|
||||
/*
|
||||
Copyright 2016 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.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
var commandOptions, stepHandler;
|
||||
|
||||
commandOptions = require('./command-options');
|
||||
|
||||
exports.download = {
|
||||
signature: 'os download <type>',
|
||||
description: 'download an unconfigured os image',
|
||||
help: 'Use this command to download an unconfigured os image for a certain device type.\n\nExamples:\n\n $ resin os download parallella -o ../foo/bar/parallella.img',
|
||||
permission: 'user',
|
||||
options: [
|
||||
{
|
||||
signature: 'output',
|
||||
description: 'output path',
|
||||
parameter: 'output',
|
||||
alias: 'o',
|
||||
required: 'You have to specify an output location'
|
||||
}
|
||||
],
|
||||
action: function(params, options, done) {
|
||||
var fs, manager, rindle, unzip, visuals;
|
||||
unzip = require('unzip2');
|
||||
fs = require('fs');
|
||||
rindle = require('rindle');
|
||||
manager = require('resin-image-manager');
|
||||
visuals = require('resin-cli-visuals');
|
||||
console.info("Getting device operating system for " + params.type);
|
||||
return manager.get(params.type).then(function(stream) {
|
||||
var bar, output, spinner;
|
||||
bar = new visuals.Progress('Downloading Device OS');
|
||||
spinner = new visuals.Spinner('Downloading Device OS (size unknown)');
|
||||
stream.on('progress', function(state) {
|
||||
if (state != null) {
|
||||
return bar.update(state);
|
||||
} else {
|
||||
return spinner.start();
|
||||
}
|
||||
});
|
||||
stream.on('end', function() {
|
||||
return spinner.stop();
|
||||
});
|
||||
if (stream.mime === 'application/zip') {
|
||||
output = unzip.Extract({
|
||||
path: options.output
|
||||
});
|
||||
} else {
|
||||
output = fs.createWriteStream(options.output);
|
||||
}
|
||||
return rindle.wait(stream.pipe(output))["return"](options.output);
|
||||
}).tap(function(output) {
|
||||
return console.info('The image was downloaded successfully');
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
stepHandler = function(step) {
|
||||
var _, bar, helpers, rindle, visuals;
|
||||
_ = require('lodash');
|
||||
rindle = require('rindle');
|
||||
visuals = require('resin-cli-visuals');
|
||||
helpers = require('../utils/helpers');
|
||||
step.on('stdout', _.bind(process.stdout.write, process.stdout));
|
||||
step.on('stderr', _.bind(process.stderr.write, process.stderr));
|
||||
step.on('state', function(state) {
|
||||
if (state.operation.command === 'burn') {
|
||||
return;
|
||||
}
|
||||
return console.log(helpers.stateToString(state));
|
||||
});
|
||||
bar = new visuals.Progress('Writing Device OS');
|
||||
step.on('burn', _.bind(bar.update, bar));
|
||||
return rindle.wait(step);
|
||||
};
|
||||
|
||||
exports.configure = {
|
||||
signature: 'os configure <image> <uuid>',
|
||||
description: 'configure an os image',
|
||||
help: 'Use this command to configure a previously download operating system image with a device.\n\nExamples:\n\n $ resin os configure ../path/rpi.img 7cf02a6',
|
||||
permission: 'user',
|
||||
options: [
|
||||
{
|
||||
signature: 'advanced',
|
||||
description: 'show advanced commands',
|
||||
boolean: true,
|
||||
alias: 'v'
|
||||
}
|
||||
],
|
||||
action: function(params, options, done) {
|
||||
var _, form, helpers, init, resin;
|
||||
_ = require('lodash');
|
||||
resin = require('resin-sdk');
|
||||
form = require('resin-cli-form');
|
||||
init = require('resin-device-init');
|
||||
helpers = require('../utils/helpers');
|
||||
console.info('Configuring operating system image');
|
||||
return resin.models.device.get(params.uuid).get('device_type').then(resin.models.device.getManifestBySlug).get('options').then(function(questions) {
|
||||
var advancedGroup, override;
|
||||
if (!options.advanced) {
|
||||
advancedGroup = _.findWhere(questions, {
|
||||
name: 'advanced',
|
||||
isGroup: true
|
||||
});
|
||||
if (advancedGroup != null) {
|
||||
override = helpers.getGroupDefaults(advancedGroup);
|
||||
}
|
||||
}
|
||||
return form.run(questions, {
|
||||
override: override
|
||||
});
|
||||
}).then(function(answers) {
|
||||
return init.configure(params.image, params.uuid, answers).then(stepHandler);
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
exports.initialize = {
|
||||
signature: 'os initialize <image>',
|
||||
description: 'initialize an os image',
|
||||
help: 'Use this command to initialize a previously configured operating system image.\n\nExamples:\n\n $ resin os initialize ../path/rpi.img --type \'raspberry-pi\'',
|
||||
permission: 'user',
|
||||
options: [
|
||||
commandOptions.yes, {
|
||||
signature: 'type',
|
||||
description: 'device type',
|
||||
parameter: 'type',
|
||||
alias: 't',
|
||||
required: 'You have to specify a device type'
|
||||
}, {
|
||||
signature: 'drive',
|
||||
description: 'drive',
|
||||
parameter: 'drive',
|
||||
alias: 'd'
|
||||
}
|
||||
],
|
||||
root: true,
|
||||
action: function(params, options, done) {
|
||||
var Promise, form, init, patterns, resin, umount;
|
||||
Promise = require('bluebird');
|
||||
umount = Promise.promisifyAll(require('umount'));
|
||||
resin = require('resin-sdk');
|
||||
form = require('resin-cli-form');
|
||||
init = require('resin-device-init');
|
||||
patterns = require('../utils/patterns');
|
||||
console.info('Initializing device');
|
||||
return resin.models.device.getManifestBySlug(options.type).then(function(manifest) {
|
||||
var ref;
|
||||
return (ref = manifest.initialization) != null ? ref.options : void 0;
|
||||
}).then(function(questions) {
|
||||
return form.run(questions, {
|
||||
override: {
|
||||
drive: options.drive
|
||||
}
|
||||
});
|
||||
}).tap(function(answers) {
|
||||
var message;
|
||||
if (answers.drive == null) {
|
||||
return;
|
||||
}
|
||||
message = "This will erase " + answers.drive + ". Are you sure?";
|
||||
return patterns.confirm(options.yes, message)["return"](answers.drive).then(umount.umountAsync);
|
||||
}).tap(function(answers) {
|
||||
return init.initialize(params.image, options.type, answers).then(stepHandler);
|
||||
}).then(function(answers) {
|
||||
if (answers.drive == null) {
|
||||
return;
|
||||
}
|
||||
return umount.umountAsync(answers.drive).tap(function() {
|
||||
return console.info("You can safely remove " + answers.drive + " now");
|
||||
});
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
}).call(this);
|
@ -1,31 +0,0 @@
|
||||
|
||||
/*
|
||||
Copyright 2016 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.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
exports.list = {
|
||||
signature: 'settings',
|
||||
description: 'print current settings',
|
||||
help: 'Use this command to display detected settings\n\nExamples:\n\n $ resin settings',
|
||||
action: function(params, options, done) {
|
||||
var prettyjson, resin;
|
||||
resin = require('resin-sdk');
|
||||
prettyjson = require('prettyjson');
|
||||
return resin.settings.getAll().then(prettyjson.render).then(console.log).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
}).call(this);
|
@ -1,63 +0,0 @@
|
||||
|
||||
/*
|
||||
Copyright 2016 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.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
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.\n\nThe wizard will guide you through:\n\n - Create an application.\n - Initialise an SDCard with the resin.io operating system.\n - Associate an existing project directory with your resin.io application.\n - Push your project to your devices.\n\nExamples:\n\n $ resin quickstart\n $ resin quickstart MyApp',
|
||||
primary: true,
|
||||
action: function(params, options, done) {
|
||||
var Promise, capitano, patterns, resin;
|
||||
Promise = require('bluebird');
|
||||
capitano = Promise.promisifyAll(require('capitano'));
|
||||
resin = require('resin-sdk');
|
||||
patterns = require('../utils/patterns');
|
||||
return resin.auth.isLoggedIn().then(function(isLoggedIn) {
|
||||
if (isLoggedIn) {
|
||||
return;
|
||||
}
|
||||
console.info('Looks like you\'re not logged in yet!');
|
||||
console.info('Lets go through a quick wizard to get you started.\n');
|
||||
return capitano.runAsync('login');
|
||||
}).then(function() {
|
||||
if (params.name != null) {
|
||||
return;
|
||||
}
|
||||
return patterns.selectOrCreateApplication().tap(function(applicationName) {
|
||||
return resin.models.application.has(applicationName).then(function(hasApplication) {
|
||||
if (hasApplication) {
|
||||
return applicationName;
|
||||
}
|
||||
return capitano.runAsync("app create " + applicationName);
|
||||
});
|
||||
}).then(function(applicationName) {
|
||||
return params.name = applicationName;
|
||||
});
|
||||
}).then(function() {
|
||||
return capitano.runAsync("device init --application " + params.name);
|
||||
}).tap(patterns.awaitDevice).then(function(uuid) {
|
||||
return capitano.runAsync("device " + uuid);
|
||||
}).then(function() {
|
||||
return resin.models.application.get(params.name);
|
||||
}).then(function(application) {
|
||||
return console.log("Your device is ready to start pushing some code!\n\nCheck our official documentation for more information:\n\n http://docs.resin.io/#/pages/introduction/introduction.md\n\nClone an example or go to an existing application directory and run:\n\n $ git remote add resin " + application.git_repository + "\n $ git push resin master");
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
}).call(this);
|
144
build/app.js
144
build/app.js
@ -1,144 +0,0 @@
|
||||
|
||||
/*
|
||||
Copyright 2016 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.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
var Promise, _, actions, capitano, errors, events, plugins, resin, update;
|
||||
|
||||
_ = require('lodash');
|
||||
|
||||
Promise = require('bluebird');
|
||||
|
||||
capitano = Promise.promisifyAll(require('capitano'));
|
||||
|
||||
resin = require('resin-sdk');
|
||||
|
||||
actions = require('./actions');
|
||||
|
||||
errors = require('./errors');
|
||||
|
||||
events = require('./events');
|
||||
|
||||
plugins = require('./utils/plugins');
|
||||
|
||||
update = require('./utils/update');
|
||||
|
||||
capitano.permission('user', function(done) {
|
||||
return resin.auth.isLoggedIn().then(function(isLoggedIn) {
|
||||
if (!isLoggedIn) {
|
||||
throw new Error('You have to log in to continue\n\nRun the following command to go through the login wizard:\n\n $ resin login');
|
||||
}
|
||||
}).nodeify(done);
|
||||
});
|
||||
|
||||
capitano.command({
|
||||
signature: '*',
|
||||
action: function() {
|
||||
return capitano.execute({
|
||||
command: 'help'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
capitano.command(actions.info.version);
|
||||
|
||||
capitano.command(actions.help.help);
|
||||
|
||||
capitano.command(actions.wizard.wizard);
|
||||
|
||||
capitano.command(actions.auth.login);
|
||||
|
||||
capitano.command(actions.auth.logout);
|
||||
|
||||
capitano.command(actions.auth.signup);
|
||||
|
||||
capitano.command(actions.auth.whoami);
|
||||
|
||||
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);
|
||||
|
||||
capitano.command(actions.device.list);
|
||||
|
||||
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.register);
|
||||
|
||||
capitano.command(actions.device.move);
|
||||
|
||||
capitano.command(actions.device.info);
|
||||
|
||||
capitano.command(actions.notes.set);
|
||||
|
||||
capitano.command(actions.keys.list);
|
||||
|
||||
capitano.command(actions.keys.add);
|
||||
|
||||
capitano.command(actions.keys.info);
|
||||
|
||||
capitano.command(actions.keys.remove);
|
||||
|
||||
capitano.command(actions.env.list);
|
||||
|
||||
capitano.command(actions.env.add);
|
||||
|
||||
capitano.command(actions.env.rename);
|
||||
|
||||
capitano.command(actions.env.remove);
|
||||
|
||||
capitano.command(actions.os.download);
|
||||
|
||||
capitano.command(actions.os.configure);
|
||||
|
||||
capitano.command(actions.os.initialize);
|
||||
|
||||
capitano.command(actions.config.read);
|
||||
|
||||
capitano.command(actions.config.write);
|
||||
|
||||
capitano.command(actions.config.reconfigure);
|
||||
|
||||
capitano.command(actions.config.generate);
|
||||
|
||||
capitano.command(actions.settings.list);
|
||||
|
||||
capitano.command(actions.logs);
|
||||
|
||||
update.notify();
|
||||
|
||||
plugins.register(/^resin-plugin-(.+)$/).then(function() {
|
||||
var cli;
|
||||
cli = capitano.parse(process.argv);
|
||||
return events.trackCommand(cli).then(function() {
|
||||
return capitano.executeAsync(cli);
|
||||
});
|
||||
})["catch"](errors.handle);
|
||||
|
||||
}).call(this);
|
@ -1,40 +0,0 @@
|
||||
|
||||
/*
|
||||
Copyright 2016 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.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
var chalk, errors, patterns;
|
||||
|
||||
chalk = require('chalk');
|
||||
|
||||
errors = require('resin-cli-errors');
|
||||
|
||||
patterns = require('./utils/patterns');
|
||||
|
||||
exports.handle = function(error) {
|
||||
var message;
|
||||
message = errors.interpret(error);
|
||||
if (message == null) {
|
||||
return;
|
||||
}
|
||||
if (process.env.DEBUG) {
|
||||
message = error.stack;
|
||||
}
|
||||
patterns.printErrorMessage(message);
|
||||
return process.exit(error.exitCode || 1);
|
||||
};
|
||||
|
||||
}).call(this);
|
@ -1,37 +0,0 @@
|
||||
(function() {
|
||||
var Mixpanel, Promise, _, packageJSON, resin;
|
||||
|
||||
_ = require('lodash');
|
||||
|
||||
Mixpanel = require('mixpanel');
|
||||
|
||||
Promise = require('bluebird');
|
||||
|
||||
resin = require('resin-sdk');
|
||||
|
||||
packageJSON = require('../package.json');
|
||||
|
||||
exports.getLoggerInstance = _.memoize(function() {
|
||||
return resin.models.config.getMixpanelToken().then(Mixpanel.init);
|
||||
});
|
||||
|
||||
exports.trackCommand = function(capitanoCommand) {
|
||||
return Promise.props({
|
||||
resinUrl: resin.settings.get('resinUrl'),
|
||||
username: resin.auth.whoami(),
|
||||
mixpanel: exports.getLoggerInstance()
|
||||
}).then(function(data) {
|
||||
return data.mixpanel.track("[CLI] " + capitanoCommand.command, {
|
||||
distinct_id: data.username,
|
||||
argv: process.argv.join(' '),
|
||||
version: packageJSON.version,
|
||||
node: process.version,
|
||||
arch: process.arch,
|
||||
resinUrl: data.resinUrl,
|
||||
platform: process.platform,
|
||||
command: capitanoCommand
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
}).call(this);
|
@ -1,66 +0,0 @@
|
||||
|
||||
/*
|
||||
Copyright 2016 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.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
var Promise, _, capitano, chalk, os, president;
|
||||
|
||||
Promise = require('bluebird');
|
||||
|
||||
capitano = Promise.promisifyAll(require('capitano'));
|
||||
|
||||
_ = require('lodash');
|
||||
|
||||
_.str = require('underscore.string');
|
||||
|
||||
president = Promise.promisifyAll(require('president'));
|
||||
|
||||
os = require('os');
|
||||
|
||||
chalk = require('chalk');
|
||||
|
||||
exports.getGroupDefaults = function(group) {
|
||||
return _.chain(group).get('options').map(function(question) {
|
||||
return [question.name, question["default"]];
|
||||
}).object().value();
|
||||
};
|
||||
|
||||
exports.stateToString = function(state) {
|
||||
var percentage, result;
|
||||
percentage = _.str.lpad(state.percentage, 3, '0') + '%';
|
||||
result = (chalk.blue(percentage)) + " " + (chalk.cyan(state.operation.command));
|
||||
switch (state.operation.command) {
|
||||
case 'copy':
|
||||
return result + " " + state.operation.from.path + " -> " + state.operation.to.path;
|
||||
case 'replace':
|
||||
return result + " " + state.operation.file.path + ", " + state.operation.copy + " -> " + state.operation.replace;
|
||||
case 'run-script':
|
||||
return result + " " + state.operation.script;
|
||||
default:
|
||||
throw new Error("Unsupported operation: " + state.operation.type);
|
||||
}
|
||||
};
|
||||
|
||||
exports.sudo = function(command, message) {
|
||||
command = _.union(_.take(process.argv, 2), command);
|
||||
console.log(message);
|
||||
if (os.platform() !== 'win32') {
|
||||
console.log('Type your computer password to continue');
|
||||
}
|
||||
return president.executeAsync(command);
|
||||
};
|
||||
|
||||
}).call(this);
|
@ -1,10 +0,0 @@
|
||||
(function() {
|
||||
exports.gettingStarted = 'Run the following command to get a device started with Resin.io\n\n $ resin quickstart';
|
||||
|
||||
exports.reachingOut = 'If you need help, or just want to say hi, don\'t hesitate in reaching out at:\n\n GitHub: https://github.com/resin-io/resin-cli/issues/new\n Gitter: https://gitter.im/resin-io/chat';
|
||||
|
||||
exports.getHelp = 'If you need help, don\'t hesitate in contacting us at:\n\n GitHub: https://github.com/resin-io/resin-cli/issues/new\n Gitter: https://gitter.im/resin-io/chat';
|
||||
|
||||
exports.resinAsciiArt = '______ _ _\n| ___ \\ (_) (_)\n| |_/ /___ ___ _ _ __ _ ___\n| // _ \\/ __| | \'_ \\ | |/ _ \\\n| |\\ \\ __/\\__ \\ | | | |_| | (_) |\n\\_| \\_\\___||___/_|_| |_(_)_|\\___/';
|
||||
|
||||
}).call(this);
|
@ -1,196 +0,0 @@
|
||||
|
||||
/*
|
||||
Copyright 2016 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.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
var Promise, _, chalk, form, messages, resin, validation, visuals;
|
||||
|
||||
_ = require('lodash');
|
||||
|
||||
Promise = require('bluebird');
|
||||
|
||||
form = require('resin-cli-form');
|
||||
|
||||
visuals = require('resin-cli-visuals');
|
||||
|
||||
resin = require('resin-sdk');
|
||||
|
||||
chalk = require('chalk');
|
||||
|
||||
validation = require('./validation');
|
||||
|
||||
messages = require('./messages');
|
||||
|
||||
exports.authenticate = function(options) {
|
||||
return form.run([
|
||||
{
|
||||
message: 'Email:',
|
||||
name: 'email',
|
||||
type: 'input',
|
||||
validate: validation.validateEmail
|
||||
}, {
|
||||
message: 'Password:',
|
||||
name: 'password',
|
||||
type: 'password'
|
||||
}
|
||||
], {
|
||||
override: options
|
||||
}).then(resin.auth.login).then(resin.auth.twoFactor.isPassed).then(function(isTwoFactorAuthPassed) {
|
||||
if (isTwoFactorAuthPassed) {
|
||||
return;
|
||||
}
|
||||
return form.ask({
|
||||
message: 'Two factor auth challenge:',
|
||||
name: 'code',
|
||||
type: 'input'
|
||||
}).then(resin.auth.twoFactor.challenge)["catch"](function() {
|
||||
return resin.auth.logout().then(function() {
|
||||
throw new Error('Invalid two factor authentication code');
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
exports.askLoginType = function() {
|
||||
return form.ask({
|
||||
message: 'How would you like to login?',
|
||||
name: 'loginType',
|
||||
type: 'list',
|
||||
choices: [
|
||||
{
|
||||
name: 'Web authorization (recommended)',
|
||||
value: 'web'
|
||||
}, {
|
||||
name: 'Credentials',
|
||||
value: 'credentials'
|
||||
}, {
|
||||
name: 'Authentication token',
|
||||
value: 'token'
|
||||
}, {
|
||||
name: 'I don\'t have a Resin account!',
|
||||
value: 'register'
|
||||
}
|
||||
]
|
||||
});
|
||||
};
|
||||
|
||||
exports.selectDeviceType = function() {
|
||||
return resin.models.device.getSupportedDeviceTypes().then(function(deviceTypes) {
|
||||
return form.ask({
|
||||
message: 'Device Type',
|
||||
type: 'list',
|
||||
choices: deviceTypes
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
exports.confirm = function(yesOption, message) {
|
||||
return Promise["try"](function() {
|
||||
if (yesOption) {
|
||||
return true;
|
||||
}
|
||||
return form.ask({
|
||||
message: message,
|
||||
type: 'confirm',
|
||||
"default": false
|
||||
});
|
||||
}).then(function(confirmed) {
|
||||
if (!confirmed) {
|
||||
throw new Error('Aborted');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
exports.selectApplication = function(filter) {
|
||||
return resin.models.application.hasAny().then(function(hasAnyApplications) {
|
||||
if (!hasAnyApplications) {
|
||||
throw new Error('You don\'t have any applications');
|
||||
}
|
||||
return resin.models.application.getAll();
|
||||
}).filter(filter || _.constant(true)).then(function(applications) {
|
||||
return form.ask({
|
||||
message: 'Select an application',
|
||||
type: 'list',
|
||||
choices: _.map(applications, function(application) {
|
||||
return {
|
||||
name: application.app_name + " (" + application.device_type + ")",
|
||||
value: application.app_name
|
||||
};
|
||||
})
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
exports.selectOrCreateApplication = function() {
|
||||
return resin.models.application.hasAny().then(function(hasAnyApplications) {
|
||||
if (!hasAnyApplications) {
|
||||
return;
|
||||
}
|
||||
return resin.models.application.getAll().then(function(applications) {
|
||||
applications = _.map(applications, function(application) {
|
||||
return {
|
||||
name: application.app_name + " (" + application.device_type + ")",
|
||||
value: application.app_name
|
||||
};
|
||||
});
|
||||
applications.unshift({
|
||||
name: 'Create a new application',
|
||||
value: null
|
||||
});
|
||||
return form.ask({
|
||||
message: 'Select an application',
|
||||
type: 'list',
|
||||
choices: applications
|
||||
});
|
||||
});
|
||||
}).then(function(application) {
|
||||
if (application != null) {
|
||||
return application;
|
||||
}
|
||||
return form.ask({
|
||||
message: 'Choose a Name for your new application',
|
||||
type: 'input',
|
||||
validate: validation.validateApplicationName
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
exports.awaitDevice = function(uuid) {
|
||||
return resin.models.device.getName(uuid).then(function(deviceName) {
|
||||
var poll, spinner;
|
||||
spinner = new visuals.Spinner("Waiting for " + deviceName + " to come online");
|
||||
poll = function() {
|
||||
return resin.models.device.isOnline(uuid).then(function(isOnline) {
|
||||
if (isOnline) {
|
||||
spinner.stop();
|
||||
console.info("The device **" + deviceName + "** is online!");
|
||||
} else {
|
||||
spinner.start();
|
||||
return Promise.delay(3000).then(poll);
|
||||
}
|
||||
});
|
||||
};
|
||||
console.info("Waiting for " + deviceName + " to connect to resin...");
|
||||
return poll()["return"](uuid);
|
||||
});
|
||||
};
|
||||
|
||||
exports.printErrorMessage = function(message) {
|
||||
console.error(chalk.red(message));
|
||||
return console.error(chalk.red("\n" + messages.getHelp + "\n"));
|
||||
};
|
||||
|
||||
}).call(this);
|
@ -1,43 +0,0 @@
|
||||
|
||||
/*
|
||||
Copyright 2016 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.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
var _, capitano, nplugm, patterns;
|
||||
|
||||
nplugm = require('nplugm');
|
||||
|
||||
_ = require('lodash');
|
||||
|
||||
capitano = require('capitano');
|
||||
|
||||
patterns = require('./patterns');
|
||||
|
||||
exports.register = function(regex) {
|
||||
return nplugm.list(regex).map(function(plugin) {
|
||||
var command;
|
||||
command = require(plugin);
|
||||
command.plugin = true;
|
||||
if (!_.isArray(command)) {
|
||||
return capitano.command(command);
|
||||
}
|
||||
return _.each(command, capitano.command);
|
||||
})["catch"](function(error) {
|
||||
return patterns.printErrorMessage(error.message);
|
||||
});
|
||||
};
|
||||
|
||||
}).call(this);
|
@ -1,49 +0,0 @@
|
||||
|
||||
/*
|
||||
Copyright 2016 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.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
var isRoot, notifier, packageJSON, updateNotifier;
|
||||
|
||||
updateNotifier = require('update-notifier');
|
||||
|
||||
isRoot = require('is-root');
|
||||
|
||||
packageJSON = require('../../package.json');
|
||||
|
||||
if (!isRoot()) {
|
||||
notifier = updateNotifier({
|
||||
pkg: packageJSON
|
||||
});
|
||||
}
|
||||
|
||||
exports.hasAvailableUpdate = function() {
|
||||
return notifier != null;
|
||||
};
|
||||
|
||||
exports.notify = function() {
|
||||
if (!exports.hasAvailableUpdate()) {
|
||||
return;
|
||||
}
|
||||
notifier.notify({
|
||||
defer: false
|
||||
});
|
||||
if (notifier.update != null) {
|
||||
return console.log('Notice that you might need administrator privileges depending on your setup\n');
|
||||
}
|
||||
};
|
||||
|
||||
}).call(this);
|
@ -1,44 +0,0 @@
|
||||
|
||||
/*
|
||||
Copyright 2016 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.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
var validEmail;
|
||||
|
||||
validEmail = require('valid-email');
|
||||
|
||||
exports.validateEmail = function(input) {
|
||||
if (!validEmail(input)) {
|
||||
return 'Email is not valid';
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
exports.validatePassword = function(input) {
|
||||
if (input.length < 8) {
|
||||
return 'Password should be 8 characters long';
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
exports.validateApplicationName = function(input) {
|
||||
if (input.length < 4) {
|
||||
return 'The application name should be at least 4 characters';
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
}).call(this);
|
115
capitanodoc.coffee
Normal file
115
capitanodoc.coffee
Normal file
@ -0,0 +1,115 @@
|
||||
# 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' ]
|
||||
},
|
||||
]
|
@ -1,84 +0,0 @@
|
||||
{
|
||||
"title": "Resin CLI Documentation",
|
||||
"introduction": "This tool allows you to interact with the resin.io api from the comfort of your command line.\n\nTo get started download the CLI from npm.\n\n\t$ npm install resin-cli -g\n\nThen authenticate yourself:\n\n\t$ resin login\n\nNow you have access to all the commands referenced below.",
|
||||
"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": "Notes",
|
||||
"files": [
|
||||
"lib/actions/notes.coffee"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "OS",
|
||||
"files": [
|
||||
"lib/actions/os.coffee"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Config",
|
||||
"files": [
|
||||
"lib/actions/config.coffee"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Settings",
|
||||
"files": [
|
||||
"lib/actions/settings.coffee"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Wizard",
|
||||
"files": [
|
||||
"lib/actions/wizard.coffee"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
112
doc/automated-init.md
Normal file
112
doc/automated-init.md
Normal file
@ -0,0 +1,112 @@
|
||||
# Provisioning Resin.io devices in automated (non-interactive) mode
|
||||
|
||||
This document describes how to run the `device init` command in non-interactive mode.
|
||||
|
||||
It requires collecting some preliminary information _once_.
|
||||
|
||||
The final command to provision the device looks like this:
|
||||
|
||||
```bash
|
||||
resin device init --app APP_ID --os-version OS_VERSION --drive DRIVE --config CONFIG_FILE --yes
|
||||
|
||||
```
|
||||
|
||||
You can run this command as many times as you need, putting the new medium (SD card / USB stick) each time.
|
||||
|
||||
But before you can run it you need to collect the parameters and build the configuration file. Keep reading to figure out how to do it.
|
||||
|
||||
|
||||
## Collect all the required parameters.
|
||||
|
||||
1. `DEVICE_TYPE`. Run
|
||||
```bash
|
||||
resin 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. `OS_VERSION`. Run
|
||||
```bash
|
||||
resin os versions DEVICE_TYPE
|
||||
```
|
||||
and pick the version that you need, like _v2.0.6+rev1.prod_.
|
||||
_Note_ that even though we support _semver ranges_ it's recommended to use the exact version when doing the automated provisioning as it
|
||||
guarantees full compatibility between the steps.
|
||||
|
||||
1. `DRIVE`. Plug in your target medium (SD card or the USB stick, depending on your device type) and run
|
||||
```bash
|
||||
resin 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,
|
||||
but still please check very carefully that you've picked the correct drive as it will be erased during the provisioning process.
|
||||
|
||||
Now we have all the parameters -- time to build the config file.
|
||||
|
||||
## Build the config file
|
||||
|
||||
Interactive device provisioning process often includes collecting some extra device configuration, like the networking mode and wifi credentials.
|
||||
|
||||
To skip this interactive step we need to buid this configuration once and save it to the JSON file for later reuse.
|
||||
|
||||
Let's say we will place it into the `CONFIG_FILE` path, like _./resin-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_.
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
1. Now we're ready to build the config:
|
||||
|
||||
```bash
|
||||
resin os build-config OS_IMAGE_PATH DEVICE_TYPE --output CONFIG_FILE
|
||||
```
|
||||
|
||||
This will run you through the interactive configuration wizard and in the end save the generated config as `CONFIG_FILE`. You can then verify it's not empty:
|
||||
|
||||
```bash
|
||||
cat CONFIG_FILE
|
||||
```
|
||||
|
||||
## Done
|
||||
|
||||
Now you're ready to run the command in the beginning of this guide.
|
||||
|
||||
Please note again that all of these steps only need to be done once (unless you need to change something), and once all the parameters are collected the main init command can be run unchanged.
|
||||
|
||||
But there are still some nuances to cover, please read below.
|
||||
|
||||
## Nuances
|
||||
|
||||
### `sudo` password on *nix systems
|
||||
|
||||
In order to write the image to the raw device we need the root permissions, this is unavoidable.
|
||||
|
||||
To improve the security we only run the minimal subcommand with `sudo`.
|
||||
|
||||
This means that with the default setup you're interrupted closer to the end of the device init process to enter your sudo password for this subcommand to work.
|
||||
|
||||
There are several ways to eliminate it and make the process fully non-interactive.
|
||||
|
||||
#### Option 1: make passwordless sudo.
|
||||
|
||||
Obviously you shouldn't do that if the machine you're working on has access to any sensitive resources or information.
|
||||
|
||||
But if you're using a machine dedicated to resin 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.
|
||||
|
||||
### Extra initialization config
|
||||
|
||||
As of June 2017 all the supported devices should not require any other interactive configuration.
|
||||
|
||||
But by the design of our system it is _possible_ (though it doesn't look very likely it's going to happen any time soon) that some extra initialization options may be requested for the specific device types.
|
||||
|
||||
If that is the case please raise the issue in the resin CLI repository and the maintainers will add the necessary options to build the similar JSON config for this step.
|
879
doc/cli.markdown
879
doc/cli.markdown
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
_ = require('lodash')
|
||||
path = require('path')
|
||||
capitanodoc = require('../../capitanodoc.json')
|
||||
capitanodoc = require('../../capitanodoc')
|
||||
markdown = require('./markdown')
|
||||
|
||||
result = {}
|
||||
|
@ -2,6 +2,8 @@ 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')
|
||||
|
||||
@ -10,13 +12,20 @@ OPTIONS =
|
||||
coffeelint: path.join(__dirname, 'coffeelint.json')
|
||||
files:
|
||||
coffee: [ 'lib/**/*.coffee', 'gulpfile.coffee' ]
|
||||
app: [ 'lib/**/*.coffee', '!lib/**/*.spec.coffee' ]
|
||||
app: 'lib/**/*.coffee'
|
||||
tests: 'tests/**/*.spec.coffee'
|
||||
pages: 'lib/auth/pages/*.ejs'
|
||||
directories:
|
||||
build: 'build/'
|
||||
|
||||
gulp.task 'pages', ->
|
||||
gulp.src(OPTIONS.files.pages)
|
||||
.pipe(inlinesource())
|
||||
.pipe(gulp.dest('build/auth/pages'))
|
||||
|
||||
gulp.task 'coffee', [ 'lint' ], ->
|
||||
gulp.src(OPTIONS.files.app)
|
||||
.pipe(coffee())
|
||||
.pipe(coffee(bare: true, header: true))
|
||||
.pipe(gulp.dest(OPTIONS.directories.build))
|
||||
|
||||
gulp.task 'lint', ->
|
||||
@ -26,9 +35,16 @@ gulp.task 'lint', ->
|
||||
}))
|
||||
.pipe(coffeelint.reporter())
|
||||
|
||||
gulp.task 'test', ->
|
||||
gulp.src(OPTIONS.files.tests, read: false)
|
||||
.pipe(mocha({
|
||||
reporter: 'min'
|
||||
}))
|
||||
|
||||
gulp.task 'build', [
|
||||
'coffee'
|
||||
'coffee',
|
||||
'pages'
|
||||
]
|
||||
|
||||
gulp.task 'watch', [ 'lint', 'coffee' ], ->
|
||||
gulp.watch([ OPTIONS.files.coffee ], [ 'coffee' ])
|
||||
gulp.task 'watch', [ 'build' ], ->
|
||||
gulp.watch([ OPTIONS.files.coffee ], [ 'build' ])
|
||||
|
@ -1,5 +1,5 @@
|
||||
###
|
||||
Copyright 2016 Resin.io
|
||||
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.
|
||||
@ -22,7 +22,7 @@ exports.create =
|
||||
help: '''
|
||||
Use this command to create a new resin.io application.
|
||||
|
||||
You can specify the application type with the `--type` option.
|
||||
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
|
||||
@ -38,14 +38,13 @@ exports.create =
|
||||
{
|
||||
signature: 'type'
|
||||
parameter: 'type'
|
||||
description: 'application type'
|
||||
description: 'application device type (Check available types with `resin devices supported`)'
|
||||
alias: 't'
|
||||
}
|
||||
]
|
||||
permission: 'user'
|
||||
primary: true
|
||||
action: (params, options, done) ->
|
||||
resin = require('resin-sdk')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
patterns = require('../utils/patterns')
|
||||
|
||||
# Validate the the application name is available
|
||||
@ -79,7 +78,7 @@ exports.list =
|
||||
permission: 'user'
|
||||
primary: true
|
||||
action: (params, options, done) ->
|
||||
resin = require('resin-sdk')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
visuals = require('resin-cli-visuals')
|
||||
|
||||
resin.models.application.getAll().then (applications) ->
|
||||
@ -105,7 +104,7 @@ exports.info =
|
||||
permission: 'user'
|
||||
primary: true
|
||||
action: (params, options, done) ->
|
||||
resin = require('resin-sdk')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
visuals = require('resin-cli-visuals')
|
||||
|
||||
resin.models.application.get(params.name).then (application) ->
|
||||
@ -130,7 +129,7 @@ exports.restart =
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
resin = require('resin-sdk')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
resin.models.application.restart(params.name).nodeify(done)
|
||||
|
||||
exports.remove =
|
||||
@ -150,7 +149,7 @@ exports.remove =
|
||||
options: [ commandOptions.yes ]
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
resin = require('resin-sdk')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
patterns = require('../utils/patterns')
|
||||
|
||||
patterns.confirm(options.yes, 'Are you sure you want to delete the application?').then ->
|
||||
|
@ -1,5 +1,5 @@
|
||||
###
|
||||
Copyright 2016 Resin.io
|
||||
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.
|
||||
@ -73,9 +73,8 @@ exports.login =
|
||||
action: (params, options, done) ->
|
||||
_ = require('lodash')
|
||||
Promise = require('bluebird')
|
||||
capitano = Promise.promisifyAll(require('capitano'))
|
||||
resin = require('resin-sdk')
|
||||
auth = require('resin-cli-auth')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
auth = require('../auth')
|
||||
form = require('resin-cli-form')
|
||||
patterns = require('../utils/patterns')
|
||||
messages = require('../utils/messages')
|
||||
@ -98,7 +97,8 @@ exports.login =
|
||||
return patterns.askLoginType().then (loginType) ->
|
||||
|
||||
if loginType is 'register'
|
||||
return capitano.runAsync('signup')
|
||||
capitanoRunAsync = Promise.promisify(require('capitano').run)
|
||||
return capitanoRunAsync('signup')
|
||||
|
||||
options[loginType] = true
|
||||
return login(options)
|
||||
@ -112,11 +112,7 @@ exports.login =
|
||||
console.info("Successfully logged in as: #{username}")
|
||||
console.info """
|
||||
|
||||
Now what?
|
||||
|
||||
#{messages.gettingStarted}
|
||||
|
||||
Find out about more super powers by running:
|
||||
Find out about the available commands by running:
|
||||
|
||||
$ resin help
|
||||
|
||||
@ -136,7 +132,7 @@ exports.logout =
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
resin = require('resin-sdk')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
resin.auth.logout().nodeify(done)
|
||||
|
||||
exports.signup =
|
||||
@ -150,15 +146,14 @@ exports.signup =
|
||||
Examples:
|
||||
|
||||
$ resin signup
|
||||
Email: me@mycompany.com
|
||||
Username: johndoe
|
||||
Email: johndoe@acme.com
|
||||
Password: ***********
|
||||
|
||||
$ resin whoami
|
||||
johndoe
|
||||
'''
|
||||
action: (params, options, done) ->
|
||||
resin = require('resin-sdk')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
form = require('resin-cli-form')
|
||||
validation = require('../utils/validation')
|
||||
|
||||
@ -170,10 +165,6 @@ exports.signup =
|
||||
name: 'email'
|
||||
type: 'input'
|
||||
validate: validation.validateEmail
|
||||
,
|
||||
message: 'Username:'
|
||||
name: 'username'
|
||||
type: 'input'
|
||||
,
|
||||
message: 'Password:'
|
||||
name: 'password'
|
||||
@ -198,7 +189,7 @@ exports.whoami =
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
Promise = require('bluebird')
|
||||
resin = require('resin-sdk')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
visuals = require('resin-cli-visuals')
|
||||
|
||||
Promise.props
|
||||
|
64
lib/actions/build.coffee
Normal file
64
lib/actions/build.coffee
Normal file
@ -0,0 +1,64 @@
|
||||
# Imported here because it's needed for the setup
|
||||
# of this action
|
||||
Promise = require('bluebird')
|
||||
dockerUtils = require('../utils/docker')
|
||||
|
||||
getBundleInfo = Promise.method (options) ->
|
||||
helpers = require('../utils/helpers')
|
||||
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
You must provide either an application or a device-type/architecture
|
||||
pair to use the resin Dockerfile pre-processor
|
||||
(e.g. Dockerfile.template -> Dockerfile).
|
||||
|
||||
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 [
|
||||
{
|
||||
signature: 'arch'
|
||||
parameter: 'arch'
|
||||
description: 'The architecture to build for'
|
||||
alias: 'A'
|
||||
},
|
||||
{
|
||||
signature: 'deviceType'
|
||||
parameter: 'deviceType'
|
||||
description: 'The type of device this build is for'
|
||||
alias: 'd'
|
||||
},
|
||||
{
|
||||
signature: 'application'
|
||||
parameter: 'application'
|
||||
description: 'The target resin.io application this build is for'
|
||||
alias: 'a'
|
||||
},
|
||||
]
|
||||
action: (params, options, done) ->
|
||||
Logger = require('../utils/logger')
|
||||
dockerUtils.runBuild(params, options, getBundleInfo, new Logger())
|
||||
.asCallback(done)
|
||||
|
@ -1,5 +1,5 @@
|
||||
###
|
||||
Copyright 2016 Resin.io
|
||||
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.
|
||||
@ -38,12 +38,29 @@ exports.optionalDevice =
|
||||
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'
|
||||
@ -61,3 +78,23 @@ exports.wifiKey =
|
||||
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'
|
||||
|
@ -1,5 +1,5 @@
|
||||
###
|
||||
Copyright 2016 Resin.io
|
||||
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.
|
||||
@ -14,11 +14,13 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
###
|
||||
|
||||
commandOptions = require('./command-options')
|
||||
|
||||
exports.read =
|
||||
signature: 'config read'
|
||||
description: 'read a device configuration'
|
||||
help: '''
|
||||
Use this command to read the config.json file from a provisioned device
|
||||
Use this command to read the config.json file from the mounted filesystem (e.g. SD card) of a provisioned device"
|
||||
|
||||
Examples:
|
||||
|
||||
@ -28,7 +30,7 @@ exports.read =
|
||||
options: [
|
||||
{
|
||||
signature: 'type'
|
||||
description: 'device type'
|
||||
description: 'device type (Check available types with `resin devices supported`)'
|
||||
parameter: 'type'
|
||||
alias: 't'
|
||||
required: 'You have to specify a device type'
|
||||
@ -46,12 +48,12 @@ exports.read =
|
||||
Promise = require('bluebird')
|
||||
config = require('resin-config-json')
|
||||
visuals = require('resin-cli-visuals')
|
||||
umount = Promise.promisifyAll(require('umount'))
|
||||
umountAsync = Promise.promisify(require('umount').umount)
|
||||
prettyjson = require('prettyjson')
|
||||
|
||||
Promise.try ->
|
||||
return options.drive or visuals.drive('Select the device drive')
|
||||
.tap(umount.umountAsync)
|
||||
.tap(umountAsync)
|
||||
.then (drive) ->
|
||||
return config.read(drive, options.type)
|
||||
.tap (configJSON) ->
|
||||
@ -62,7 +64,7 @@ exports.write =
|
||||
signature: 'config write <key> <value>'
|
||||
description: 'write a device configuration'
|
||||
help: '''
|
||||
Use this command to write the config.json file of a provisioned device
|
||||
Use this command to write the config.json file to the mounted filesystem (e.g. SD card) of a provisioned device
|
||||
|
||||
Examples:
|
||||
|
||||
@ -73,7 +75,7 @@ exports.write =
|
||||
options: [
|
||||
{
|
||||
signature: 'type'
|
||||
description: 'device type'
|
||||
description: 'device type (Check available types with `resin devices supported`)'
|
||||
parameter: 'type'
|
||||
alias: 't'
|
||||
required: 'You have to specify a device type'
|
||||
@ -92,24 +94,69 @@ exports.write =
|
||||
_ = require('lodash')
|
||||
config = require('resin-config-json')
|
||||
visuals = require('resin-cli-visuals')
|
||||
umount = Promise.promisifyAll(require('umount'))
|
||||
umountAsync = Promise.promisify(require('umount').umount)
|
||||
|
||||
Promise.try ->
|
||||
return options.drive or visuals.drive('Select the device drive')
|
||||
.tap(umount.umountAsync)
|
||||
.tap(umountAsync)
|
||||
.then (drive) ->
|
||||
config.read(drive, options.type).then (configJSON) ->
|
||||
console.info("Setting #{params.key} to #{params.value}")
|
||||
_.set(configJSON, params.key, params.value)
|
||||
return configJSON
|
||||
.tap ->
|
||||
return umount.umountAsync(drive)
|
||||
return umountAsync(drive)
|
||||
.then (configJSON) ->
|
||||
return config.write(drive, options.type, configJSON)
|
||||
.tap ->
|
||||
console.info('Done')
|
||||
.nodeify(done)
|
||||
|
||||
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"
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin config inject my/config.json --type raspberry-pi
|
||||
$ resin config inject my/config.json --type raspberry-pi --drive /dev/disk2
|
||||
'''
|
||||
options: [
|
||||
{
|
||||
signature: 'type'
|
||||
description: 'device type (Check available types with `resin devices supported`)'
|
||||
parameter: 'type'
|
||||
alias: 't'
|
||||
required: 'You have to specify a device type'
|
||||
}
|
||||
{
|
||||
signature: 'drive'
|
||||
description: 'drive'
|
||||
parameter: 'drive'
|
||||
alias: 'd'
|
||||
}
|
||||
]
|
||||
permission: 'user'
|
||||
root: true
|
||||
action: (params, options, done) ->
|
||||
Promise = require('bluebird')
|
||||
config = require('resin-config-json')
|
||||
visuals = require('resin-cli-visuals')
|
||||
umountAsync = Promise.promisify(require('umount').umount)
|
||||
readFileAsync = Promise.promisify(require('fs').readFile)
|
||||
|
||||
Promise.try ->
|
||||
return options.drive or visuals.drive('Select the device drive')
|
||||
.tap(umountAsync)
|
||||
.then (drive) ->
|
||||
readFileAsync(params.file, 'utf8').then(JSON.parse).then (configJSON) ->
|
||||
return config.write(drive, options.type, configJSON)
|
||||
.tap ->
|
||||
console.info('Done')
|
||||
.nodeify(done)
|
||||
|
||||
exports.reconfigure =
|
||||
signature: 'config reconfigure'
|
||||
description: 'reconfigure a provisioned device'
|
||||
@ -125,7 +172,7 @@ exports.reconfigure =
|
||||
options: [
|
||||
{
|
||||
signature: 'type'
|
||||
description: 'device type'
|
||||
description: 'device type (Check available types with `resin devices supported`)'
|
||||
parameter: 'type'
|
||||
alias: 't'
|
||||
required: 'You have to specify a device type'
|
||||
@ -149,62 +196,116 @@ exports.reconfigure =
|
||||
Promise = require('bluebird')
|
||||
config = require('resin-config-json')
|
||||
visuals = require('resin-cli-visuals')
|
||||
capitano = Promise.promisifyAll(require('capitano'))
|
||||
umount = Promise.promisifyAll(require('umount'))
|
||||
capitanoRunAsync = Promise.promisify(require('capitano').run)
|
||||
umountAsync = Promise.promisify(require('umount').umount)
|
||||
|
||||
Promise.try ->
|
||||
return options.drive or visuals.drive('Select the device drive')
|
||||
.tap(umount.umountAsync)
|
||||
.tap(umountAsync)
|
||||
.then (drive) ->
|
||||
config.read(drive, options.type).get('uuid')
|
||||
.tap ->
|
||||
umount.umountAsync(drive)
|
||||
umountAsync(drive)
|
||||
.then (uuid) ->
|
||||
configureCommand = "os configure #{drive} #{uuid}"
|
||||
if options.advanced
|
||||
configureCommand += ' --advanced'
|
||||
return capitano.runAsync(configureCommand)
|
||||
return capitanoRunAsync(configureCommand)
|
||||
.then ->
|
||||
console.info('Done')
|
||||
.nodeify(done)
|
||||
|
||||
exports.generate =
|
||||
signature: 'config generate <uuid>'
|
||||
signature: 'config generate'
|
||||
description: 'generate a config.json file'
|
||||
help: '''
|
||||
Use this command to generate a config.json for a device
|
||||
Use this command to generate a config.json for a device or application.
|
||||
|
||||
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.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin config generate 7cf02a6
|
||||
$ resin config generate 7cf02a6 --output config.json
|
||||
$ 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
|
||||
'''
|
||||
options: [
|
||||
commandOptions.optionalApplication
|
||||
commandOptions.optionalDevice
|
||||
commandOptions.optionalDeviceApiKey
|
||||
{
|
||||
signature: 'output'
|
||||
description: 'output'
|
||||
parameter: 'output'
|
||||
alias: 'o'
|
||||
}
|
||||
# Options for non-interactive configuration
|
||||
{
|
||||
signature: 'network'
|
||||
description: 'the network type to use: ethernet or wifi'
|
||||
parameter: 'network'
|
||||
}
|
||||
{
|
||||
signature: 'wifiSsid'
|
||||
description: 'the wifi ssid to use (used only if --network is set to wifi)'
|
||||
parameter: 'wifiSsid'
|
||||
}
|
||||
{
|
||||
signature: 'wifiKey'
|
||||
description: 'the wifi key to use (used only if --network is set to wifi)'
|
||||
parameter: 'wifiKey'
|
||||
}
|
||||
{
|
||||
signature: 'appUpdatePollInterval'
|
||||
description: 'how frequently (in minutes) to poll for application updates'
|
||||
parameter: 'appUpdatePollInterval'
|
||||
}
|
||||
]
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
Promise = require('bluebird')
|
||||
fs = Promise.promisifyAll(require('fs'))
|
||||
resin = require('resin-sdk')
|
||||
writeFileAsync = Promise.promisify(require('fs').writeFile)
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
_ = require('lodash')
|
||||
form = require('resin-cli-form')
|
||||
deviceConfig = require('resin-device-config')
|
||||
prettyjson = require('prettyjson')
|
||||
{ generateDeviceConfig, generateApplicationConfig } = require('../utils/config')
|
||||
|
||||
resin.models.device.get(params.uuid).then (device) ->
|
||||
resin.models.device.getManifestBySlug(device.device_type)
|
||||
.get('options')
|
||||
.then(form.run)
|
||||
.then(_.partial(deviceConfig.get, device.uuid))
|
||||
if not options.device? and not options.application?
|
||||
throw new Error '''
|
||||
You have to pass either a device or an application.
|
||||
|
||||
See the help page for examples:
|
||||
|
||||
$ resin help config generate
|
||||
'''
|
||||
|
||||
Promise.try ->
|
||||
if options.device?
|
||||
return resin.models.device.get(options.device)
|
||||
return resin.models.application.get(options.application)
|
||||
.then (resource) ->
|
||||
resin.models.device.getManifestBySlug(resource.device_type)
|
||||
.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) ->
|
||||
if resource.uuid?
|
||||
generateDeviceConfig(resource, options.deviceApiKey, answers)
|
||||
else
|
||||
generateApplicationConfig(resource, answers)
|
||||
.then (config) ->
|
||||
deviceConfig.validate(config)
|
||||
if options.output?
|
||||
return fs.writeFileAsync(options.output, JSON.stringify(config))
|
||||
return writeFileAsync(options.output, JSON.stringify(config))
|
||||
|
||||
console.log(prettyjson.render(config))
|
||||
.nodeify(done)
|
||||
|
229
lib/actions/deploy.coffee
Normal file
229
lib/actions/deploy.coffee
Normal file
@ -0,0 +1,229 @@
|
||||
Promise = require('bluebird')
|
||||
dockerUtils = require('../utils/docker')
|
||||
|
||||
getBuilderPushEndpoint = (baseUrl, owner, app) ->
|
||||
querystring = require('querystring')
|
||||
args = querystring.stringify({ owner, app })
|
||||
"https://builder.#{baseUrl}/v1/push?#{args}"
|
||||
|
||||
getBuilderLogPushEndpoint = (baseUrl, buildId, owner, app) ->
|
||||
querystring = require('querystring')
|
||||
args = querystring.stringify({ owner, app, buildId })
|
||||
"https://builder.#{baseUrl}/v1/pushLogs?#{args}"
|
||||
|
||||
formatImageName = (image) ->
|
||||
image.split('/').pop()
|
||||
|
||||
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!')
|
||||
|
||||
return [appName, options.build, source, image]
|
||||
|
||||
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)
|
||||
|
||||
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.
|
||||
|
||||
Usage: `deploy <appName> ([image] | --build [--source build-dir])`
|
||||
|
||||
To deploy to an app on which you're a collaborator, use
|
||||
`resin deploy <appOwnerUsername>/<appName>`.
|
||||
|
||||
Note: If building with this command, all options supported by `resin build`
|
||||
are also supported with this command.
|
||||
|
||||
Examples:
|
||||
$ resin deploy myApp --build --source myBuildDir/
|
||||
$ resin deploy myApp myApp/myImage
|
||||
'''
|
||||
permission: 'user'
|
||||
options: dockerUtils.appendOptions [
|
||||
{
|
||||
signature: 'build'
|
||||
boolean: true
|
||||
description: 'Build image then deploy'
|
||||
alias: 'b'
|
||||
},
|
||||
{
|
||||
signature: 'source'
|
||||
parameter: 'source'
|
||||
description: 'The source directory to use when building the image'
|
||||
alias: 's'
|
||||
},
|
||||
{
|
||||
signature: 'nologupload'
|
||||
description: "Don't upload build logs to the dashboard with image (if building)"
|
||||
boolean: true
|
||||
}
|
||||
]
|
||||
action: (params, options, done) ->
|
||||
_ = require('lodash')
|
||||
tmp = require('tmp')
|
||||
tmpNameAsync = Promise.promisify(tmp.tmpName)
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
|
||||
Logger = require('../utils/logger')
|
||||
logger = new Logger()
|
||||
|
||||
# Ensure the tmp files gets deleted
|
||||
tmp.setGracefulCleanup()
|
||||
|
||||
logs = ''
|
||||
|
||||
upload = (token, username, url) ->
|
||||
dockerUtils.getDocker(options)
|
||||
.then (docker) ->
|
||||
# Check input parameters
|
||||
parseInput(params, options)
|
||||
.then ([appName, build, source, imageName]) ->
|
||||
tmpNameAsync()
|
||||
.then (bufferFile) ->
|
||||
|
||||
# Setup the build args for how the build routine expects them
|
||||
options = _.assign({}, options, { appName })
|
||||
params = _.assign({}, params, { source })
|
||||
|
||||
Promise.try ->
|
||||
if build
|
||||
dockerUtils.runBuild(params, options, getBundleInfo, logger)
|
||||
else
|
||||
{ image: imageName, log: '' }
|
||||
.then ({ image: imageName, log: buildLogs }) ->
|
||||
logger.logInfo('Initializing deploy...')
|
||||
|
||||
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 ''
|
||||
|
||||
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
|
||||
)
|
@ -1,5 +1,5 @@
|
||||
###
|
||||
Copyright 2016 Resin.io
|
||||
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.
|
||||
@ -15,6 +15,7 @@ limitations under the License.
|
||||
###
|
||||
|
||||
commandOptions = require('./command-options')
|
||||
_ = require('lodash')
|
||||
|
||||
exports.list =
|
||||
signature: 'devices'
|
||||
@ -36,8 +37,7 @@ exports.list =
|
||||
primary: true
|
||||
action: (params, options, done) ->
|
||||
Promise = require('bluebird')
|
||||
_ = require('lodash')
|
||||
resin = require('resin-sdk')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
visuals = require('resin-cli-visuals')
|
||||
|
||||
Promise.try ->
|
||||
@ -58,6 +58,9 @@ exports.list =
|
||||
'application_name'
|
||||
'status'
|
||||
'is_online'
|
||||
'supervisor_version'
|
||||
'os_version'
|
||||
'dashboard_url'
|
||||
]
|
||||
.nodeify(done)
|
||||
|
||||
@ -74,7 +77,7 @@ exports.info =
|
||||
permission: 'user'
|
||||
primary: true
|
||||
action: (params, options, done) ->
|
||||
resin = require('resin-sdk')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
visuals = require('resin-cli-visuals')
|
||||
|
||||
resin.models.device.get(params.uuid).then (device) ->
|
||||
@ -96,37 +99,72 @@ exports.info =
|
||||
'supervisor_version'
|
||||
'is_web_accessible'
|
||||
'note'
|
||||
'os_version'
|
||||
'dashboard_url'
|
||||
]
|
||||
.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>
|
||||
'''
|
||||
permission: 'user'
|
||||
options: [
|
||||
signature: 'uuid'
|
||||
description: 'custom uuid'
|
||||
parameter: 'uuid'
|
||||
alias: 'u'
|
||||
{
|
||||
signature: 'uuid'
|
||||
description: 'custom uuid'
|
||||
parameter: 'uuid'
|
||||
alias: 'u'
|
||||
}
|
||||
commandOptions.optionalDeviceApiKey
|
||||
]
|
||||
action: (params, options, done) ->
|
||||
Promise = require('bluebird')
|
||||
resin = require('resin-sdk')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
|
||||
resin.models.application.get(params.application).then (application) ->
|
||||
|
||||
Promise.try ->
|
||||
return options.uuid or resin.models.device.generateUUID()
|
||||
.then (uuid) ->
|
||||
Promise.join(
|
||||
resin.models.application.get(params.application)
|
||||
options.uuid ? resin.models.device.generateUniqueKey()
|
||||
options.deviceApiKey ? resin.models.device.generateUniqueKey()
|
||||
(application, uuid, deviceApiKey) ->
|
||||
console.info("Registering to #{application.app_name}: #{uuid}")
|
||||
return resin.models.device.register(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)
|
||||
)
|
||||
.get('uuid')
|
||||
.nodeify(done)
|
||||
|
||||
@ -147,7 +185,7 @@ exports.remove =
|
||||
options: [ commandOptions.yes ]
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
resin = require('resin-sdk')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
patterns = require('../utils/patterns')
|
||||
|
||||
patterns.confirm(options.yes, 'Are you sure you want to delete the device?').then ->
|
||||
@ -168,7 +206,7 @@ exports.identify =
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
resin = require('resin-sdk')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
resin.models.device.identify(params.uuid).nodeify(done)
|
||||
|
||||
exports.reboot =
|
||||
@ -181,10 +219,91 @@ exports.reboot =
|
||||
|
||||
$ resin device reboot 23c73a1
|
||||
'''
|
||||
options: [ commandOptions.forceUpdateLock ]
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
resin = require('resin-sdk')
|
||||
resin.models.device.reboot(params.uuid).nodeify(done)
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
resin.models.device.reboot(params.uuid, options).nodeify(done)
|
||||
|
||||
exports.shutdown =
|
||||
signature: 'device shutdown <uuid>'
|
||||
description: 'shutdown a device'
|
||||
help: '''
|
||||
Use this command to remotely shutdown a device
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin 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)
|
||||
|
||||
exports.enableDeviceUrl =
|
||||
signature: 'device public-url enable <uuid>'
|
||||
description: 'enable public URL for a device'
|
||||
help: '''
|
||||
Use this command to enable public URL for a device
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin device public-url enable 23c73a1
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
resin.models.device.enableDeviceUrl(params.uuid).nodeify(done)
|
||||
|
||||
exports.disableDeviceUrl =
|
||||
signature: 'device public-url disable <uuid>'
|
||||
description: 'disable public URL for a device'
|
||||
help: '''
|
||||
Use this command to disable public URL for a device
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin device public-url disable 23c73a1
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
resin.models.device.disableDeviceUrl(params.uuid).nodeify(done)
|
||||
|
||||
exports.getDeviceUrl =
|
||||
signature: 'device public-url <uuid>'
|
||||
description: 'gets the public URL of a device'
|
||||
help: '''
|
||||
Use this command to get the public URL of a device
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin device public-url 23c73a1
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
resin.models.device.getDeviceUrl(params.uuid).then (url) ->
|
||||
console.log(url)
|
||||
.nodeify(done)
|
||||
|
||||
exports.hasDeviceUrl =
|
||||
signature: 'device public-url status <uuid>'
|
||||
description: 'Returns true if public URL is enabled for a device'
|
||||
help: '''
|
||||
Use this command to determine if public URL is enabled for a device
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin device public-url status 23c73a1
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
resin.models.device.hasDeviceUrl(params.uuid).then (hasDeviceUrl) ->
|
||||
console.log(hasDeviceUrl)
|
||||
.nodeify(done)
|
||||
|
||||
exports.rename =
|
||||
signature: 'device rename <uuid> [newName]'
|
||||
@ -202,8 +321,7 @@ exports.rename =
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
Promise = require('bluebird')
|
||||
_ = require('lodash')
|
||||
resin = require('resin-sdk')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
form = require('resin-cli-form')
|
||||
|
||||
Promise.try ->
|
||||
@ -232,13 +350,12 @@ exports.move =
|
||||
permission: 'user'
|
||||
options: [ commandOptions.optionalApplication ]
|
||||
action: (params, options, done) ->
|
||||
resin = require('resin-sdk')
|
||||
_ = require('lodash')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
patterns = require('../utils/patterns')
|
||||
|
||||
resin.models.device.get(params.uuid).then (device) ->
|
||||
return options.application or patterns.selectApplication (application) ->
|
||||
return _.all [
|
||||
return _.every [
|
||||
application.device_type is device.device_type
|
||||
device.application_name isnt application.app_name
|
||||
]
|
||||
@ -250,7 +367,7 @@ exports.move =
|
||||
|
||||
exports.init =
|
||||
signature: 'device init'
|
||||
description: 'initialise a device with resin os'
|
||||
description: 'initialise a device with resinOS'
|
||||
help: '''
|
||||
Use this command to download the OS image of a certain application and write it to an SD Card.
|
||||
|
||||
@ -265,22 +382,25 @@ exports.init =
|
||||
options: [
|
||||
commandOptions.optionalApplication
|
||||
commandOptions.yes
|
||||
commandOptions.advancedConfig
|
||||
_.assign({}, commandOptions.osVersion, { signature: 'os-version', parameter: 'os-version' })
|
||||
commandOptions.drive
|
||||
{
|
||||
signature: 'advanced'
|
||||
description: 'enable advanced configuration'
|
||||
boolean: true
|
||||
alias: 'v'
|
||||
signature: 'config'
|
||||
description: 'path to the config JSON file, see `resin os build-config`'
|
||||
parameter: 'config'
|
||||
}
|
||||
]
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
Promise = require('bluebird')
|
||||
capitano = Promise.promisifyAll(require('capitano'))
|
||||
capitanoRunAsync = Promise.promisify(require('capitano').run)
|
||||
rimraf = Promise.promisify(require('rimraf'))
|
||||
tmp = Promise.promisifyAll(require('tmp'))
|
||||
tmp = require('tmp')
|
||||
tmpNameAsync = Promise.promisify(tmp.tmpName)
|
||||
tmp.setGracefulCleanup()
|
||||
|
||||
resin = require('resin-sdk')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
helpers = require('../utils/helpers')
|
||||
patterns = require('../utils/patterns')
|
||||
|
||||
@ -291,26 +411,29 @@ exports.init =
|
||||
.then (application) ->
|
||||
|
||||
download = ->
|
||||
tmp.tmpNameAsync().then (temporalPath) ->
|
||||
capitano.runAsync("os download #{application.device_type} --output #{temporalPath}")
|
||||
.disposer (temporalPath) ->
|
||||
return rimraf(temporalPath)
|
||||
tmpNameAsync().then (tempPath) ->
|
||||
osVersion = options['os-version'] or 'default'
|
||||
capitanoRunAsync("os download #{application.device_type} --output '#{tempPath}' --version #{osVersion}")
|
||||
.disposer (tempPath) ->
|
||||
return rimraf(tempPath)
|
||||
|
||||
Promise.using download(), (temporalPath) ->
|
||||
capitano.runAsync("device register #{application.app_name}")
|
||||
Promise.using download(), (tempPath) ->
|
||||
capitanoRunAsync("device register #{application.app_name}")
|
||||
.then(resin.models.device.get)
|
||||
.tap (device) ->
|
||||
configure = "os configure #{temporalPath} #{device.uuid}"
|
||||
configure += ' --advanced' if options.advanced
|
||||
capitano.runAsync(configure).then ->
|
||||
message = '''
|
||||
Initializing a device requires administrative permissions
|
||||
given that we need to access raw devices directly.
|
||||
|
||||
'''
|
||||
|
||||
helpers.sudo([ 'os', 'initialize', temporalPath, '--type', application.device_type ], message)
|
||||
|
||||
configureCommand = "os configure '#{tempPath}' #{device.uuid}"
|
||||
if options.config
|
||||
configureCommand += " --config '#{options.config}'"
|
||||
else if options.advanced
|
||||
configureCommand += ' --advanced'
|
||||
capitanoRunAsync(configureCommand)
|
||||
.then ->
|
||||
osInitCommand = "os initialize '#{tempPath}' --type #{application.device_type}"
|
||||
if options.yes
|
||||
osInitCommand += ' --yes'
|
||||
if options.drive
|
||||
osInitCommand += " --drive #{options.drive}"
|
||||
capitanoRunAsync(osInitCommand)
|
||||
# Make sure the device resource is removed if there is an
|
||||
# error when configuring or initializing a device image
|
||||
.catch (error) ->
|
||||
|
@ -1,5 +1,5 @@
|
||||
###
|
||||
Copyright 2016 Resin.io
|
||||
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.
|
||||
@ -48,7 +48,7 @@ exports.list =
|
||||
action: (params, options, done) ->
|
||||
Promise = require('bluebird')
|
||||
_ = require('lodash')
|
||||
resin = require('resin-sdk')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
visuals = require('resin-cli-visuals')
|
||||
|
||||
Promise.try ->
|
||||
@ -98,7 +98,7 @@ exports.remove =
|
||||
]
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
resin = require('resin-sdk')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
patterns = require('../utils/patterns')
|
||||
|
||||
patterns.confirm(options.yes, 'Are you sure you want to delete the environment variable?').then ->
|
||||
@ -136,7 +136,7 @@ exports.add =
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
Promise = require('bluebird')
|
||||
resin = require('resin-sdk')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
|
||||
Promise.try ->
|
||||
if not params.value?
|
||||
@ -172,7 +172,7 @@ exports.rename =
|
||||
options: [ commandOptions.booleanDevice ]
|
||||
action: (params, options, done) ->
|
||||
Promise = require('bluebird')
|
||||
resin = require('resin-sdk')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
|
||||
Promise.try ->
|
||||
if options.device
|
||||
|
@ -1,5 +1,5 @@
|
||||
###
|
||||
Copyright 2016 Resin.io
|
||||
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.
|
||||
@ -15,13 +15,12 @@ limitations under the License.
|
||||
###
|
||||
|
||||
_ = require('lodash')
|
||||
_.str = require('underscore.string')
|
||||
capitano = require('capitano')
|
||||
columnify = require('columnify')
|
||||
messages = require('../utils/messages')
|
||||
|
||||
parse = (object) ->
|
||||
return _.object _.map object, (item) ->
|
||||
return _.fromPairs _.map object, (item) ->
|
||||
|
||||
# Hacky way to determine if an object is
|
||||
# a function or a command
|
||||
@ -36,7 +35,7 @@ parse = (object) ->
|
||||
]
|
||||
|
||||
indent = (text) ->
|
||||
text = _.map _.str.lines(text), (line) ->
|
||||
text = _.map text.split('\n'), (line) ->
|
||||
return ' ' + line
|
||||
return text.join('\n')
|
||||
|
||||
@ -47,19 +46,18 @@ print = (data) ->
|
||||
|
||||
general = (params, options, done) ->
|
||||
console.log('Usage: resin [COMMAND] [OPTIONS]\n')
|
||||
console.log("#{messages.gettingStarted}\n")
|
||||
console.log(messages.reachingOut)
|
||||
console.log('\nPrimary commands:\n')
|
||||
|
||||
# We do not want the wildcard command
|
||||
# to be printed in the help screen.
|
||||
commands = _.reject capitano.state.commands, (command) ->
|
||||
return command.isWildcard()
|
||||
return command.hidden or command.isWildcard()
|
||||
|
||||
groupedCommands = _.groupBy commands, (command) ->
|
||||
if command.plugin
|
||||
return 'plugins'
|
||||
else if command.primary
|
||||
if command.primary
|
||||
return 'primary'
|
||||
return 'secondary'
|
||||
|
||||
@ -93,7 +91,7 @@ command = (params, options, done) ->
|
||||
if command.help?
|
||||
console.log("\n#{command.help}")
|
||||
else if command.description?
|
||||
console.log("\n#{_.str.humanize(command.description)}")
|
||||
console.log("\n#{_.capitalize(command.description)}")
|
||||
|
||||
if not _.isEmpty(command.options)
|
||||
console.log('\nOptions:\n')
|
||||
|
@ -1,5 +1,5 @@
|
||||
###
|
||||
Copyright 2016 Resin.io
|
||||
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.
|
||||
@ -23,8 +23,16 @@ module.exports =
|
||||
env: require('./environment-variables')
|
||||
keys: require('./keys')
|
||||
logs: require('./logs')
|
||||
local: require('./local')
|
||||
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')
|
||||
|
@ -1,5 +1,5 @@
|
||||
###
|
||||
Copyright 2016 Resin.io
|
||||
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.
|
||||
|
37
lib/actions/internal.coffee
Normal file
37
lib/actions/internal.coffee
Normal file
@ -0,0 +1,37 @@
|
||||
###
|
||||
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.
|
||||
###
|
||||
|
||||
# These are internal commands we want to be runnable from the outside
|
||||
# One use-case for this is spawning the minimal operation with root priviledges
|
||||
|
||||
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.
|
||||
'''
|
||||
hidden: true
|
||||
root: true
|
||||
action: (params, options, done) ->
|
||||
Promise = require('bluebird')
|
||||
init = require('resin-device-init')
|
||||
helpers = require('../utils/helpers')
|
||||
|
||||
return Promise.try ->
|
||||
config = JSON.parse(params.config)
|
||||
init.initialize(params.image, params.type, config)
|
||||
.then(helpers.osProgressHandler)
|
||||
.nodeify(done)
|
@ -1,5 +1,5 @@
|
||||
###
|
||||
Copyright 2016 Resin.io
|
||||
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.
|
||||
@ -28,7 +28,7 @@ exports.list =
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
resin = require('resin-sdk')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
visuals = require('resin-cli-visuals')
|
||||
|
||||
resin.models.key.getAll().then (keys) ->
|
||||
@ -50,7 +50,7 @@ exports.info =
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
resin = require('resin-sdk')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
visuals = require('resin-cli-visuals')
|
||||
|
||||
resin.models.key.get(params.id).then (key) ->
|
||||
@ -82,7 +82,7 @@ exports.remove =
|
||||
options: [ commandOptions.yes ]
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
resin = require('resin-sdk')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
patterns = require('../utils/patterns')
|
||||
|
||||
patterns.confirm(options.yes, 'Are you sure you want to delete the key?').then ->
|
||||
@ -107,13 +107,14 @@ exports.add =
|
||||
action: (params, options, done) ->
|
||||
_ = require('lodash')
|
||||
Promise = require('bluebird')
|
||||
fs = Promise.promisifyAll(require('fs'))
|
||||
readFileAsync = Promise.promisify(require('fs').readFile)
|
||||
capitano = require('capitano')
|
||||
resin = require('resin-sdk')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
|
||||
Promise.try ->
|
||||
return fs.readFileAsync(params.path, encoding: 'utf8') if params.path?
|
||||
return readFileAsync(params.path, encoding: 'utf8') if params.path?
|
||||
|
||||
# TODO: should this be promisified for consistency?
|
||||
Promise.fromNode (callback) ->
|
||||
capitano.utils.getStdin (data) ->
|
||||
return callback(null, data)
|
||||
|
61
lib/actions/local/common.coffee
Normal file
61
lib/actions/local/common.coffee
Normal file
@ -0,0 +1,61 @@
|
||||
Promise = require('bluebird')
|
||||
_ = require('lodash')
|
||||
Docker = require('docker-toolbelt')
|
||||
form = require('resin-cli-form')
|
||||
chalk = require('chalk')
|
||||
|
||||
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 true
|
||||
|
||||
exports.selectContainerFromDevice = Promise.method (deviceIp, filterSupervisor = false) ->
|
||||
docker = new Docker(host: deviceIp, port: dockerPort, timeout: dockerTimeout)
|
||||
|
||||
# List all containers, including those not running
|
||||
docker.listContainersAsync(all: true)
|
||||
.filter (container) ->
|
||||
return true if not filterSupervisor
|
||||
filterOutSupervisorContainer(container)
|
||||
.then (containers) ->
|
||||
if _.isEmpty(containers)
|
||||
throw new Error("No containers found in #{deviceIp}")
|
||||
|
||||
return form.ask
|
||||
message: 'Select a container'
|
||||
type: 'list'
|
||||
choices: _.map containers, (container) ->
|
||||
containerName = container.Names[0] or 'Untitled'
|
||||
shortContainerId = ('' + container.Id).substr(0, 11)
|
||||
containerStatus = container.Status
|
||||
|
||||
return {
|
||||
name: "#{containerName} (#{shortContainerId}) - #{containerStatus}"
|
||||
value: container.Id
|
||||
}
|
||||
|
||||
exports.pipeContainerStream = Promise.method ({ deviceIp, name, outStream, follow = false }) ->
|
||||
docker = new Docker(host: deviceIp, port: dockerPort)
|
||||
|
||||
container = docker.getContainer(name)
|
||||
container.inspectAsync()
|
||||
.then (containerInfo) ->
|
||||
return containerInfo?.State?.Running
|
||||
.then (isRunning) ->
|
||||
container.attachAsync
|
||||
logs: not follow or not isRunning
|
||||
stream: follow and isRunning
|
||||
stdout: true
|
||||
stderr: true
|
||||
.then (containerStream) ->
|
||||
containerStream.pipe(outStream)
|
||||
.catch (err) ->
|
||||
err = '' + err.statusCode
|
||||
if err is '404'
|
||||
return console.log(chalk.red.bold("Container '#{name}' not found."))
|
||||
throw err
|
||||
|
||||
exports.getSubShellCommand = require('../../utils/helpers').getSubShellCommand
|
228
lib/actions/local/configure.coffee
Normal file
228
lib/actions/local/configure.coffee
Normal file
@ -0,0 +1,228 @@
|
||||
###
|
||||
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.
|
||||
###
|
||||
|
||||
BOOT_PARTITION = { primary: 1 }
|
||||
CONNECTIONS_FOLDER = '/system-connections'
|
||||
|
||||
getConfigurationSchema = (connnectionFileName = 'resin-wifi') ->
|
||||
mapper: [
|
||||
{
|
||||
template:
|
||||
persistentLogging: '{{persistentLogging}}'
|
||||
domain: [
|
||||
[ 'config_json', 'persistentLogging' ]
|
||||
]
|
||||
}
|
||||
{
|
||||
template:
|
||||
hostname: '{{hostname}}'
|
||||
domain: [
|
||||
[ 'config_json', 'hostname' ]
|
||||
]
|
||||
}
|
||||
{
|
||||
template:
|
||||
wifi:
|
||||
ssid: '{{networkSsid}}'
|
||||
'wifi-security':
|
||||
psk: '{{networkKey}}'
|
||||
domain: [
|
||||
[ 'system_connections', connnectionFileName, 'wifi' ]
|
||||
[ 'system_connections', connnectionFileName, 'wifi-security' ]
|
||||
]
|
||||
}
|
||||
]
|
||||
files:
|
||||
system_connections:
|
||||
fileset: true
|
||||
type: 'ini'
|
||||
location:
|
||||
path: CONNECTIONS_FOLDER.slice(1)
|
||||
partition: BOOT_PARTITION
|
||||
config_json:
|
||||
type: 'json'
|
||||
location:
|
||||
path: 'config.json'
|
||||
partition: BOOT_PARTITION
|
||||
|
||||
inquirerOptions = (data) -> [
|
||||
{
|
||||
message: 'Network SSID'
|
||||
type: 'input'
|
||||
name: 'networkSsid'
|
||||
default: data.networkSsid
|
||||
}
|
||||
{
|
||||
message: 'Network Key'
|
||||
type: 'input'
|
||||
name: 'networkKey'
|
||||
default: data.networkKey
|
||||
}
|
||||
{
|
||||
message: 'Do you want to set advanced settings?'
|
||||
type: 'confirm'
|
||||
name: 'advancedSettings'
|
||||
default: false
|
||||
}
|
||||
{
|
||||
message: 'Device Hostname'
|
||||
type: 'input'
|
||||
name: 'hostname'
|
||||
default: data.hostname,
|
||||
when: (answers) ->
|
||||
answers.advancedSettings
|
||||
}
|
||||
{
|
||||
message: 'Do you want to enable persistent logging?'
|
||||
type: 'confirm'
|
||||
name: 'persistentLogging'
|
||||
default: data.persistentLogging
|
||||
when: (answers) ->
|
||||
answers.advancedSettings
|
||||
}
|
||||
]
|
||||
|
||||
getConfiguration = (data) ->
|
||||
_ = require('lodash')
|
||||
inquirer = require('inquirer')
|
||||
|
||||
# `persistentLogging` can be `undefined`, so we want
|
||||
# to make sure that case defaults to `false`
|
||||
data = _.assign data,
|
||||
persistentLogging: data.persistentLogging or false
|
||||
|
||||
inquirer.prompt(inquirerOptions(data))
|
||||
.then (answers) ->
|
||||
return _.merge(data, answers)
|
||||
|
||||
# Taken from https://goo.gl/kr1kCt
|
||||
CONNECTION_FILE = '''
|
||||
[connection]
|
||||
id=resin-wifi
|
||||
type=wifi
|
||||
|
||||
[wifi]
|
||||
hidden=true
|
||||
mode=infrastructure
|
||||
ssid=My_Wifi_Ssid
|
||||
|
||||
[wifi-security]
|
||||
auth-alg=open
|
||||
key-mgmt=wpa-psk
|
||||
psk=super_secret_wifi_password
|
||||
|
||||
[ipv4]
|
||||
method=auto
|
||||
|
||||
[ipv6]
|
||||
addr-gen-mode=stable-privacy
|
||||
method=auto
|
||||
'''
|
||||
|
||||
###
|
||||
* if the `resin-wifi` file exists (previously configured image or downloaded from the UI) it's used and reconfigured
|
||||
* if the `resin-sample.ignore` exists it's copied to `resin-wifi`
|
||||
* if the `resin-sample` exists it's reconfigured (legacy mode, will be removed eventually)
|
||||
* otherwise, the new file is created
|
||||
###
|
||||
prepareConnectionFile = (target) ->
|
||||
_ = require('lodash')
|
||||
imagefs = require('resin-image-fs')
|
||||
|
||||
imagefs.listDirectory
|
||||
image: target
|
||||
partition: BOOT_PARTITION
|
||||
path: CONNECTIONS_FOLDER
|
||||
.then (files) ->
|
||||
# The required file already exists
|
||||
if _.includes(files, 'resin-wifi')
|
||||
return null
|
||||
|
||||
# Fresh image, new mode, accoding to https://github.com/resin-os/meta-resin/pull/770/files
|
||||
if _.includes(files, 'resin-sample.ignore')
|
||||
return imagefs.copy
|
||||
image: target
|
||||
partition: BOOT_PARTITION
|
||||
path: "#{CONNECTIONS_FOLDER}/resin-sample.ignore"
|
||||
,
|
||||
image: target
|
||||
partition: BOOT_PARTITION
|
||||
path: "#{CONNECTIONS_FOLDER}/resin-wifi"
|
||||
.thenReturn(null)
|
||||
|
||||
# Legacy mode, to be removed later
|
||||
# We return the file name override from this branch
|
||||
# When it is removed the following cleanup should be done:
|
||||
# * delete all the null returns from this method
|
||||
# * turn `getConfigurationSchema` back into the constant, with the connection filename always being `resin-wifi`
|
||||
# * drop the final `then` from this method
|
||||
# * adapt the code in the main listener to not receive the config from this method, and use that constant instead
|
||||
if _.includes(files, 'resin-sample')
|
||||
return 'resin-sample'
|
||||
|
||||
# In case there's no file at all (shouldn't happen normally, but the file might have been removed)
|
||||
return imagefs.writeFile
|
||||
image: target
|
||||
partition: BOOT_PARTITION
|
||||
path: "#{CONNECTIONS_FOLDER}/resin-wifi"
|
||||
, CONNECTION_FILE
|
||||
.thenReturn(null)
|
||||
|
||||
.then (connectionFileName) ->
|
||||
return getConfigurationSchema(connectionFileName)
|
||||
|
||||
removeHostname = (schema) ->
|
||||
_ = require('lodash')
|
||||
schema.mapper = _.reject schema.mapper, (mapper) ->
|
||||
_.isEqual(Object.keys(mapper.template), ['hostname'])
|
||||
|
||||
module.exports =
|
||||
signature: 'local configure <target>'
|
||||
description: '(Re)configure a resinOS drive or image'
|
||||
help: '''
|
||||
Use this command to configure or reconfigure a resinOS drive or image.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin local configure /dev/sdc
|
||||
$ resin local configure path/to/image.img
|
||||
'''
|
||||
root: true
|
||||
action: (params, options, done) ->
|
||||
Promise = require('bluebird')
|
||||
umount = require('umount')
|
||||
umountAsync = Promise.promisify(umount.umount)
|
||||
isMountedAsync = Promise.promisify(umount.isMounted)
|
||||
reconfix = require('reconfix')
|
||||
denymount = Promise.promisify(require('denymount'))
|
||||
|
||||
prepareConnectionFile(params.target)
|
||||
.tap ->
|
||||
isMountedAsync(params.target).then (isMounted) ->
|
||||
return if not isMounted
|
||||
umountAsync(params.target)
|
||||
.then (configurationSchema) ->
|
||||
denymount params.target, (cb) ->
|
||||
reconfix.readConfiguration(configurationSchema, params.target)
|
||||
.then(getConfiguration)
|
||||
.then (answers) ->
|
||||
if not answers.hostname
|
||||
removeHostname(configurationSchema)
|
||||
reconfix.writeConfiguration(configurationSchema, answers, params.target)
|
||||
.asCallback(cb)
|
||||
.then ->
|
||||
console.log('Done!')
|
||||
.asCallback(done)
|
120
lib/actions/local/flash.coffee
Normal file
120
lib/actions/local/flash.coffee
Normal file
@ -0,0 +1,120 @@
|
||||
###
|
||||
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)
|
@ -1,6 +1,5 @@
|
||||
|
||||
/*
|
||||
Copyright 2016 Resin.io
|
||||
###
|
||||
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.
|
||||
@ -13,19 +12,12 @@ 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.
|
||||
*/
|
||||
###
|
||||
|
||||
(function() {
|
||||
exports.version = {
|
||||
signature: 'version',
|
||||
description: 'output the version number',
|
||||
help: 'Display the Resin CLI version.',
|
||||
action: function(params, options, done) {
|
||||
var packageJSON;
|
||||
packageJSON = require('../../package.json');
|
||||
console.log(packageJSON.version);
|
||||
return done();
|
||||
}
|
||||
};
|
||||
|
||||
}).call(this);
|
||||
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')
|
66
lib/actions/local/logs.coffee
Normal file
66
lib/actions/local/logs.coffee
Normal file
@ -0,0 +1,66 @@
|
||||
###
|
||||
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.
|
||||
###
|
||||
|
||||
# A function to reliably execute a command
|
||||
# in all supported operating systems, including
|
||||
# different Windows environments like `cmd.exe`
|
||||
# and `Cygwin` should be encapsulated in a
|
||||
# re-usable package.
|
||||
#
|
||||
module.exports =
|
||||
signature: 'local logs [deviceIp]'
|
||||
description: 'Get or attach to logs of a running container on a resinOS 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
|
||||
'''
|
||||
options: [
|
||||
signature: 'follow'
|
||||
boolean: true
|
||||
description: 'follow log'
|
||||
alias: 'f'
|
||||
,
|
||||
signature: 'app-name'
|
||||
parameter: 'name'
|
||||
description: 'name of container to get logs from'
|
||||
alias: 'a'
|
||||
]
|
||||
root: true
|
||||
action: (params, options, done) ->
|
||||
Promise = require('bluebird')
|
||||
{ forms } = require('resin-sync')
|
||||
{ selectContainerFromDevice, pipeContainerStream } = require('./common')
|
||||
|
||||
Promise.try ->
|
||||
if not params.deviceIp?
|
||||
return forms.selectLocalResinOsDevice()
|
||||
return params.deviceIp
|
||||
.then (@deviceIp) =>
|
||||
if not options['app-name']?
|
||||
return selectContainerFromDevice(@deviceIp)
|
||||
return options['app-name']
|
||||
.then (appName) =>
|
||||
pipeContainerStream
|
||||
deviceIp: @deviceIp
|
||||
name: appName
|
||||
outStream: process.stdout
|
||||
follow: options['follow']
|
76
lib/actions/local/push.coffee
Normal file
76
lib/actions/local/push.coffee
Normal file
@ -0,0 +1,76 @@
|
||||
###
|
||||
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.
|
||||
###
|
||||
|
||||
# Loads '.resin-sync.yml' configuration from 'source' directory.
|
||||
# Returns the configuration object on success
|
||||
#
|
||||
|
||||
_ = require('lodash')
|
||||
|
||||
resinPush = require('resin-sync').capitano('resin-toolbox')
|
||||
|
||||
# TODO: This is a temporary workaround to reuse the existing `rdt push`
|
||||
# capitano frontend in `resin 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
|
||||
|
||||
Use this command to push your local changes to a container on a LAN-accessible resinOS device on the fly.
|
||||
|
||||
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.
|
||||
|
||||
Here is an example '.resin-sync.yml' :
|
||||
|
||||
$ cat $PWD/.resin-sync.yml
|
||||
destination: '/usr/src/app'
|
||||
before: 'echo Hello'
|
||||
after: 'echo Done'
|
||||
ignore:
|
||||
- .git
|
||||
- node_modules/
|
||||
|
||||
Command line options have precedence over the ones saved in '.resin-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
|
||||
'--skip-gitignore' option.
|
||||
|
||||
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'
|
||||
'''
|
||||
|
||||
|
||||
module.exports = _.assign resinPush,
|
||||
signature: 'local push [deviceIp]'
|
||||
help: resinPushHelp
|
||||
primary: true
|
||||
root: true
|
99
lib/actions/local/scan.coffee
Normal file
99
lib/actions/local/scan.coffee
Normal file
@ -0,0 +1,99 @@
|
||||
###
|
||||
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.
|
||||
###
|
||||
|
||||
dockerInfoProperties = [
|
||||
'Containers'
|
||||
'ContainersRunning'
|
||||
'ContainersPaused'
|
||||
'ContainersStopped'
|
||||
'Images'
|
||||
'Driver'
|
||||
'SystemTime'
|
||||
'KernelVersion'
|
||||
'OperatingSystem'
|
||||
'Architecture'
|
||||
]
|
||||
|
||||
dockerVersionProperties = [
|
||||
'Version'
|
||||
'ApiVersion'
|
||||
]
|
||||
|
||||
module.exports =
|
||||
signature: 'local scan'
|
||||
description: 'Scan for resinOS devices in your local network'
|
||||
help: '''
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin local scan
|
||||
$ resin local scan --timeout 120
|
||||
$ resin local scan --verbose
|
||||
'''
|
||||
options: [
|
||||
signature: 'verbose'
|
||||
boolean: true
|
||||
description: 'Display full info'
|
||||
alias: 'v'
|
||||
,
|
||||
signature: 'timeout'
|
||||
parameter: 'timeout'
|
||||
description: 'Scan timeout in seconds'
|
||||
alias: 't'
|
||||
]
|
||||
primary: true
|
||||
root: true
|
||||
action: (params, options, done) ->
|
||||
Promise = require('bluebird')
|
||||
_ = require('lodash')
|
||||
prettyjson = require('prettyjson')
|
||||
Docker = require('docker-toolbelt')
|
||||
{ discover } = require('resin-sync')
|
||||
{ SpinnerPromise } = require('resin-cli-visuals')
|
||||
{ dockerPort, dockerTimeout } = require('./common')
|
||||
|
||||
if options.timeout?
|
||||
options.timeout *= 1000
|
||||
|
||||
Promise.try ->
|
||||
new SpinnerPromise
|
||||
promise: discover.discoverLocalResinOsDevices(options.timeout)
|
||||
startMessage: 'Scanning for local resinOS devices..'
|
||||
stopMessage: 'Reporting scan results'
|
||||
.filter ({ address }) ->
|
||||
Promise.try ->
|
||||
docker = new Docker(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')
|
||||
.map ({ host, address }) ->
|
||||
docker = new Docker(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')
|
||||
.then ({ dockerInfo, dockerVersion }) ->
|
||||
|
||||
if not options.verbose
|
||||
dockerInfo = _.pick(dockerInfo, dockerInfoProperties) if _.isObject(dockerInfo)
|
||||
dockerVersion = _.pick(dockerVersion, dockerVersionProperties) if _.isObject(dockerVersion)
|
||||
|
||||
return { host, address, dockerInfo, dockerVersion }
|
||||
.then (devicesInfo) ->
|
||||
console.log(prettyjson.render(devicesInfo, noColor: true))
|
||||
.nodeify(done)
|
109
lib/actions/local/ssh.coffee
Normal file
109
lib/actions/local/ssh.coffee
Normal file
@ -0,0 +1,109 @@
|
||||
###
|
||||
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 ssh [deviceIp]'
|
||||
description: 'Get a shell into a resinOS device'
|
||||
help: '''
|
||||
Warning: 'resin 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
|
||||
|
||||
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.
|
||||
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
|
||||
'''
|
||||
options: [
|
||||
signature: 'verbose'
|
||||
boolean: true
|
||||
description: 'increase verbosity'
|
||||
alias: 'v'
|
||||
,
|
||||
signature: 'host'
|
||||
boolean: true
|
||||
description: 'get a shell into the host OS'
|
||||
alias: 's'
|
||||
,
|
||||
signature: 'container'
|
||||
parameter: 'container'
|
||||
default: null
|
||||
description: 'name of container to access'
|
||||
alias: 'c'
|
||||
,
|
||||
signature: 'port'
|
||||
parameter: 'port'
|
||||
description: 'ssh port number (default: 22222)'
|
||||
alias: 'p'
|
||||
]
|
||||
root: true
|
||||
action: (params, options, done) ->
|
||||
child_process = require('child_process')
|
||||
Promise = require 'bluebird'
|
||||
_ = require('lodash')
|
||||
{ forms } = require('resin-sync')
|
||||
{ selectContainerFromDevice, getSubShellCommand } = require('./common')
|
||||
|
||||
if (options.host is true and options.container?)
|
||||
throw new Error('Please pass either --host or --container option')
|
||||
|
||||
if not options.port?
|
||||
options.port = 22222
|
||||
|
||||
verbose = if options.verbose then '-vvv' else ''
|
||||
|
||||
Promise.try ->
|
||||
if not params.deviceIp?
|
||||
return forms.selectLocalResinOsDevice()
|
||||
return params.deviceIp
|
||||
.then (deviceIp) ->
|
||||
_.assign(options, { deviceIp })
|
||||
|
||||
return if options.host
|
||||
|
||||
if not options.container?
|
||||
return selectContainerFromDevice(deviceIp)
|
||||
|
||||
return options.container
|
||||
.then (container) ->
|
||||
|
||||
command = "ssh \
|
||||
#{verbose} \
|
||||
-t \
|
||||
-p #{options.port} \
|
||||
-o LogLevel=ERROR \
|
||||
-o StrictHostKeyChecking=no \
|
||||
-o UserKnownHostsFile=/dev/null \
|
||||
root@#{options.deviceIp}"
|
||||
|
||||
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}"
|
||||
|
||||
subShellCommand = getSubShellCommand(command)
|
||||
child_process.spawn subShellCommand.program, subShellCommand.args,
|
||||
stdio: 'inherit'
|
||||
.nodeify(done)
|
79
lib/actions/local/stop.coffee
Normal file
79
lib/actions/local/stop.coffee
Normal file
@ -0,0 +1,79 @@
|
||||
###
|
||||
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.
|
||||
###
|
||||
|
||||
# A function to reliably execute a command
|
||||
# in all supported operating systems, including
|
||||
# different Windows environments like `cmd.exe`
|
||||
# and `Cygwin` should be encapsulated in a
|
||||
# re-usable package.
|
||||
#
|
||||
module.exports =
|
||||
signature: 'local stop [deviceIp]'
|
||||
description: 'Stop a running container on a resinOS 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
|
||||
'''
|
||||
options: [
|
||||
signature: 'all'
|
||||
boolean: true
|
||||
description: 'stop all containers'
|
||||
,
|
||||
signature: 'app-name'
|
||||
parameter: 'name'
|
||||
description: 'name of container to stop'
|
||||
alias: 'a'
|
||||
]
|
||||
root: true
|
||||
action: (params, options, done) ->
|
||||
Promise = require('bluebird')
|
||||
chalk = require('chalk')
|
||||
{ forms, config, ResinLocalDockerUtils } = require('resin-sync')
|
||||
{ selectContainerFromDevice, filterOutSupervisorContainer } = require('./common')
|
||||
|
||||
Promise.try ->
|
||||
if not params.deviceIp?
|
||||
return forms.selectLocalResinOsDevice()
|
||||
return params.deviceIp
|
||||
.then (@deviceIp) =>
|
||||
@docker = new ResinLocalDockerUtils(@deviceIp)
|
||||
|
||||
if options.all
|
||||
# Only list running containers
|
||||
return @docker.docker.listContainersAsync(all: false)
|
||||
.filter(filterOutSupervisorContainer)
|
||||
.then (containers) =>
|
||||
Promise.map containers, ({ Names, Id }) =>
|
||||
console.log(chalk.yellow.bold("* Stopping container #{Names[0]}"))
|
||||
@docker.stopContainer(Id)
|
||||
|
||||
ymlConfig = config.load()
|
||||
@appName = options['app-name'] ? ymlConfig['local_resinos']?['app-name']
|
||||
@docker.checkForRunningContainer(@appName)
|
||||
.then (isRunning) =>
|
||||
if not isRunning
|
||||
return selectContainerFromDevice(@deviceIp, true)
|
||||
|
||||
console.log(chalk.yellow.bold("* Stopping container #{@appName}"))
|
||||
return @appName
|
||||
.then (runningContainerName) =>
|
||||
@docker.stopContainer(runningContainerName)
|
@ -1,5 +1,5 @@
|
||||
###
|
||||
Copyright 2016 Resin.io
|
||||
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.
|
||||
@ -45,7 +45,7 @@ module.exports =
|
||||
primary: true
|
||||
action: (params, options, done) ->
|
||||
_ = require('lodash')
|
||||
resin = require('resin-sdk')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
moment = require('moment')
|
||||
|
||||
printLine = (line) ->
|
||||
|
@ -1,5 +1,5 @@
|
||||
###
|
||||
Copyright 2016 Resin.io
|
||||
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.
|
||||
@ -40,7 +40,7 @@ exports.set =
|
||||
action: (params, options, done) ->
|
||||
Promise = require('bluebird')
|
||||
_ = require('lodash')
|
||||
resin = require('resin-sdk')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
|
||||
Promise.try ->
|
||||
if _.isEmpty(params.note)
|
||||
|
@ -1,5 +1,5 @@
|
||||
###
|
||||
Copyright 2016 Resin.io
|
||||
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.
|
||||
@ -15,26 +15,90 @@ limitations under the License.
|
||||
###
|
||||
|
||||
commandOptions = require('./command-options')
|
||||
_ = require('lodash')
|
||||
|
||||
formatVersion = (v, isRecommended) ->
|
||||
result = "v#{v}"
|
||||
if isRecommended
|
||||
result += ' (recommended)'
|
||||
return result
|
||||
|
||||
resolveVersion = (deviceType, version) ->
|
||||
if version isnt 'menu'
|
||||
if version[0] == 'v'
|
||||
version = version.slice(1)
|
||||
return Promise.resolve(version)
|
||||
|
||||
form = require('resin-cli-form')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
|
||||
resin.models.os.getSupportedVersions(deviceType)
|
||||
.then ({ versions, recommended }) ->
|
||||
choices = versions.map (v) ->
|
||||
value: v
|
||||
name: formatVersion(v, v is recommended)
|
||||
|
||||
return form.ask
|
||||
message: 'Select the OS version:'
|
||||
type: 'list'
|
||||
choices: choices
|
||||
default: recommended
|
||||
|
||||
exports.versions =
|
||||
signature: 'os versions <type>'
|
||||
description: 'show the available resinOS 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`
|
||||
|
||||
Example:
|
||||
|
||||
$ resin os versions raspberrypi3
|
||||
'''
|
||||
action: (params, options, done) ->
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
|
||||
resin.models.os.getSupportedVersions(params.type)
|
||||
.then ({ versions, recommended }) ->
|
||||
versions.forEach (v) ->
|
||||
console.log(formatVersion(v, v is recommended))
|
||||
|
||||
exports.download =
|
||||
signature: 'os download <type>'
|
||||
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`
|
||||
|
||||
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
|
||||
versions for the given device type are pre-release).
|
||||
|
||||
You can pass `--version menu` to pick the OS version from the interactive menu
|
||||
of all available versions.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin os download parallella -o ../foo/bar/parallella.img
|
||||
$ 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
|
||||
'''
|
||||
permission: 'user'
|
||||
options: [
|
||||
signature: 'output'
|
||||
description: 'output path'
|
||||
parameter: 'output'
|
||||
alias: 'o'
|
||||
required: 'You have to specify an output location'
|
||||
{
|
||||
signature: 'output'
|
||||
description: 'output path'
|
||||
parameter: 'output'
|
||||
alias: 'o'
|
||||
required: 'You have to specify the output location'
|
||||
}
|
||||
commandOptions.osVersion
|
||||
]
|
||||
action: (params, options, done) ->
|
||||
Promise = require('bluebird')
|
||||
unzip = require('unzip2')
|
||||
fs = require('fs')
|
||||
rindle = require('rindle')
|
||||
@ -43,9 +107,22 @@ exports.download =
|
||||
|
||||
console.info("Getting device operating system for #{params.type}")
|
||||
|
||||
manager.get(params.type).then (stream) ->
|
||||
bar = new visuals.Progress('Downloading Device OS')
|
||||
spinner = new visuals.Spinner('Downloading Device OS (size unknown)')
|
||||
displayVersion = ''
|
||||
Promise.try ->
|
||||
if not options.version
|
||||
console.warn('OS version is not specified, using the default version:
|
||||
the newest stable (non-pre-release) version if available,
|
||||
or the newest version otherwise (if all existing
|
||||
versions for the given device type are pre-release).')
|
||||
return 'default'
|
||||
return resolveVersion(params.type, options.version)
|
||||
.then (version) ->
|
||||
if version isnt 'default'
|
||||
displayVersion = " #{version}"
|
||||
return manager.get(params.type, version)
|
||||
.then (stream) ->
|
||||
bar = new visuals.Progress("Downloading Device OS#{displayVersion}")
|
||||
spinner = new visuals.Spinner("Downloading Device OS#{displayVersion} (size unknown)")
|
||||
|
||||
stream.on 'progress', (state) ->
|
||||
if state?
|
||||
@ -69,107 +146,186 @@ exports.download =
|
||||
console.info('The image was downloaded successfully')
|
||||
.nodeify(done)
|
||||
|
||||
stepHandler = (step) ->
|
||||
_ = require('lodash')
|
||||
rindle = require('rindle')
|
||||
visuals = require('resin-cli-visuals')
|
||||
buildConfig = (image, deviceType, advanced = false) ->
|
||||
form = require('resin-cli-form')
|
||||
helpers = require('../utils/helpers')
|
||||
|
||||
step.on('stdout', _.bind(process.stdout.write, process.stdout))
|
||||
step.on('stderr', _.bind(process.stderr.write, process.stderr))
|
||||
helpers.getManifest(image, deviceType)
|
||||
.get('options')
|
||||
.then (questions) ->
|
||||
if not advanced
|
||||
advancedGroup = _.find questions,
|
||||
name: 'advanced'
|
||||
isGroup: true
|
||||
|
||||
step.on 'state', (state) ->
|
||||
return if state.operation.command is 'burn'
|
||||
console.log(helpers.stateToString(state))
|
||||
if advancedGroup?
|
||||
override = helpers.getGroupDefaults(advancedGroup)
|
||||
|
||||
bar = new visuals.Progress('Writing Device OS')
|
||||
return form.run(questions, { override })
|
||||
|
||||
step.on('burn', _.bind(bar.update, bar))
|
||||
|
||||
return rindle.wait(step)
|
||||
|
||||
exports.configure =
|
||||
signature: 'os configure <image> <uuid>'
|
||||
description: 'configure an os image'
|
||||
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 configure a previously download operating system image with a device.
|
||||
Use this command to prebuild the OS config once and skip the interactive part of `resin os configure`.
|
||||
|
||||
Examples:
|
||||
Example:
|
||||
|
||||
$ resin os configure ../path/rpi.img 7cf02a6
|
||||
$ resin os build-config ../path/rpi3.img raspberrypi3 --output rpi3-config.json
|
||||
$ resin os configure ../path/rpi3.img 7cf02a6 --config "$(cat rpi3-config.json)"
|
||||
'''
|
||||
permission: 'user'
|
||||
options: [
|
||||
signature: 'advanced'
|
||||
description: 'show advanced commands'
|
||||
boolean: true
|
||||
alias: 'v'
|
||||
commandOptions.advancedConfig
|
||||
{
|
||||
signature: 'output'
|
||||
description: 'the path to the output JSON file'
|
||||
alias: 'o'
|
||||
required: 'the output path is required'
|
||||
parameter: 'output'
|
||||
}
|
||||
]
|
||||
action: (params, options, done) ->
|
||||
_ = require('lodash')
|
||||
resin = require('resin-sdk')
|
||||
form = require('resin-cli-form')
|
||||
fs = require('fs')
|
||||
Promise = require('bluebird')
|
||||
writeFileAsync = Promise.promisify(fs.writeFile)
|
||||
|
||||
buildConfig(params.image, params['device-type'], options.advanced)
|
||||
.then (answers) ->
|
||||
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')
|
||||
resin.models.device.get(params.uuid)
|
||||
.get('device_type')
|
||||
.then(resin.models.device.getManifestBySlug)
|
||||
.get('options')
|
||||
.then (questions) ->
|
||||
|
||||
if not options.advanced
|
||||
advancedGroup = _.findWhere questions,
|
||||
name: 'advanced'
|
||||
isGroup: true
|
||||
configurationResourceType = if uuid then 'device' else 'application'
|
||||
|
||||
if advancedGroup?
|
||||
override = helpers.getGroupDefaults(advancedGroup)
|
||||
|
||||
return form.run(questions, { override })
|
||||
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) ->
|
||||
init.configure(params.image, params.uuid, answers).then(stepHandler)
|
||||
(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.
|
||||
'''
|
||||
|
||||
exports.initialize =
|
||||
signature: 'os initialize <image>'
|
||||
description: 'initialize an os image'
|
||||
help: '''
|
||||
Use this command to initialize a previously configured operating system image.
|
||||
help: """
|
||||
Use this command to initialize a device with previously configured operating system image.
|
||||
|
||||
#{INIT_WARNING_MESSAGE}
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin os initialize ../path/rpi.img --type 'raspberry-pi'
|
||||
'''
|
||||
"""
|
||||
permission: 'user'
|
||||
options: [
|
||||
commandOptions.yes
|
||||
{
|
||||
signature: 'type'
|
||||
description: 'device type'
|
||||
description: 'device type (Check available types with `resin devices supported`)'
|
||||
parameter: 'type'
|
||||
alias: 't'
|
||||
required: 'You have to specify a device type'
|
||||
}
|
||||
{
|
||||
signature: 'drive'
|
||||
description: 'drive'
|
||||
parameter: 'drive'
|
||||
alias: 'd'
|
||||
}
|
||||
commandOptions.drive
|
||||
]
|
||||
root: true
|
||||
action: (params, options, done) ->
|
||||
Promise = require('bluebird')
|
||||
umount = Promise.promisifyAll(require('umount'))
|
||||
resin = require('resin-sdk')
|
||||
umountAsync = Promise.promisify(require('umount').umount)
|
||||
form = require('resin-cli-form')
|
||||
init = require('resin-device-init')
|
||||
patterns = require('../utils/patterns')
|
||||
helpers = require('../utils/helpers')
|
||||
|
||||
console.info('Initializing device')
|
||||
resin.models.device.getManifestBySlug(options.type)
|
||||
console.info("""
|
||||
Initializing device
|
||||
|
||||
#{INIT_WARNING_MESSAGE}
|
||||
""")
|
||||
helpers.getManifest(params.image, options.type)
|
||||
.then (manifest) ->
|
||||
return manifest.initialization?.options
|
||||
.then (questions) ->
|
||||
@ -178,14 +334,39 @@ exports.initialize =
|
||||
drive: options.drive
|
||||
.tap (answers) ->
|
||||
return if not answers.drive?
|
||||
message = "This will erase #{answers.drive}. Are you sure?"
|
||||
patterns.confirm(options.yes, message)
|
||||
patterns.confirm(
|
||||
options.yes
|
||||
"This will erase #{answers.drive}. Are you sure?"
|
||||
"Going to erase #{answers.drive}."
|
||||
)
|
||||
.return(answers.drive)
|
||||
.then(umount.umountAsync)
|
||||
.then(umountAsync)
|
||||
.tap (answers) ->
|
||||
return init.initialize(params.image, options.type, answers).then(stepHandler)
|
||||
return helpers.sudo([
|
||||
'internal'
|
||||
'osinit'
|
||||
params.image
|
||||
options.type
|
||||
JSON.stringify(answers)
|
||||
])
|
||||
.then (answers) ->
|
||||
return if not answers.drive?
|
||||
umount.umountAsync(answers.drive).tap ->
|
||||
|
||||
# TODO: resin local makes use of ejectAsync, see below
|
||||
# DO we need this / should we do that here?
|
||||
|
||||
# getDrive = (drive) ->
|
||||
# driveListAsync().then (drives) ->
|
||||
# selectedDrive = _.find(drives, device: drive)
|
||||
|
||||
# if not selectedDrive?
|
||||
# throw new Error("Drive not found: #{drive}")
|
||||
|
||||
# return selectedDrive
|
||||
# if (os.platform() is 'win32') and selectedDrive.mountpoint?
|
||||
# ejectAsync = Promise.promisify(require('removedrive').eject)
|
||||
# return ejectAsync(selectedDrive.mountpoint)
|
||||
|
||||
umountAsync(answers.drive).tap ->
|
||||
console.info("You can safely remove #{answers.drive} now")
|
||||
.nodeify(done)
|
||||
|
276
lib/actions/preload.coffee
Normal file
276
lib/actions/preload.coffee
Normal file
@ -0,0 +1,276 @@
|
||||
###
|
||||
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.
|
||||
###
|
||||
|
||||
dockerUtils = require('../utils/docker')
|
||||
|
||||
LATEST = 'latest'
|
||||
|
||||
getApplicationsWithSuccessfulBuilds = (deviceType) ->
|
||||
preload = require('resin-preload')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
|
||||
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'
|
||||
|
||||
selectApplication = (deviceType) ->
|
||||
visuals = require('resin-cli-visuals')
|
||||
form = require('resin-cli-form')
|
||||
{ expectedError } = require('../utils/patterns')
|
||||
|
||||
applicationInfoSpinner = new visuals.Spinner('Downloading list of applications and builds.')
|
||||
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.")
|
||||
form.ask
|
||||
message: 'Select an application'
|
||||
type: 'list'
|
||||
choices: applications.map (app) ->
|
||||
name: app.app_name
|
||||
value: app
|
||||
|
||||
selectApplicationCommit = (builds) ->
|
||||
form = require('resin-cli-form')
|
||||
{ expectedError } = 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
|
||||
return form.ask
|
||||
message: 'Select a build'
|
||||
type: 'list'
|
||||
default: LATEST
|
||||
choices: choices
|
||||
|
||||
offerToDisableAutomaticUpdates = (application, commit) ->
|
||||
Promise = require('bluebird')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
form = require('resin-cli-form')
|
||||
|
||||
if commit == LATEST or not application.should_track_latest_release
|
||||
return Promise.resolve()
|
||||
message = '''
|
||||
|
||||
This application is set to automatically update all devices to the latest available version.
|
||||
This might be unexpected behaviour: with this enabled, the preloaded device will still
|
||||
download and install the latest build once it is online.
|
||||
|
||||
Do you want to disable automatic updates for this application?
|
||||
'''
|
||||
form.ask
|
||||
message: message,
|
||||
type: 'confirm'
|
||||
.then (update) ->
|
||||
if not update
|
||||
return
|
||||
resin.pine.patch
|
||||
resource: 'application'
|
||||
id: application.id
|
||||
body:
|
||||
should_track_latest_release: false
|
||||
|
||||
module.exports =
|
||||
signature: 'preload <image>'
|
||||
description: '(beta) 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 .
|
||||
|
||||
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.
|
||||
|
||||
Examples:
|
||||
$ resin preload resin.img --app 1234 --commit e1f2592fc6ee949e68756d4f4a48e49bff8d72a0 --splash-image some-image.png
|
||||
$ resin preload resin.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'
|
||||
}
|
||||
]
|
||||
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')
|
||||
visuals = require('resin-cli-visuals')
|
||||
nodeCleanup = require('node-cleanup')
|
||||
{ expectedError } = require('../utils/patterns')
|
||||
|
||||
progressBars = {}
|
||||
|
||||
progressHandler = (event) ->
|
||||
progressBar = progressBars[event.name]
|
||||
if not progressBar
|
||||
progressBar = progressBars[event.name] = new visuals.Progress(event.name)
|
||||
progressBar.update(percentage: event.percentage)
|
||||
|
||||
spinners = {}
|
||||
|
||||
spinnerHandler = (event) ->
|
||||
spinner = spinners[event.name]
|
||||
if not spinner
|
||||
spinner = spinners[event.name] = new visuals.Spinner(event.name)
|
||||
if event.action == 'start'
|
||||
spinner.start()
|
||||
else
|
||||
console.log()
|
||||
spinner.stop()
|
||||
|
||||
options.image = params.image
|
||||
options.appId = options.app
|
||||
delete options.app
|
||||
|
||||
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.')
|
||||
|
||||
# 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,
|
||||
)
|
||||
|
||||
gotSignal = false
|
||||
|
||||
nodeCleanup (exitCode, signal) ->
|
||||
if signal
|
||||
gotSignal = true
|
||||
nodeCleanup.uninstall() # don't call cleanup handler again
|
||||
preloader.cleanup()
|
||||
.then ->
|
||||
# calling process.exit() won't inform parent process of signal
|
||||
process.kill(process.pid, signal)
|
||||
return false
|
||||
|
||||
if process.env.DEBUG
|
||||
preloader.stderr.pipe(process.stderr)
|
||||
|
||||
preloader.on('progress', progressHandler)
|
||||
preloader.on('spinner', spinnerHandler)
|
||||
|
||||
return new Promise (resolve, reject) ->
|
||||
preloader.on('error', reject)
|
||||
|
||||
preloader.build()
|
||||
.then ->
|
||||
preloader.prepare()
|
||||
.then ->
|
||||
preloader.getDeviceTypeAndPreloadedBuilds()
|
||||
.then (info) ->
|
||||
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"
|
||||
)
|
||||
|
||||
# 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)
|
||||
.then(resolve)
|
||||
.catch(reject)
|
||||
.then(done)
|
||||
.finally ->
|
||||
if not gotSignal
|
||||
preloader.cleanup()
|
@ -1,5 +1,5 @@
|
||||
###
|
||||
Copyright 2016 Resin.io
|
||||
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.
|
||||
@ -25,7 +25,7 @@ exports.list =
|
||||
$ resin settings
|
||||
'''
|
||||
action: (params, options, done) ->
|
||||
resin = require('resin-sdk')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
prettyjson = require('prettyjson')
|
||||
|
||||
resin.settings.getAll()
|
||||
|
130
lib/actions/ssh.coffee
Normal file
130
lib/actions/ssh.coffee
Normal file
@ -0,0 +1,130 @@
|
||||
###
|
||||
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)
|
@ -1,6 +1,5 @@
|
||||
|
||||
/*
|
||||
Copyright 2016 Resin.io
|
||||
###
|
||||
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.
|
||||
@ -13,23 +12,6 @@ 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.
|
||||
*/
|
||||
###
|
||||
|
||||
(function() {
|
||||
module.exports = {
|
||||
wizard: require('./wizard'),
|
||||
app: require('./app'),
|
||||
info: require('./info'),
|
||||
auth: require('./auth'),
|
||||
device: require('./device'),
|
||||
env: require('./environment-variables'),
|
||||
keys: require('./keys'),
|
||||
logs: require('./logs'),
|
||||
notes: require('./notes'),
|
||||
help: require('./help'),
|
||||
os: require('./os'),
|
||||
settings: require('./settings'),
|
||||
config: require('./config')
|
||||
};
|
||||
|
||||
}).call(this);
|
||||
module.exports = require('resin-sync').capitano('resin-cli')
|
56
lib/actions/util.coffee
Normal file
56
lib/actions/util.coffee
Normal file
@ -0,0 +1,56 @@
|
||||
###
|
||||
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'
|
||||
]
|
@ -1,5 +1,5 @@
|
||||
###
|
||||
Copyright 2016 Resin.io
|
||||
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.
|
||||
@ -35,28 +35,28 @@ exports.wizard =
|
||||
primary: true
|
||||
action: (params, options, done) ->
|
||||
Promise = require('bluebird')
|
||||
capitano = Promise.promisifyAll(require('capitano'))
|
||||
resin = require('resin-sdk')
|
||||
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 capitano.runAsync('login')
|
||||
return capitanoRunAsync('login')
|
||||
.then ->
|
||||
return if params.name?
|
||||
patterns.selectOrCreateApplication().tap (applicationName) ->
|
||||
resin.models.application.has(applicationName).then (hasApplication) ->
|
||||
return applicationName if hasApplication
|
||||
capitano.runAsync("app create #{applicationName}")
|
||||
capitanoRunAsync("app create #{applicationName}")
|
||||
.then (applicationName) ->
|
||||
params.name = applicationName
|
||||
.then ->
|
||||
return capitano.runAsync("device init --application #{params.name}")
|
||||
return capitanoRunAsync("device init --application #{params.name}")
|
||||
.tap(patterns.awaitDevice)
|
||||
.then (uuid) ->
|
||||
return capitano.runAsync("device #{uuid}")
|
||||
return capitanoRunAsync("device #{uuid}")
|
||||
.then ->
|
||||
return resin.models.application.get(params.name)
|
||||
.then (application) ->
|
||||
|
117
lib/app.coffee
117
lib/app.coffee
@ -1,5 +1,5 @@
|
||||
###
|
||||
Copyright 2016 Resin.io
|
||||
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.
|
||||
@ -14,16 +14,76 @@ 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 = Promise.promisifyAll(require('capitano'))
|
||||
resin = require('resin-sdk')
|
||||
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
|
||||
@ -41,6 +101,11 @@ capitano.command
|
||||
action: ->
|
||||
capitano.execute(command: 'help')
|
||||
|
||||
capitano.globalOption
|
||||
signature: 'help'
|
||||
boolean: true
|
||||
alias: 'h'
|
||||
|
||||
# ---------- Info Module ----------
|
||||
capitano.command(actions.info.version)
|
||||
|
||||
@ -65,11 +130,17 @@ 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)
|
||||
@ -90,13 +161,16 @@ 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)
|
||||
|
||||
@ -106,12 +180,45 @@ 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)
|
||||
|
||||
events.trackCommand(cli).then ->
|
||||
capitano.executeAsync(cli)
|
||||
runCommand = ->
|
||||
if cli.global?.help
|
||||
capitanoExecuteAsync(command: "help #{cli.command ? ''}")
|
||||
else
|
||||
capitanoExecuteAsync(cli)
|
||||
|
||||
Promise.all([events.trackCommand(cli), runCommand()])
|
||||
|
||||
.catch(errors.handle)
|
||||
|
63
lib/auth/index.coffee
Normal file
63
lib/auth/index.coffee
Normal file
@ -0,0 +1,63 @@
|
||||
###
|
||||
Copyright 2016 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 auth
|
||||
###
|
||||
|
||||
open = require('opn')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
server = require('./server')
|
||||
utils = require('./utils')
|
||||
|
||||
###*
|
||||
# @summary Login to the Resin 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
|
||||
# take place.
|
||||
#
|
||||
# Once the the token is retrieved, it's automatically persisted.
|
||||
#
|
||||
# @fulfil {String} - session token
|
||||
# @returns {Promise}
|
||||
#
|
||||
# @example
|
||||
# auth.login().then (sessionToken) ->
|
||||
# console.log('I\'m logged in!')
|
||||
# console.log("My session token is: #{sessionToken}")
|
||||
###
|
||||
exports.login = ->
|
||||
options =
|
||||
port: 8989
|
||||
path: '/auth'
|
||||
|
||||
# Needs to be 127.0.0.1 not localhost, because the ip only is whitelisted
|
||||
# from mixed content warnings (as the target of a form in the result page)
|
||||
callbackUrl = "http://127.0.0.1:#{options.port}#{options.path}"
|
||||
return utils.getDashboardLoginURL(callbackUrl).then (loginUrl) ->
|
||||
|
||||
# Leave a bit of time for the
|
||||
# server to get up and runing
|
||||
setTimeout ->
|
||||
open(loginUrl)
|
||||
, 1000
|
||||
|
||||
return server.awaitForToken(options)
|
||||
.tap(resin.auth.loginWithToken)
|
21
lib/auth/pages/error.ejs
Normal file
21
lib/auth/pages/error.ejs
Normal file
@ -0,0 +1,21 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||
<title>Resin 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>
|
||||
</head>
|
||||
<body>
|
||||
<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>
|
||||
<br>
|
||||
<br>
|
||||
<a href="https://forums.resin.io/" class="button danger">Get help in our forums</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
BIN
lib/auth/pages/static/images/happy.png
Normal file
BIN
lib/auth/pages/static/images/happy.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.3 KiB |
BIN
lib/auth/pages/static/images/sad.png
Normal file
BIN
lib/auth/pages/static/images/sad.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.6 KiB |
60
lib/auth/pages/static/style.css
Normal file
60
lib/auth/pages/static/style.css
Normal file
@ -0,0 +1,60 @@
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
text-align: center;
|
||||
background-color: #fff;
|
||||
color: rgb(24, 24, 24);
|
||||
font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.center {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: auto;
|
||||
width: 50%;
|
||||
height: 50%;
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: block;
|
||||
width: 40px;
|
||||
height: 45px;
|
||||
margin: 0 auto;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3rem;
|
||||
margin: 0;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
p {
|
||||
color: rgb(99, 99, 99);
|
||||
font-size: 1.1rem;
|
||||
margin: 0;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
a.button {
|
||||
padding: 15px 25px;
|
||||
border-radius: 5px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a.button.danger {
|
||||
background-color: rgb(235, 110, 111);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
a.button.normal {
|
||||
background-color: rgb(252, 191, 44);
|
||||
color: #fff;
|
||||
}
|
21
lib/auth/pages/success.ejs
Normal file
21
lib/auth/pages/success.ejs
Normal file
@ -0,0 +1,21 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||
<title>Resin 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>
|
||||
</head>
|
||||
<body>
|
||||
<div class="center">
|
||||
<img class="icon" src="./static/images/happy.png" inline>
|
||||
<h1>Success!</h1>
|
||||
<p>You successfully logged in the Resin CLI</p>
|
||||
<br>
|
||||
<br>
|
||||
<a href="<%= dashboardUrl %>" class="button normal">Go to the dashboard</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
101
lib/auth/server.coffee
Normal file
101
lib/auth/server.coffee
Normal file
@ -0,0 +1,101 @@
|
||||
###
|
||||
Copyright 2016 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.
|
||||
###
|
||||
|
||||
express = require('express')
|
||||
path = require('path')
|
||||
bodyParser = require('body-parser')
|
||||
Promise = require('bluebird')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
utils = require('./utils')
|
||||
|
||||
createServer = ({ port, isDev } = {}) ->
|
||||
app = express()
|
||||
app.use bodyParser.urlencoded
|
||||
extended: true
|
||||
|
||||
app.set('view engine', 'ejs')
|
||||
app.set('views', path.join(__dirname, 'pages'))
|
||||
|
||||
if isDev
|
||||
app.use(express.static(path.join(__dirname, 'pages', 'static')))
|
||||
|
||||
server = app.listen(port)
|
||||
|
||||
return { app, server }
|
||||
|
||||
###*
|
||||
# @summary Await for token
|
||||
# @function
|
||||
# @protected
|
||||
#
|
||||
# @param {Object} options - options
|
||||
# @param {String} options.path - callback path
|
||||
# @param {Number} options.port - http port
|
||||
#
|
||||
# @example
|
||||
# server.awaitForToken
|
||||
# path: '/auth'
|
||||
# port: 9001
|
||||
# .then (token) ->
|
||||
# console.log(token)
|
||||
###
|
||||
exports.awaitForToken = (options) ->
|
||||
{ app, server } = createServer(port: options.port)
|
||||
|
||||
return new Promise (resolve, reject) ->
|
||||
closeServer = (errorMessage, successPayload) ->
|
||||
server.close ->
|
||||
if errorMessage
|
||||
reject(new Error(errorMessage))
|
||||
return
|
||||
|
||||
resolve(successPayload)
|
||||
|
||||
renderAndDone = ({ request, response, viewName, errorMessage, statusCode, token }) ->
|
||||
return getContext(viewName)
|
||||
.then (context) ->
|
||||
response.status(statusCode || 200).render(viewName, context)
|
||||
request.connection.destroy()
|
||||
closeServer(errorMessage, token)
|
||||
|
||||
app.post options.path, (request, response) ->
|
||||
token = request.body.token?.trim()
|
||||
|
||||
Promise.try ->
|
||||
if not token
|
||||
throw new Error('No token')
|
||||
return utils.isTokenValid(token)
|
||||
.tap (isValid) ->
|
||||
if not isValid
|
||||
throw new Error('Invalid token')
|
||||
.then ->
|
||||
renderAndDone({ request, response, viewName: 'success', token })
|
||||
.catch (error) ->
|
||||
renderAndDone({
|
||||
request, response, viewName: 'error',
|
||||
statusCode: 401, errorMessage: error.message
|
||||
})
|
||||
|
||||
app.use (request, response) ->
|
||||
response.status(404).send('Not found')
|
||||
closeServer('Unknown path or verb')
|
||||
|
||||
exports.getContext = getContext = (viewName) ->
|
||||
if viewName is 'success'
|
||||
return Promise.props
|
||||
dashboardUrl: resin.settings.get('dashboardUrl')
|
||||
|
||||
return Promise.resolve({})
|
75
lib/auth/utils.coffee
Normal file
75
lib/auth/utils.coffee
Normal file
@ -0,0 +1,75 @@
|
||||
###
|
||||
Copyright 2016 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.
|
||||
###
|
||||
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
_ = require('lodash')
|
||||
url = require('url')
|
||||
Promise = require('bluebird')
|
||||
|
||||
###*
|
||||
# @summary Get dashboard CLI login URL
|
||||
# @function
|
||||
# @protected
|
||||
#
|
||||
# @param {String} callbackUrl - callback url
|
||||
# @fulfil {String} - dashboard login url
|
||||
# @returns {Promise}
|
||||
#
|
||||
# @example
|
||||
# utils.getDashboardLoginURL('http://127.0.0.1:3000').then (url) ->
|
||||
# console.log(url)
|
||||
###
|
||||
exports.getDashboardLoginURL = (callbackUrl) ->
|
||||
|
||||
# Encode percentages signs from the escaped url
|
||||
# characters to avoid angular getting confused.
|
||||
callbackUrl = encodeURIComponent(callbackUrl).replace(/%/g, '%25')
|
||||
|
||||
resin.settings.get('dashboardUrl').then (dashboardUrl) ->
|
||||
return url.resolve(dashboardUrl, "/login/cli/#{callbackUrl}")
|
||||
|
||||
###*
|
||||
# @summary Check if a token is valid
|
||||
# @function
|
||||
# @protected
|
||||
#
|
||||
# @description
|
||||
# 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
|
||||
# @returns {Promise}
|
||||
#
|
||||
# utils.isTokenValid('...').then (isValid) ->
|
||||
# if isValid
|
||||
# console.log('Token is valid!')
|
||||
###
|
||||
exports.isTokenValid = (sessionToken) ->
|
||||
if not sessionToken? or _.isEmpty(sessionToken.trim())
|
||||
return Promise.resolve(false)
|
||||
|
||||
return resin.token.get().then (currentToken) ->
|
||||
resin.auth.loginWithToken(sessionToken)
|
||||
.return(sessionToken)
|
||||
.then(resin.auth.isLoggedIn)
|
||||
.tap (isLoggedIn) ->
|
||||
return if isLoggedIn
|
||||
|
||||
if currentToken?
|
||||
return resin.auth.loginWithToken(currentToken)
|
||||
else
|
||||
return resin.auth.logout()
|
1
lib/config.ts
Normal file
1
lib/config.ts
Normal file
@ -0,0 +1 @@
|
||||
exports.sentryDsn = 'https://56d2a46124614b01b0f4086897e96110:6e175465accc41b595a96947155f61fb@sentry.io/149239'
|
@ -1,5 +1,5 @@
|
||||
###
|
||||
Copyright 2016 Resin.io
|
||||
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.
|
||||
@ -17,6 +17,10 @@ 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)
|
||||
@ -26,4 +30,9 @@ exports.handle = (error) ->
|
||||
message = error.stack
|
||||
|
||||
patterns.printErrorMessage(message)
|
||||
process.exit(error.exitCode or 1)
|
||||
|
||||
captureException(error)
|
||||
.timeout(1000)
|
||||
.catch(-> # Ignore any errors (from error logging, or timeouts)
|
||||
).finally ->
|
||||
process.exit(error.exitCode or 1)
|
||||
|
@ -1,24 +1,34 @@
|
||||
_ = require('lodash')
|
||||
Mixpanel = require('mixpanel')
|
||||
Raven = require('raven')
|
||||
Promise = require('bluebird')
|
||||
resin = require('resin-sdk')
|
||||
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()
|
||||
username: resin.auth.whoami().catchReturn(undefined)
|
||||
mixpanel: exports.getLoggerInstance()
|
||||
.then (data) ->
|
||||
data.mixpanel.track "[CLI] #{capitanoCommand.command}",
|
||||
distinct_id: data.username
|
||||
argv: process.argv.join(' ')
|
||||
version: packageJSON.version
|
||||
node: process.version
|
||||
arch: process.arch
|
||||
resinUrl: data.resinUrl
|
||||
platform: process.platform
|
||||
command: capitanoCommand
|
||||
.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()
|
||||
|
97
lib/utils/config.coffee
Normal file
97
lib/utils/config.coffee
Normal file
@ -0,0 +1,97 @@
|
||||
###
|
||||
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.generateBaseConfig = (application, options) ->
|
||||
Promise = require('bluebird')
|
||||
_ = require('lodash')
|
||||
deviceConfig = require('resin-device-config')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
|
||||
options = _.mapValues options, (value, key) ->
|
||||
if key == 'appUpdatePollInterval'
|
||||
value * 60 * 1000
|
||||
else
|
||||
value
|
||||
|
||||
Promise.props
|
||||
userId: resin.auth.getUserId()
|
||||
username: resin.auth.whoami()
|
||||
apiUrl: resin.settings.get('apiUrl')
|
||||
vpnUrl: resin.settings.get('vpnUrl')
|
||||
registryUrl: resin.settings.get('registryUrl')
|
||||
deltaUrl: resin.settings.get('deltaUrl')
|
||||
pubNubKeys: resin.models.config.getPubNubKeys()
|
||||
mixpanelToken: resin.models.config.getMixpanelToken()
|
||||
.then (results) ->
|
||||
deviceConfig.generate
|
||||
application: application
|
||||
user:
|
||||
id: results.userId
|
||||
username: results.username
|
||||
endpoints:
|
||||
api: results.apiUrl
|
||||
vpn: results.vpnUrl
|
||||
registry: results.registryUrl
|
||||
delta: results.deltaUrl
|
||||
pubnub: results.pubNubKeys
|
||||
mixpanel:
|
||||
token: results.mixpanelToken
|
||||
, options
|
||||
|
||||
exports.generateApplicationConfig = (application, options) ->
|
||||
exports.generateBaseConfig(application, options)
|
||||
.tap (config) ->
|
||||
authenticateWithApplicationKey(config, application.id)
|
||||
|
||||
exports.generateDeviceConfig = (device, deviceApiKey, options) ->
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
|
||||
resin.models.application.get(device.application_name)
|
||||
.then (application) ->
|
||||
exports.generateBaseConfig(application, options)
|
||||
.tap (config) ->
|
||||
# Device API keys are only safe for ResinOS 2.0.3+. We could somehow obtain
|
||||
# the expected version for this config and generate one when we know it's safe,
|
||||
# but instead for now we fall back to app keys unless the user has explicitly opted in.
|
||||
if deviceApiKey?
|
||||
authenticateWithDeviceKey(config, device.uuid, deviceApiKey)
|
||||
else
|
||||
authenticateWithApplicationKey(config, application.id)
|
||||
.then (config) ->
|
||||
# Associate a device, to prevent the supervisor
|
||||
# from creating another one on its own.
|
||||
config.registered_at = Math.floor(Date.now() / 1000)
|
||||
config.deviceId = device.id
|
||||
config.uuid = device.uuid
|
||||
|
||||
return config
|
||||
|
||||
authenticateWithApplicationKey = (config, applicationNameOrId) ->
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
resin.models.application.generateApiKey(applicationNameOrId)
|
||||
.then (apiKey) ->
|
||||
config.apiKey = apiKey
|
||||
return config
|
||||
|
||||
authenticateWithDeviceKey = (config, uuid, customDeviceApiKey) ->
|
||||
Promise = require('bluebird')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
|
||||
Promise.try ->
|
||||
customDeviceApiKey || resin.models.device.generateDeviceKey(uuid)
|
||||
.then (deviceApiKey) ->
|
||||
config.deviceApiKey = deviceApiKey
|
||||
return config
|
420
lib/utils/docker.coffee
Normal file
420
lib/utils/docker.coffee
Normal file
@ -0,0 +1,420 @@
|
||||
# Functions to help actions which rely on using docker
|
||||
|
||||
QEMU_VERSION = 'v2.5.50-resin-execve'
|
||||
QEMU_BIN_NAME = 'qemu-execve'
|
||||
|
||||
# Use this function to seed an action's list of capitano options
|
||||
# with the docker options. Using this interface means that
|
||||
# all functions using docker will expose the same interface
|
||||
#
|
||||
# NOTE: Care MUST be taken when using the function, so as to
|
||||
# not redefine/override options already provided.
|
||||
exports.appendConnectionOptions = appendConnectionOptions = (opts) ->
|
||||
opts.concat [
|
||||
{
|
||||
signature: 'docker'
|
||||
parameter: 'docker'
|
||||
description: 'Path to a local docker socket'
|
||||
alias: 'P'
|
||||
},
|
||||
{
|
||||
signature: 'dockerHost'
|
||||
parameter: 'dockerHost'
|
||||
description: 'The address of the host containing the docker daemon'
|
||||
alias: 'h'
|
||||
},
|
||||
{
|
||||
signature: 'dockerPort'
|
||||
parameter: 'dockerPort'
|
||||
description: 'The port on which the host docker daemon is listening'
|
||||
alias: 'p'
|
||||
},
|
||||
{
|
||||
signature: 'ca'
|
||||
parameter: 'ca'
|
||||
description: 'Docker host TLS certificate authority file'
|
||||
},
|
||||
{
|
||||
signature: 'cert'
|
||||
parameter: 'cert'
|
||||
description: 'Docker host TLS certificate file'
|
||||
},
|
||||
{
|
||||
signature: 'key'
|
||||
parameter: 'key'
|
||||
description: 'Docker host TLS key file'
|
||||
},
|
||||
]
|
||||
|
||||
# Use this function to seed an action's list of capitano options
|
||||
# with the docker options. Using this interface means that
|
||||
# all functions using docker will expose the same interface
|
||||
#
|
||||
# NOTE: Care MUST be taken when using the function, so as to
|
||||
# not redefine/override options already provided.
|
||||
exports.appendOptions = (opts) ->
|
||||
appendConnectionOptions(opts).concat [
|
||||
{
|
||||
signature: 'tag'
|
||||
parameter: 'tag'
|
||||
description: 'The alias to the generated image'
|
||||
alias: 't'
|
||||
},
|
||||
{
|
||||
signature: 'buildArg'
|
||||
parameter: 'arg'
|
||||
description: 'Set a build-time variable (eg. "-B \'ARG=value\'"). Can be specified multiple times.'
|
||||
alias: 'B'
|
||||
},
|
||||
{
|
||||
signature: 'nocache'
|
||||
description: "Don't use docker layer caching when building"
|
||||
boolean: true
|
||||
},
|
||||
{
|
||||
signature: 'emulated'
|
||||
description: 'Run an emulated build using Qemu'
|
||||
boolean: true
|
||||
alias: 'e'
|
||||
},
|
||||
{
|
||||
signature: 'squash'
|
||||
description: 'Squash newly built layers into a single new layer'
|
||||
boolean: true
|
||||
}
|
||||
]
|
||||
|
||||
exports.generateConnectOpts = generateConnectOpts = (opts) ->
|
||||
Promise = require('bluebird')
|
||||
buildDockerodeOpts = require('dockerode-options')
|
||||
fs = require('mz/fs')
|
||||
_ = require('lodash')
|
||||
|
||||
Promise.try ->
|
||||
connectOpts = {}
|
||||
# Firsly need to decide between a local docker socket
|
||||
# and a host available over a host:port combo
|
||||
if opts.docker? and not opts.dockerHost?
|
||||
# good, local docker socket
|
||||
connectOpts.socketPath = opts.docker
|
||||
else if opts.dockerHost? and not opts.docker?
|
||||
# Good a host is provided, and local socket isn't
|
||||
connectOpts.host = opts.dockerHost
|
||||
connectOpts.port = opts.dockerPort || 2376
|
||||
else if opts.docker? and opts.dockerHost?
|
||||
# Both provided, no obvious way to continue
|
||||
throw new Error("Both a local docker socket and docker host have been provided. Don't know how to continue.")
|
||||
else if process.env.DOCKER_HOST
|
||||
# If no explicit options are provided, use the env
|
||||
connectOpts = buildDockerodeOpts(process.env.DOCKER_HOST)
|
||||
else
|
||||
# No options anywhere, assume default docker local socket
|
||||
connectOpts.socketPath = '/var/run/docker.sock'
|
||||
|
||||
# Now need to check if the user wants to connect over TLS
|
||||
# to the host
|
||||
|
||||
# If any are set...
|
||||
if (opts.ca? or opts.cert? or opts.key?)
|
||||
# but not all
|
||||
if not (opts.ca? and opts.cert? and opts.key?)
|
||||
throw new Error('You must provide a CA, certificate and key in order to use TLS')
|
||||
|
||||
certBodies = {
|
||||
ca: fs.readFile(opts.ca, 'utf-8')
|
||||
cert: fs.readFile(opts.cert, 'utf-8')
|
||||
key: fs.readFile(opts.key, 'utf-8')
|
||||
}
|
||||
return Promise.props(certBodies)
|
||||
.then (toMerge) ->
|
||||
_.merge(connectOpts, toMerge)
|
||||
|
||||
return connectOpts
|
||||
|
||||
exports.tarDirectory = tarDirectory = (dir) ->
|
||||
Promise = require('bluebird')
|
||||
tar = require('tar-stream')
|
||||
klaw = require('klaw')
|
||||
path = require('path')
|
||||
fs = require('mz/fs')
|
||||
streamToPromise = require('stream-to-promise')
|
||||
|
||||
getFiles = ->
|
||||
streamToPromise(klaw(dir))
|
||||
.filter((item) -> not item.stats.isDirectory())
|
||||
.map((item) -> item.path)
|
||||
|
||||
pack = tar.pack()
|
||||
getFiles(dir)
|
||||
.map (file) ->
|
||||
relPath = path.relative(path.resolve(dir), file)
|
||||
Promise.join relPath, fs.stat(file), fs.readFile(file),
|
||||
(filename, stats, data) ->
|
||||
pack.entryAsync({ name: filename, size: stats.size, mode: stats.mode }, data)
|
||||
.then ->
|
||||
pack.finalize()
|
||||
return pack
|
||||
|
||||
cacheHighlightStream = ->
|
||||
colors = require('colors/safe')
|
||||
es = require('event-stream')
|
||||
{ EOL } = require('os')
|
||||
|
||||
extractArrowMessage = (message) ->
|
||||
arrowTest = /^\s*-+>\s*(.+)/i
|
||||
if (match = arrowTest.exec(message))
|
||||
match[1]
|
||||
else
|
||||
undefined
|
||||
|
||||
es.mapSync (data) ->
|
||||
msg = extractArrowMessage(data)
|
||||
if msg? and msg.toLowerCase() == 'using cache'
|
||||
data = colors.bgGreen.black(msg)
|
||||
return data + EOL
|
||||
|
||||
parseBuildArgs = (args, onError) ->
|
||||
_ = require('lodash')
|
||||
if not _.isArray(args)
|
||||
args = [ args ]
|
||||
buildArgs = {}
|
||||
args.forEach (str) ->
|
||||
pair = /^([^\s]+?)=(.*)$/.exec(str)
|
||||
if pair?
|
||||
buildArgs[pair[1]] = pair[2]
|
||||
else
|
||||
onError(str)
|
||||
return buildArgs
|
||||
|
||||
# Pass in the command line parameters and options and also
|
||||
# a function which will return the information about the bundle
|
||||
exports.runBuild = (params, options, getBundleInfo, logger) ->
|
||||
Promise = require('bluebird')
|
||||
dockerBuild = require('resin-docker-build')
|
||||
resolver = require('resin-bundle-resolve')
|
||||
es = require('event-stream')
|
||||
doodles = require('resin-doodles')
|
||||
transpose = require('docker-qemu-transpose')
|
||||
path = require('path')
|
||||
|
||||
# The default build context is the current directory
|
||||
params.source ?= '.'
|
||||
logs = ''
|
||||
# Only used in emulated builds
|
||||
qemuPath = ''
|
||||
|
||||
Promise.try ->
|
||||
return if not (options.emulated and platformNeedsQemu())
|
||||
|
||||
hasQemu()
|
||||
.then (present) ->
|
||||
if !present
|
||||
logger.logInfo('Installing qemu for ARM emulation...')
|
||||
installQemu()
|
||||
.then ->
|
||||
# Copy the qemu binary into the build context
|
||||
copyQemu(params.source)
|
||||
.then (binPath) ->
|
||||
qemuPath = path.relative(params.source, binPath)
|
||||
.then ->
|
||||
# Tar up the directory, ready for the build stream
|
||||
tarDirectory(params.source)
|
||||
.then (tarStream) ->
|
||||
new Promise (resolve, reject) ->
|
||||
hooks =
|
||||
buildSuccess: (image) ->
|
||||
# Show charlie. In the interest of cloud parity,
|
||||
# use console.log, not the standard logging streams
|
||||
doodle = doodles.getDoodle()
|
||||
console.log()
|
||||
console.log(doodle)
|
||||
console.log()
|
||||
|
||||
resolve({ image, log: logs + '\n' + doodle + '\n' } )
|
||||
|
||||
buildFailure: reject
|
||||
buildStream: (stream) ->
|
||||
if options.emulated
|
||||
logger.logInfo('Running emulated build')
|
||||
|
||||
getBundleInfo(options)
|
||||
.then (info) ->
|
||||
if !info?
|
||||
logger.logWarn '''
|
||||
Warning: No architecture/device type or application information provided.
|
||||
Dockerfile/project pre-processing will not be performed.
|
||||
'''
|
||||
return tarStream
|
||||
else
|
||||
[arch, deviceType] = info
|
||||
# Perform type resolution on the project
|
||||
bundle = new resolver.Bundle(tarStream, deviceType, arch)
|
||||
resolver.resolveBundle(bundle, resolver.getDefaultResolvers())
|
||||
.then (resolved) ->
|
||||
logger.logInfo("Building #{resolved.projectType} project")
|
||||
|
||||
return resolved.tarStream
|
||||
.then (buildStream) ->
|
||||
# if we need emulation
|
||||
if options.emulated and platformNeedsQemu()
|
||||
return transpose.transposeTarStream buildStream,
|
||||
hostQemuPath: qemuPath
|
||||
containerQemuPath: "/tmp/#{QEMU_BIN_NAME}"
|
||||
else
|
||||
return buildStream
|
||||
.then (buildStream) ->
|
||||
# Send the resolved tar stream to the docker daemon
|
||||
buildStream.pipe(stream)
|
||||
.catch(reject)
|
||||
|
||||
# And print the output
|
||||
logThroughStream = es.through (data) ->
|
||||
logs += data.toString()
|
||||
this.emit('data', data)
|
||||
|
||||
if options.emulated and platformNeedsQemu()
|
||||
buildThroughStream = transpose.getBuildThroughStream
|
||||
hostQemuPath: qemuPath
|
||||
containerQemuPath: "/tmp/#{QEMU_BIN_NAME}"
|
||||
|
||||
newStream = stream.pipe(buildThroughStream)
|
||||
else
|
||||
newStream = stream
|
||||
|
||||
newStream
|
||||
.pipe(logThroughStream)
|
||||
.pipe(cacheHighlightStream())
|
||||
.pipe(logger.streams.build)
|
||||
|
||||
# Create a builder
|
||||
generateConnectOpts(options)
|
||||
.tap (connectOpts) ->
|
||||
ensureDockerSeemsAccessible(connectOpts)
|
||||
.then (connectOpts) ->
|
||||
# Allow degugging output, hidden behind an env var
|
||||
logger.logDebug('Connecting with the following options:')
|
||||
logger.logDebug(JSON.stringify(connectOpts, null, ' '))
|
||||
|
||||
builder = new dockerBuild.Builder(connectOpts)
|
||||
opts = {}
|
||||
|
||||
if options.tag?
|
||||
opts['t'] = options.tag
|
||||
if options.nocache?
|
||||
opts['nocache'] = true
|
||||
if options.buildArg?
|
||||
opts['buildargs'] = parseBuildArgs options.buildArg, (arg) ->
|
||||
logger.logWarn("Could not parse variable: '#{arg}'")
|
||||
if options.squash?
|
||||
opts['squash'] = true
|
||||
|
||||
builder.createBuildStream(opts, hooks, reject)
|
||||
|
||||
# Given an image id or tag, export the image to a tar archive,
|
||||
# gzip the result, and buffer it to disk.
|
||||
exports.bufferImage = (docker, imageId, bufferFile) ->
|
||||
Promise = require('bluebird')
|
||||
streamUtils = require('./streams')
|
||||
|
||||
image = docker.getImage(imageId)
|
||||
imageMetadata = image.inspectAsync()
|
||||
|
||||
Promise.join image.get(), imageMetadata.get('Size'), (imageStream, imageSize) ->
|
||||
streamUtils.buffer(imageStream, bufferFile)
|
||||
.tap (bufferedStream) ->
|
||||
bufferedStream.length = imageSize
|
||||
|
||||
exports.getDocker = (options) ->
|
||||
Docker = require('dockerode')
|
||||
Promise = require('bluebird')
|
||||
|
||||
generateConnectOpts(options)
|
||||
.tap (connectOpts) ->
|
||||
ensureDockerSeemsAccessible(connectOpts)
|
||||
.then (connectOpts) ->
|
||||
# Use bluebird's promises
|
||||
connectOpts['Promise'] = Promise
|
||||
new Docker(connectOpts)
|
||||
|
||||
ensureDockerSeemsAccessible = (options) ->
|
||||
fs = require('mz/fs')
|
||||
|
||||
if options.socketPath?
|
||||
# If we're trying to use a socket, check it exists and we have access to it
|
||||
fs.access(options.socketPath, (fs.constants || fs).R_OK | (fs.constants || fs).W_OK)
|
||||
.return(true)
|
||||
.catch (err) ->
|
||||
throw new Error(
|
||||
"Docker seems to be unavailable (using socket #{options.socketPath}). Is it
|
||||
installed, and do you have permission to talk to it?"
|
||||
)
|
||||
else
|
||||
# Otherwise, we think we're probably ok
|
||||
Promise.resolve(true)
|
||||
|
||||
hasQemu = ->
|
||||
fs = require('mz/fs')
|
||||
|
||||
getQemuPath()
|
||||
.then(fs.stat)
|
||||
.return(true)
|
||||
.catchReturn(false)
|
||||
|
||||
getQemuPath = ->
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
path = require('path')
|
||||
fs = require('mz/fs')
|
||||
|
||||
resin.settings.get('binDirectory')
|
||||
.then (binDir) ->
|
||||
# The directory might not be created already,
|
||||
# if not, create it
|
||||
fs.access(binDir)
|
||||
.catch code: 'ENOENT', ->
|
||||
fs.mkdir(binDir)
|
||||
.then ->
|
||||
path.join(binDir, QEMU_BIN_NAME)
|
||||
|
||||
platformNeedsQemu = ->
|
||||
os = require('os')
|
||||
os.platform() == 'linux'
|
||||
|
||||
installQemu = ->
|
||||
request = require('request')
|
||||
fs = require('fs')
|
||||
zlib = require('zlib')
|
||||
|
||||
getQemuPath()
|
||||
.then (qemuPath) ->
|
||||
new Promise (resolve, reject) ->
|
||||
installStream = fs.createWriteStream(qemuPath)
|
||||
qemuUrl = "https://github.com/resin-io/qemu/releases/download/#{QEMU_VERSION}/#{QEMU_BIN_NAME}.gz"
|
||||
request(qemuUrl)
|
||||
.pipe(zlib.createGunzip())
|
||||
.pipe(installStream)
|
||||
.on('error', reject)
|
||||
.on('finish', resolve)
|
||||
|
||||
copyQemu = (context) ->
|
||||
path = require('path')
|
||||
fs = require('mz/fs')
|
||||
# Create a hidden directory in the build context, containing qemu
|
||||
binDir = path.join(context, '.resin')
|
||||
binPath = path.join(binDir, QEMU_BIN_NAME)
|
||||
|
||||
fs.access(binDir)
|
||||
.catch code: 'ENOENT', ->
|
||||
fs.mkdir(binDir)
|
||||
.then ->
|
||||
getQemuPath()
|
||||
.then (qemu) ->
|
||||
new Promise (resolve, reject) ->
|
||||
read = fs.createReadStream(qemu)
|
||||
write = fs.createWriteStream(binPath)
|
||||
read
|
||||
.pipe(write)
|
||||
.on('error', reject)
|
||||
.on('finish', resolve)
|
||||
.then ->
|
||||
fs.chmod(binPath, '755')
|
||||
.return(binPath)
|
@ -1,5 +1,5 @@
|
||||
###
|
||||
Copyright 2016 Resin.io
|
||||
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.
|
||||
@ -15,23 +15,22 @@ limitations under the License.
|
||||
###
|
||||
|
||||
Promise = require('bluebird')
|
||||
capitano = Promise.promisifyAll(require('capitano'))
|
||||
_ = require('lodash')
|
||||
_.str = require('underscore.string')
|
||||
president = Promise.promisifyAll(require('president'))
|
||||
os = require('os')
|
||||
chalk = require('chalk')
|
||||
|
||||
exports.getGroupDefaults = (group) ->
|
||||
_ = require('lodash')
|
||||
|
||||
return _.chain(group)
|
||||
.get('options')
|
||||
.map (question) ->
|
||||
return [ question.name, question.default ]
|
||||
.object()
|
||||
.fromPairs()
|
||||
.value()
|
||||
|
||||
exports.stateToString = (state) ->
|
||||
percentage = _.str.lpad(state.percentage, 3, '0') + '%'
|
||||
_str = require('underscore.string')
|
||||
chalk = require('chalk')
|
||||
|
||||
percentage = _str.lpad(state.percentage, 3, '0') + '%'
|
||||
result = "#{chalk.blue(percentage)} #{chalk.cyan(state.operation.command)}"
|
||||
|
||||
switch state.operation.command
|
||||
@ -44,11 +43,93 @@ exports.stateToString = (state) ->
|
||||
else
|
||||
throw new Error("Unsupported operation: #{state.operation.type}")
|
||||
|
||||
exports.sudo = (command, message) ->
|
||||
command = _.union(_.take(process.argv, 2), command)
|
||||
console.log(message)
|
||||
exports.sudo = (command) ->
|
||||
_ = require('lodash')
|
||||
os = require('os')
|
||||
|
||||
if os.platform() isnt 'win32'
|
||||
console.log('Type your computer password to continue')
|
||||
console.log('If asked please type your computer password to continue')
|
||||
|
||||
return president.executeAsync(command)
|
||||
command = _.union(_.take(process.argv, 2), command)
|
||||
presidentExecuteAsync = Promise.promisify(require('president').execute)
|
||||
return presidentExecuteAsync(command)
|
||||
|
||||
exports.getManifest = (image, deviceType) ->
|
||||
rindle = require('rindle')
|
||||
imagefs = require('resin-image-fs')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
|
||||
# Attempt to read manifest from the first
|
||||
# partition, but fallback to the API if
|
||||
# we encounter any errors along the way.
|
||||
imagefs.read
|
||||
image: image
|
||||
partition:
|
||||
primary: 1
|
||||
path: '/device-type.json'
|
||||
.then(rindle.extractAsync)
|
||||
.then(JSON.parse)
|
||||
.catch ->
|
||||
resin.models.device.getManifestBySlug(deviceType)
|
||||
|
||||
exports.osProgressHandler = (step) ->
|
||||
rindle = require('rindle')
|
||||
visuals = require('resin-cli-visuals')
|
||||
|
||||
step.on('stdout', process.stdout.write.bind(process.stdout))
|
||||
step.on('stderr', process.stderr.write.bind(process.stderr))
|
||||
|
||||
step.on 'state', (state) ->
|
||||
return if state.operation.command is 'burn'
|
||||
console.log(exports.stateToString(state))
|
||||
|
||||
progressBars =
|
||||
write: new visuals.Progress('Writing Device OS')
|
||||
check: new visuals.Progress('Validating Device OS')
|
||||
|
||||
step.on 'burn', (state) ->
|
||||
progressBars[state.type].update(state)
|
||||
|
||||
return rindle.wait(step)
|
||||
|
||||
exports.getAppInfo = (application) ->
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
_ = require('lodash')
|
||||
Promise.join(
|
||||
getApplication(application),
|
||||
resin.models.config.getDeviceTypes(),
|
||||
(app, config) ->
|
||||
config = _.find(config, 'slug': app.device_type)
|
||||
if !config?
|
||||
throw new Error('Could not read application information!')
|
||||
app.arch = config.arch
|
||||
return app
|
||||
)
|
||||
|
||||
getApplication = (application) ->
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
|
||||
# Check for an app of the form `user/application`, and send
|
||||
# this off to a special handler (before importing any modules)
|
||||
if (match = /(\w+)\/(\w+)/.exec(application))
|
||||
return resin.models.application.getAppWithOwner(match[2], match[1])
|
||||
|
||||
return resin.models.application.get(application)
|
||||
|
||||
# A function to reliably execute a command
|
||||
# in all supported operating systems, including
|
||||
# different Windows environments like `cmd.exe`
|
||||
# and `Cygwin`.
|
||||
exports.getSubShellCommand = (command) ->
|
||||
os = require('os')
|
||||
|
||||
if os.platform() is 'win32'
|
||||
return {
|
||||
program: 'cmd.exe'
|
||||
args: [ '/s', '/c', command ]
|
||||
}
|
||||
else
|
||||
return {
|
||||
program: '/bin/sh'
|
||||
args: [ '-c', command ]
|
||||
}
|
||||
|
46
lib/utils/logger.coffee
Normal file
46
lib/utils/logger.coffee
Normal file
@ -0,0 +1,46 @@
|
||||
eol = require('os').EOL
|
||||
|
||||
module.exports = class Logger
|
||||
constructor: ->
|
||||
{ StreamLogger } = require('resin-stream-logger')
|
||||
colors = require('colors')
|
||||
_ = require('lodash')
|
||||
|
||||
logger = new StreamLogger()
|
||||
logger.addPrefix('build', colors.blue('[Build]'))
|
||||
logger.addPrefix('info', colors.cyan('[Info]'))
|
||||
logger.addPrefix('debug', colors.magenta('[Debug]'))
|
||||
logger.addPrefix('success', colors.green('[Success]'))
|
||||
logger.addPrefix('warn', colors.yellow('[Warn]'))
|
||||
logger.addPrefix('error', colors.red('[Error]'))
|
||||
|
||||
@streams =
|
||||
build: logger.createLogStream('build'),
|
||||
info: logger.createLogStream('info'),
|
||||
debug: logger.createLogStream('debug'),
|
||||
success: logger.createLogStream('success'),
|
||||
warn: logger.createLogStream('warn'),
|
||||
error: logger.createLogStream('error')
|
||||
|
||||
_.mapKeys @streams, (stream, key) ->
|
||||
if key isnt 'debug'
|
||||
stream.pipe(process.stdout)
|
||||
else
|
||||
stream.pipe(process.stdout) if process.env.DEBUG?
|
||||
|
||||
@formatMessage = logger.formatWithPrefix.bind(logger)
|
||||
|
||||
logInfo: (msg) ->
|
||||
@streams.info.write(msg + eol)
|
||||
|
||||
logDebug: (msg) ->
|
||||
@streams.debug.write(msg + eol)
|
||||
|
||||
logSuccess: (msg) ->
|
||||
@streams.success.write(msg + eol)
|
||||
|
||||
logWarn: (msg) ->
|
||||
@streams.warn.write(msg + eol)
|
||||
|
||||
logError: (msg) ->
|
||||
@streams.error.write(msg + eol)
|
@ -1,21 +1,15 @@
|
||||
exports.gettingStarted = '''
|
||||
Run the following command to get a device started with Resin.io
|
||||
|
||||
$ resin quickstart
|
||||
'''
|
||||
|
||||
exports.reachingOut = '''
|
||||
If you need help, or just want to say hi, don't hesitate in reaching out at:
|
||||
|
||||
GitHub: https://github.com/resin-io/resin-cli/issues/new
|
||||
Gitter: https://gitter.im/resin-io/chat
|
||||
Forums: https://forums.resin.io
|
||||
'''
|
||||
|
||||
exports.getHelp = '''
|
||||
If you need help, don't hesitate in contacting us at:
|
||||
|
||||
GitHub: https://github.com/resin-io/resin-cli/issues/new
|
||||
Gitter: https://gitter.im/resin-io/chat
|
||||
Forums: https://forums.resin.io
|
||||
'''
|
||||
|
||||
exports.resinAsciiArt = '''
|
||||
|
@ -1,5 +1,5 @@
|
||||
###
|
||||
Copyright 2016 Resin.io
|
||||
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.
|
||||
@ -18,7 +18,7 @@ _ = require('lodash')
|
||||
Promise = require('bluebird')
|
||||
form = require('resin-cli-form')
|
||||
visuals = require('resin-cli-visuals')
|
||||
resin = require('resin-sdk')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
chalk = require('chalk')
|
||||
validation = require('./validation')
|
||||
messages = require('./messages')
|
||||
@ -44,9 +44,11 @@ exports.authenticate = (options) ->
|
||||
name: 'code'
|
||||
type: 'input'
|
||||
.then(resin.auth.twoFactor.challenge)
|
||||
.catch ->
|
||||
.catch (error) ->
|
||||
resin.auth.logout().then ->
|
||||
throw new Error('Invalid two factor authentication code')
|
||||
if error.name is 'ResinRequestError' and error.statusCode is 401
|
||||
throw new Error('Invalid two factor authentication code')
|
||||
throw error
|
||||
|
||||
exports.askLoginType = ->
|
||||
return form.ask
|
||||
@ -74,9 +76,11 @@ exports.selectDeviceType = ->
|
||||
type: 'list'
|
||||
choices: deviceTypes
|
||||
|
||||
exports.confirm = (yesOption, message) ->
|
||||
exports.confirm = (yesOption, message, yesMessage) ->
|
||||
Promise.try ->
|
||||
return true if yesOption
|
||||
if yesOption
|
||||
console.log(yesMessage) if yesMessage
|
||||
return true
|
||||
return form.ask
|
||||
message: message
|
||||
type: 'confirm'
|
||||
@ -148,6 +152,30 @@ exports.awaitDevice = (uuid) ->
|
||||
console.info("Waiting for #{deviceName} to connect to resin...")
|
||||
poll().return(uuid)
|
||||
|
||||
exports.inferOrSelectDevice = (preferredUuid) ->
|
||||
resin.models.device.getAll()
|
||||
.filter (device) ->
|
||||
device.is_online
|
||||
.then (onlineDevices) ->
|
||||
if _.isEmpty(onlineDevices)
|
||||
throw new Error('You don\'t have any devices online')
|
||||
|
||||
return form.ask
|
||||
message: 'Select a device'
|
||||
type: 'list'
|
||||
default: if preferredUuid in _.map(onlineDevices, 'uuid') then preferredUuid else onlineDevices[0].uuid
|
||||
choices: _.map onlineDevices, (device) ->
|
||||
return {
|
||||
name: "#{device.name or 'Untitled'} (#{device.uuid.slice(0, 7)})"
|
||||
value: device.uuid
|
||||
}
|
||||
|
||||
exports.printErrorMessage = (message) ->
|
||||
console.error(chalk.red(message))
|
||||
console.error(chalk.red("\n#{messages.getHelp}\n"))
|
||||
|
||||
exports.expectedError = (message) ->
|
||||
if message instanceof Error
|
||||
message = message.message
|
||||
exports.printErrorMessage(message)
|
||||
process.exit(1)
|
||||
|
@ -1,5 +1,5 @@
|
||||
###
|
||||
Copyright 2016 Resin.io
|
||||
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.
|
||||
|
17
lib/utils/streams.coffee
Normal file
17
lib/utils/streams.coffee
Normal file
@ -0,0 +1,17 @@
|
||||
exports.buffer = (stream, bufferFile) ->
|
||||
Promise = require('bluebird')
|
||||
fs = require('fs')
|
||||
|
||||
fileWriteStream = fs.createWriteStream(bufferFile)
|
||||
|
||||
new Promise (resolve, reject) ->
|
||||
stream
|
||||
.on('error', reject)
|
||||
.on('end', resolve)
|
||||
.pipe(fileWriteStream)
|
||||
.then ->
|
||||
new Promise (resolve, reject) ->
|
||||
fs.createReadStream(bufferFile)
|
||||
.on 'open', ->
|
||||
resolve(this)
|
||||
.on('error', reject)
|
@ -1,5 +1,5 @@
|
||||
###
|
||||
Copyright 2016 Resin.io
|
||||
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.
|
||||
@ -18,11 +18,17 @@ updateNotifier = require('update-notifier')
|
||||
isRoot = require('is-root')
|
||||
packageJSON = require('../../package.json')
|
||||
|
||||
# Check for an update once a day. 1 day granularity should be
|
||||
# enough, rather than every run.
|
||||
resinUpdateInterval = 1000 * 60 * 60 * 24 * 1
|
||||
|
||||
# `update-notifier` creates files to make the next
|
||||
# running time ask for updated, however this can lead
|
||||
# to ugly EPERM issues if those files are created as root.
|
||||
if not isRoot()
|
||||
notifier = updateNotifier(pkg: packageJSON)
|
||||
notifier = updateNotifier
|
||||
pkg: packageJSON
|
||||
updateCheckInterval: resinUpdateInterval
|
||||
|
||||
exports.hasAvailableUpdate = ->
|
||||
return notifier?
|
||||
|
@ -1,5 +1,5 @@
|
||||
###
|
||||
Copyright 2016 Resin.io
|
||||
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.
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
###
|
||||
|
||||
validEmail = require('valid-email')
|
||||
validEmail = require('@resin.io/valid-email')
|
||||
|
||||
exports.validateEmail = (input) ->
|
||||
if not validEmail(input)
|
||||
|
136
package.json
136
package.json
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "resin-cli",
|
||||
"version": "2.7.0",
|
||||
"description": "Git Push to your devices",
|
||||
"version": "6.10.2",
|
||||
"description": "The official resin.io CLI tool",
|
||||
"main": "./build/actions/index.js",
|
||||
"homepage": "https://github.com/resin-io/resin-cli",
|
||||
"repository": {
|
||||
@ -9,12 +9,39 @@
|
||||
"url": "git@github.com:resin-io/resin-cli.git"
|
||||
},
|
||||
"preferGlobal": true,
|
||||
"files": [
|
||||
"bin/",
|
||||
"build/",
|
||||
"doc/",
|
||||
"lib/"
|
||||
],
|
||||
"bin": {
|
||||
"resin": "./bin/resin"
|
||||
},
|
||||
"pkg": {
|
||||
"scripts": [
|
||||
"node_modules/resin-sync/build/capitano/*.js",
|
||||
"node_modules/resin-sync/build/sync/*.js"
|
||||
],
|
||||
"assets": [
|
||||
"build/auth/pages/*.ejs",
|
||||
"node_modules/resin-discoverable-services/services/**/*"
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"prepublish": "gulp build",
|
||||
"doc": "mkdir -p doc/ && coffee extras/capitanodoc/index.coffee > doc/cli.markdown"
|
||||
"prebuild": "rimraf build/ build-bin/",
|
||||
"build": "npm run build:src && npm run build:bin",
|
||||
"build:src": "gulp build && tsc && npm run doc",
|
||||
"build:bin": "ts-node --type-check -P automation automation/build-bin.ts",
|
||||
"release": "npm run build && ts-node --type-check -P automation automation/deploy-bin.ts",
|
||||
"pretest": "npm run build",
|
||||
"test": "gulp test",
|
||||
"ci": "npm run test && catch-uncommitted",
|
||||
"doc": "mkdirp doc/ && coffee extras/capitanodoc/index.coffee > doc/cli.markdown",
|
||||
"watch": "gulp watch",
|
||||
"lint": "gulp lint",
|
||||
"prepublish": "require-npm4-to-publish",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"keywords": [
|
||||
"resin",
|
||||
@ -22,46 +49,97 @@
|
||||
],
|
||||
"author": "Juan Cruz Viotti <juan@resin.io>",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/archiver": "^2.0.1",
|
||||
"@types/fs-extra": "^5.0.0",
|
||||
"catch-uncommitted": "^1.0.0",
|
||||
"ent": "^2.2.0",
|
||||
"filehound": "^1.16.2",
|
||||
"fs-extra": "^5.0.0",
|
||||
"gulp": "^3.9.0",
|
||||
"gulp-coffee": "^2.2.0",
|
||||
"gulp-coffeelint": "^0.5.0",
|
||||
"gulp-shell": "^0.4.2",
|
||||
"gulp-util": "^3.0.6"
|
||||
"gulp-coffeelint": "^0.6.0",
|
||||
"gulp-inline-source": "^2.1.0",
|
||||
"gulp-mocha": "^2.0.0",
|
||||
"gulp-shell": "^0.5.2",
|
||||
"mochainon": "^2.0.0",
|
||||
"pkg": "^4.3.0-beta.1",
|
||||
"publish-release": "^1.3.3",
|
||||
"require-npm4-to-publish": "^1.0.0",
|
||||
"ts-node": "^4.0.1",
|
||||
"typescript": "^2.6.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"bluebird": "^2.9.34",
|
||||
"capitano": "~1.7.0",
|
||||
"chalk": "^1.1.1",
|
||||
"coffee-script": "^1.9.3",
|
||||
"@resin.io/valid-email": "^0.1.0",
|
||||
"ansi-escapes": "^2.0.0",
|
||||
"any-promise": "^1.3.0",
|
||||
"archiver": "^2.1.0",
|
||||
"bash": "0.0.1",
|
||||
"bluebird": "^3.3.3",
|
||||
"body-parser": "^1.14.1",
|
||||
"capitano": "^1.7.0",
|
||||
"chalk": "^1.1.3",
|
||||
"coffee-script": "^1.12.6",
|
||||
"columnify": "^1.5.2",
|
||||
"denymount": "^2.2.0",
|
||||
"docker-qemu-transpose": "^0.2.2",
|
||||
"docker-toolbelt": "^1.3.3",
|
||||
"dockerode": "^2.5.0",
|
||||
"dockerode-options": "^0.2.1",
|
||||
"drivelist": "^5.0.22",
|
||||
"ejs": "^2.5.7",
|
||||
"etcher-image-write": "^9.0.3",
|
||||
"express": "^4.13.3",
|
||||
"global-tunnel-ng": "github:zvin/global-tunnel#dont-proxy-connections-to-file-sockets",
|
||||
"hasbin": "^1.2.3",
|
||||
"inquirer": "^3.1.1",
|
||||
"is-root": "^1.0.0",
|
||||
"lodash": "^3.10.0",
|
||||
"js-yaml": "^3.7.0",
|
||||
"klaw": "^1.3.1",
|
||||
"lodash": "^4.17.4",
|
||||
"mixpanel": "^0.4.0",
|
||||
"moment": "^2.10.6",
|
||||
"mkdirp": "^0.5.1",
|
||||
"moment": "^2.12.0",
|
||||
"mz": "^2.6.0",
|
||||
"node-cleanup": "^2.1.2",
|
||||
"nplugm": "^3.0.0",
|
||||
"opn": "^5.1.0",
|
||||
"president": "^2.0.1",
|
||||
"prettyjson": "^1.1.3",
|
||||
"resin-cli-auth": "^1.0.0",
|
||||
"resin-cli-errors": "^1.0.0",
|
||||
"resin-cli-form": "^1.4.0",
|
||||
"resin-cli-visuals": "^1.2.2",
|
||||
"progress-stream": "^2.0.0",
|
||||
"raven": "^1.2.0",
|
||||
"reconfix": "^0.0.3",
|
||||
"request": "^2.81.0",
|
||||
"resin-bundle-resolve": "^0.0.2",
|
||||
"resin-cli-errors": "^1.2.0",
|
||||
"resin-cli-form": "^1.4.1",
|
||||
"resin-cli-visuals": "^1.4.0",
|
||||
"resin-config-json": "^1.0.0",
|
||||
"resin-device-config": "^2.6.1",
|
||||
"resin-device-init": "^2.0.0",
|
||||
"resin-image-manager": "^4.0.0",
|
||||
"resin-pine": "^1.3.0",
|
||||
"resin-sdk": "^5.2.0",
|
||||
"resin-settings-client": "^3.1.0",
|
||||
"resin-vcs": "^2.0.0",
|
||||
"resin-device-config": "^4.0.0",
|
||||
"resin-device-init": "^4.0.0",
|
||||
"resin-docker-build": "^0.4.0",
|
||||
"resin-doodles": "0.0.1",
|
||||
"resin-image-fs": "^2.3.0",
|
||||
"resin-image-manager": "^5.0.0",
|
||||
"resin-preload": "^5.0.0",
|
||||
"resin-sdk": "^7.0.0",
|
||||
"resin-sdk-preconfigured": "^6.9.0",
|
||||
"resin-settings-client": "^3.6.1",
|
||||
"resin-stream-logger": "^0.0.4",
|
||||
"resin-sync": "^9.2.3",
|
||||
"rimraf": "^2.4.3",
|
||||
"rindle": "^1.0.0",
|
||||
"tmp": "0.0.28",
|
||||
"umount": "^1.1.1",
|
||||
"underscore.string": "^3.1.1",
|
||||
"semver": "^5.3.0",
|
||||
"stream-to-promise": "^2.2.0",
|
||||
"tmp": "0.0.31",
|
||||
"umount": "^1.1.6",
|
||||
"unzip2": "^0.2.5",
|
||||
"update-notifier": "^0.5.0",
|
||||
"valid-email": "0.0.2"
|
||||
"update-notifier": "^2.2.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"removedrive": "^1.0.0"
|
||||
}
|
||||
}
|
||||
|
124
tests/auth/server.spec.coffee
Normal file
124
tests/auth/server.spec.coffee
Normal file
@ -0,0 +1,124 @@
|
||||
m = require('mochainon')
|
||||
request = require('request')
|
||||
Promise = require('bluebird')
|
||||
path = require('path')
|
||||
fs = require('fs')
|
||||
ejs = require('ejs')
|
||||
server = require('../../build/auth/server')
|
||||
utils = require('../../build/auth/utils')
|
||||
tokens = require('./tokens.json')
|
||||
|
||||
options =
|
||||
port: 3000
|
||||
path: '/auth'
|
||||
|
||||
getPage = (name) ->
|
||||
pagePath = path.join(__dirname, '..', '..', 'build', 'auth', 'pages', "#{name}.ejs")
|
||||
tpl = fs.readFileSync(pagePath, encoding: 'utf8')
|
||||
compiledTpl = ejs.compile(tpl)
|
||||
return server.getContext(name)
|
||||
.then (context) ->
|
||||
compiledTpl(context)
|
||||
|
||||
describe 'Server:', ->
|
||||
|
||||
it 'should get 404 if posting to an unknown path', (done) ->
|
||||
promise = server.awaitForToken(options)
|
||||
m.chai.expect(promise).to.be.rejectedWith('Unknown path or verb')
|
||||
|
||||
request.post "http://localhost:#{options.port}/foobarbaz",
|
||||
form:
|
||||
token: tokens.johndoe.token
|
||||
, (error, response, body) ->
|
||||
m.chai.expect(error).to.not.exist
|
||||
m.chai.expect(response.statusCode).to.equal(404)
|
||||
m.chai.expect(body).to.equal('Not found')
|
||||
done()
|
||||
|
||||
it 'should get 404 if not using the correct verb', (done) ->
|
||||
promise = server.awaitForToken(options)
|
||||
m.chai.expect(promise).to.be.rejectedWith('Unknown path or verb')
|
||||
|
||||
request.get "http://localhost:#{options.port}#{options.path}",
|
||||
form:
|
||||
token: tokens.johndoe.token
|
||||
, (error, response, body) ->
|
||||
m.chai.expect(error).to.not.exist
|
||||
m.chai.expect(response.statusCode).to.equal(404)
|
||||
m.chai.expect(body).to.equal('Not found')
|
||||
done()
|
||||
|
||||
describe 'given the token authenticates with the server', ->
|
||||
|
||||
beforeEach ->
|
||||
@utilsIsTokenValidStub = m.sinon.stub(utils, 'isTokenValid')
|
||||
@utilsIsTokenValidStub.returns(Promise.resolve(true))
|
||||
|
||||
afterEach ->
|
||||
@utilsIsTokenValidStub.restore()
|
||||
|
||||
it 'should eventually be the token', (done) ->
|
||||
promise = server.awaitForToken(options)
|
||||
m.chai.expect(promise).to.eventually.equal(tokens.johndoe.token)
|
||||
|
||||
request.post "http://localhost:#{options.port}#{options.path}",
|
||||
form:
|
||||
token: tokens.johndoe.token
|
||||
, (error, response, body) ->
|
||||
m.chai.expect(error).to.not.exist
|
||||
m.chai.expect(response.statusCode).to.equal(200)
|
||||
getPage('success').then (expectedBody) ->
|
||||
m.chai.expect(body).to.equal(expectedBody)
|
||||
done()
|
||||
|
||||
describe 'given the token does not authenticate with the server', ->
|
||||
|
||||
beforeEach ->
|
||||
@utilsIsTokenValidStub = m.sinon.stub(utils, 'isTokenValid')
|
||||
@utilsIsTokenValidStub.returns(Promise.resolve(false))
|
||||
|
||||
afterEach ->
|
||||
@utilsIsTokenValidStub.restore()
|
||||
|
||||
it 'should be rejected', (done) ->
|
||||
promise = server.awaitForToken(options)
|
||||
m.chai.expect(promise).to.be.rejectedWith('Invalid token')
|
||||
|
||||
request.post "http://localhost:#{options.port}#{options.path}",
|
||||
form:
|
||||
token: tokens.johndoe.token
|
||||
, (error, response, body) ->
|
||||
m.chai.expect(error).to.not.exist
|
||||
m.chai.expect(response.statusCode).to.equal(401)
|
||||
getPage('error').then (expectedBody) ->
|
||||
m.chai.expect(body).to.equal(expectedBody)
|
||||
done()
|
||||
|
||||
it 'should be rejected if no token', (done) ->
|
||||
promise = server.awaitForToken(options)
|
||||
m.chai.expect(promise).to.be.rejectedWith('No token')
|
||||
|
||||
request.post "http://localhost:#{options.port}#{options.path}",
|
||||
form:
|
||||
token: ''
|
||||
, (error, response, body) ->
|
||||
m.chai.expect(error).to.not.exist
|
||||
m.chai.expect(response.statusCode).to.equal(401)
|
||||
getPage('error').then (expectedBody) ->
|
||||
m.chai.expect(body).to.equal(expectedBody)
|
||||
done()
|
||||
|
||||
it 'should be rejected if token is malformed', (done) ->
|
||||
promise = server.awaitForToken(options)
|
||||
m.chai.expect(promise).to.be.rejectedWith('Invalid token')
|
||||
|
||||
request.post "http://localhost:#{options.port}#{options.path}",
|
||||
form:
|
||||
token: 'asdf'
|
||||
, (error, response, body) ->
|
||||
m.chai.expect(error).to.not.exist
|
||||
m.chai.expect(response.statusCode).to.equal(401)
|
||||
getPage('error').then (expectedBody) ->
|
||||
m.chai.expect(body).to.equal(expectedBody)
|
||||
done()
|
||||
|
18
tests/auth/tokens.json
Normal file
18
tests/auth/tokens.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"johndoe": {
|
||||
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImpvaG5kb2UxIiwiZW1haWwiOiJqb2huZG9lQGpvaG5kb2UuY29tIiwiZ2l0bGFiX2lkIjoxMzI1LCJzb2NpYWxfc2VydmljZV9hY2NvdW50IjpudWxsLCJoYXNQYXNzd29yZFNldCI6dHJ1ZSwibmVlZHNQYXNzd29yZFJlc2V0IjpmYWxzZSwicHVibGljX2tleSI6ZmFsc2UsImZlYXR1cmVzIjpbXSwiaWQiOjEzNDQsImludGVyY29tVXNlckhhc2giOiJlMDM3NzhkZDI5ZTE1NzQ0NWYyNzJhY2M5MjExNzBjZjI4MTBiNjJmNTAyNjQ1MjY1Y2MzNDlkNmRlZGEzNTI0IiwicGVybWlzc2lvbnMiOltdLCJpYXQiOjE0MjY3ODMzMTJ9.v5bmh9HwyUZu8zhh1rA79mTL-1jzDOO8eUr_lVaBwhg",
|
||||
"data": {
|
||||
"email": "johndoe@johndoe.com",
|
||||
"username": "johndoe1",
|
||||
"id": 1344
|
||||
}
|
||||
},
|
||||
"janedoe": {
|
||||
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MTUyLCJ1c2VybmFtZSI6ImphbmVkb2UiLCJlbWFpbCI6ImphbmVkb2VAYXNkZi5jb20iLCJzb2NpYWxfc2VydmljZV9hY2NvdW50IjpudWxsLCJoYXNfZGlzYWJsZWRfbmV3c2xldHRlciI6dHJ1ZSwiaGFzUGFzc3dvcmRTZXQiOnRydWUsIm5lZWRzUGFzc3dvcmRSZXNldCI6ZmFsc2UsInB1YmxpY19rZXkiOmZhbHNlLCJmZWF0dXJlcyI6W10sImludGVyY29tVXNlckhhc2giOiIwYjRmOWViNDRiMzcxZjBlMzI4ZWY1ZmUwM2FkN2ViMmY1ZjcyZGQ0MThlZjIzMTQ5ZDUyODcwOTY1NThjZTAzIiwicGVybWlzc2lvbnMiOltdLCJpYXQiOjE0MzUzMjAyNjN9.jVzUFu58vzdJFctR8ulyjGL0Em1kjIZSbSxX2SeU03Y",
|
||||
"data": {
|
||||
"email": "janedoe@asdf.com",
|
||||
"username": "janedoe",
|
||||
"id": 152
|
||||
}
|
||||
}
|
||||
}
|
111
tests/auth/utils.spec.coffee
Normal file
111
tests/auth/utils.spec.coffee
Normal file
@ -0,0 +1,111 @@
|
||||
m = require('mochainon')
|
||||
url = require('url')
|
||||
Promise = require('bluebird')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
utils = require('../../build/auth/utils')
|
||||
tokens = require('./tokens.json')
|
||||
|
||||
describe 'Utils:', ->
|
||||
|
||||
describe '.getDashboardLoginURL()', ->
|
||||
|
||||
it 'should eventually be a valid url', (done) ->
|
||||
utils.getDashboardLoginURL('https://127.0.0.1:3000/callback').then (loginUrl) ->
|
||||
m.chai.expect ->
|
||||
url.parse(loginUrl)
|
||||
.to.not.throw(Error)
|
||||
.nodeify(done)
|
||||
|
||||
it 'should eventually contain an https protocol', (done) ->
|
||||
Promise.props
|
||||
dashboardUrl: resin.settings.get('dashboardUrl')
|
||||
loginUrl: utils.getDashboardLoginURL('https://127.0.0.1:3000/callback')
|
||||
.then ({ dashboardUrl, loginUrl }) ->
|
||||
protocol = url.parse(loginUrl).protocol
|
||||
m.chai.expect(protocol).to.equal(url.parse(dashboardUrl).protocol)
|
||||
.nodeify(done)
|
||||
|
||||
it 'should correctly escape a callback url without a path', (done) ->
|
||||
Promise.props
|
||||
dashboardUrl: resin.settings.get('dashboardUrl')
|
||||
loginUrl: utils.getDashboardLoginURL('http://127.0.0.1:3000')
|
||||
.then ({ dashboardUrl, loginUrl }) ->
|
||||
expectedUrl = "#{dashboardUrl}/login/cli/http%253A%252F%252F127.0.0.1%253A3000"
|
||||
m.chai.expect(loginUrl).to.equal(expectedUrl)
|
||||
.nodeify(done)
|
||||
|
||||
it 'should correctly escape a callback url with a path', (done) ->
|
||||
Promise.props
|
||||
dashboardUrl: resin.settings.get('dashboardUrl')
|
||||
loginUrl: utils.getDashboardLoginURL('http://127.0.0.1:3000/callback')
|
||||
.then ({ dashboardUrl, loginUrl }) ->
|
||||
expectedUrl = "#{dashboardUrl}/login/cli/http%253A%252F%252F127.0.0.1%253A3000%252Fcallback"
|
||||
m.chai.expect(loginUrl).to.equal(expectedUrl)
|
||||
.nodeify(done)
|
||||
|
||||
describe '.isTokenValid()', ->
|
||||
|
||||
it 'should eventually be false if token is undefined', ->
|
||||
promise = utils.isTokenValid(undefined)
|
||||
m.chai.expect(promise).to.eventually.be.false
|
||||
|
||||
it 'should eventually be false if token is null', ->
|
||||
promise = utils.isTokenValid(null)
|
||||
m.chai.expect(promise).to.eventually.be.false
|
||||
|
||||
it 'should eventually be false if token is an empty string', ->
|
||||
promise = utils.isTokenValid('')
|
||||
m.chai.expect(promise).to.eventually.be.false
|
||||
|
||||
it 'should eventually be false if token is a string containing only spaces', ->
|
||||
promise = utils.isTokenValid(' ')
|
||||
m.chai.expect(promise).to.eventually.be.false
|
||||
|
||||
describe 'given the token does not authenticate with the server', ->
|
||||
|
||||
beforeEach ->
|
||||
@resinAuthIsLoggedInStub = m.sinon.stub(resin.auth, 'isLoggedIn')
|
||||
@resinAuthIsLoggedInStub.returns(Promise.resolve(false))
|
||||
|
||||
afterEach ->
|
||||
@resinAuthIsLoggedInStub.restore()
|
||||
|
||||
it 'should eventually be false', ->
|
||||
promise = utils.isTokenValid(tokens.johndoe.token)
|
||||
m.chai.expect(promise).to.eventually.be.false
|
||||
|
||||
describe 'given there was a token already', ->
|
||||
|
||||
beforeEach (done) ->
|
||||
resin.auth.loginWithToken(tokens.janedoe.token).nodeify(done)
|
||||
|
||||
it 'should preserve the old token', (done) ->
|
||||
resin.auth.getToken().then (originalToken) ->
|
||||
m.chai.expect(originalToken).to.equal(tokens.janedoe.token)
|
||||
return utils.isTokenValid(tokens.johndoe.token)
|
||||
.then(resin.auth.getToken).then (currentToken) ->
|
||||
m.chai.expect(currentToken).to.equal(tokens.janedoe.token)
|
||||
.nodeify(done)
|
||||
|
||||
describe 'given there was no token', ->
|
||||
|
||||
beforeEach (done) ->
|
||||
resin.auth.logout().nodeify(done)
|
||||
|
||||
it 'should stay without a token', (done) ->
|
||||
utils.isTokenValid(tokens.johndoe.token).then ->
|
||||
m.chai.expect(resin.token.get()).to.eventually.not.exist
|
||||
.nodeify(done)
|
||||
|
||||
describe 'given the token does authenticate with the server', ->
|
||||
|
||||
beforeEach ->
|
||||
@resinAuthIsLoggedInStub = m.sinon.stub(resin.auth, 'isLoggedIn')
|
||||
@resinAuthIsLoggedInStub.returns(Promise.resolve(true))
|
||||
|
||||
afterEach ->
|
||||
@resinAuthIsLoggedInStub.restore()
|
||||
|
||||
it 'should eventually be true', ->
|
||||
promise = utils.isTokenValid(tokens.johndoe.token)
|
||||
m.chai.expect(promise).to.eventually.be.true
|
16
tsconfig.json
Normal file
16
tsconfig.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"outDir": "build",
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"preserveConstEnums": true,
|
||||
"removeComments": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": [
|
||||
"./lib/**/*.ts"
|
||||
]
|
||||
}
|
Reference in New Issue
Block a user