mirror of
https://github.com/balena-io/balena-cli.git
synced 2025-06-24 18:45:07 +00:00
Compare commits
779 Commits
Author | SHA1 | Date | |
---|---|---|---|
73455b4264 | |||
28b0793fc9 | |||
c904726259 | |||
6606b65c9b | |||
61160fd2f5 | |||
bf71f9ea16 | |||
fe751fdb23 | |||
947f91d570 | |||
c5d4e30e24 | |||
f560aa7523 | |||
6bcfb2dd51 | |||
bf062124f7 | |||
221666f59a | |||
4369a2d161 | |||
cd6ee4ef5e | |||
872b17cf24 | |||
88e11347bc | |||
a3dd489c70 | |||
0c1c108b2b | |||
f02ed43f33 | |||
63c3d7ceee | |||
dac45a884e | |||
ec589c2639 | |||
f65e777d1b | |||
684ac9fa24 | |||
330cbc6a68 | |||
14bfca8c3a | |||
20c07d31b2 | |||
64b4f67477 | |||
a8ceadc300 | |||
973d25f467 | |||
0d06701e2f | |||
379f1cc217 | |||
7b7ae4ff89 | |||
8e83a401eb | |||
2d1891a182 | |||
8df066df12 | |||
bd59f95e1a | |||
2b982a1c0c | |||
ab64fbc904 | |||
733b98f072 | |||
7c538a3658 | |||
8298ba5765 | |||
33a23773d8 | |||
21a3b82845 | |||
8688eb5da0 | |||
5b0ea9673f | |||
44fd8adeba | |||
a5e03d55c3 | |||
80629322ea | |||
946efbcb7f | |||
be8a314d2b | |||
0a7203cafe | |||
786fed0151 | |||
9cd8228a20 | |||
652b5f22dd | |||
eed3c06789 | |||
3b283d4a98 | |||
bc6b5ba7b3 | |||
74789ae88f | |||
295d6dee74 | |||
5010a1e312 | |||
3c2f7ea622 | |||
94f02f0ad8 | |||
375f84b24e | |||
06c649dfd0 | |||
71eca70a22 | |||
53c7bc622c | |||
975ae45e49 | |||
e7c68c1a5c | |||
5beeb78220 | |||
c90ba7aa0f | |||
802ccc1b9a | |||
b6ef251625 | |||
fd707d6a07 | |||
392cd8569f | |||
e32eda26d9 | |||
d8aaccf80c | |||
d5fd5f5f2d | |||
2cb69c12f1 | |||
7c75346a1a | |||
148d15b6d9 | |||
a46a79df59 | |||
e350f9b335 | |||
bd00773f1b | |||
ef3c7f0fd6 | |||
f4f44f978e | |||
442416efc3 | |||
ef33ffedcf | |||
430d4aeaa7 | |||
171632f83f | |||
1fa7141b58 | |||
916cc36430 | |||
27b877dd33 | |||
5cbe1c410f | |||
7846af390e | |||
79d9ebc805 | |||
25b853c535 | |||
a93141343f | |||
9a467c5ecd | |||
70be2ae596 | |||
36eb0a108e | |||
0bf6fb1739 | |||
892adf4c47 | |||
5d1d004b72 | |||
dea5a60b2d | |||
652a1b7650 | |||
350843af1e | |||
e04c4a8ee3 | |||
9d0c3f7535 | |||
9561d4da2e | |||
8296dcf946 | |||
e62e8b88c2 | |||
4388a248b9 | |||
f9cf0aaf23 | |||
dc9ee09838 | |||
7cb27283c5 | |||
10a9840b34 | |||
ce3e04bfe8 | |||
52f93f8f12 | |||
af9e1a122d | |||
9017b8ec11 | |||
bf4f687a2a | |||
9d4e6eb825 | |||
fba4afb7d2 | |||
8c74f784f7 | |||
69ca1ffa59 | |||
7d1b00877e | |||
1a48fed1f7 | |||
bc86359e63 | |||
f6822f1502 | |||
398c34d842 | |||
72a893be95 | |||
7b23b0e103 | |||
0ce7878042 | |||
da8483e6a6 | |||
16f70fd946 | |||
78aa898b37 | |||
b7f94a222d | |||
7bea2c26b8 | |||
7c178b8095 | |||
865f085094 | |||
28fe69fe94 | |||
232cf8d426 | |||
22e74983b0 | |||
c88dd2257a | |||
439d8d396f | |||
6d8086c09b | |||
e85f252f29 | |||
4b818ad51c | |||
c2518448a3 | |||
e7a8deed05 | |||
0ac599d20c | |||
7d7074e6b7 | |||
35ca34d07d | |||
90d7316b4c | |||
904b4e96d9 | |||
2c46c59a79 | |||
297ff86895 | |||
a154401424 | |||
ad2713fc00 | |||
6388cfaf40 | |||
167f38e342 | |||
919b3c3435 | |||
2e1ab22173 | |||
0a23563d7e | |||
37e4ec6364 | |||
6a8b947c2e | |||
a16ac37625 | |||
cf4c7826b2 | |||
a0a26f0a1e | |||
a921139a12 | |||
36da7b66c8 | |||
3aa87544eb | |||
6121fa505e | |||
a5ba5befd1 | |||
b7214a306c | |||
d7616e941a | |||
834a2f1e4d | |||
0e5f2fe748 | |||
e0bcb5e0b9 | |||
59d4890eae | |||
51da5360da | |||
2655aef28b | |||
45d3a7a124 | |||
662e4f8940 | |||
c06993cb8e | |||
a650f30ce8 | |||
0a924b2dcb | |||
89f62683ce | |||
143d88f3df | |||
d166a65422 | |||
dd268993b3 | |||
13a35b288f | |||
81e653d31b | |||
875ec8b8bd | |||
989df9b857 | |||
0829d3c176 | |||
ce64889b04 | |||
d3a0bfc5f6 | |||
e965c603d2 | |||
0e2fb8c96c | |||
2db1d84d3c | |||
12a1916007 | |||
b4526e9895 | |||
a2d867c860 | |||
05b1c37379 | |||
906cfe9268 | |||
3c8054faa7 | |||
c6c9046826 | |||
2bbbbf6fdd | |||
9cce4001af | |||
2e944cf2f4 | |||
2b0143775c | |||
49fec7d8f2 | |||
ca1ac2bb83 | |||
50b1a7e6b0 | |||
69ce2c0473 | |||
a3b446dbe7 | |||
1032d9927f | |||
12e8a50abc | |||
a4142097f8 | |||
b388ccb6f3 | |||
e011502b7e | |||
4f167cb836 | |||
9455d438e2 | |||
a356ecf9b6 | |||
066ac591ac | |||
62f006b89a | |||
ee75ff2753 | |||
e4c9defb70 | |||
bb102c1918 | |||
24ebe2946c | |||
ba82b1fa27 | |||
e3b145e7b7 | |||
242c3731ee | |||
5f7eee8eac | |||
1833f6ff0a | |||
e5fb954645 | |||
13f76dc020 | |||
b409bdcc73 | |||
8c3cb3f585 | |||
76a8b4df50 | |||
a03680311d | |||
6ee36cb5c7 | |||
5625326c65 | |||
b912419839 | |||
fe01ead023 | |||
229c105d0c | |||
b6e044345f | |||
d9906121e1 | |||
3e019f7f34 | |||
eb34cb6f27 | |||
3a3178bcb9 | |||
cdf6580ecc | |||
c42bc74f1f | |||
35fd79f577 | |||
4ef0682e5a | |||
d0b7047189 | |||
ae3f936b66 | |||
1ef492809b | |||
5bf9dd3a9d | |||
b18a66f66b | |||
1dadfdc699 | |||
14a3f51b73 | |||
96116aeaec | |||
7fd31b6a64 | |||
299bc0db13 | |||
4b9ccae442 | |||
079ce552e3 | |||
163684e3a9 | |||
f698f561c9 | |||
cb207f18a5 | |||
76a5cdc977 | |||
a82af1d2d1 | |||
ac7d51ad80 | |||
797a739c92 | |||
666b59b463 | |||
a83d9a070c | |||
7637377471 | |||
6515f88d92 | |||
92534b9c82 | |||
c12360daa8 | |||
3d28118f3e | |||
04adfde064 | |||
d8aabfd448 | |||
cf95870d9d | |||
55f8876bcc | |||
9fb66186f0 | |||
da8fe99ca4 | |||
20374fde36 | |||
5131f722a7 | |||
1ef0a1028f | |||
0fd1f04eda | |||
5c0ba5d06c | |||
d9532b6fa0 | |||
b96065514f | |||
0e9b8e4140 | |||
d1c773360f | |||
74538bba8d | |||
64c95e3811 | |||
33fd70291a | |||
0cb4bc951a | |||
3761ab9610 | |||
8c29bba108 | |||
4e41261237 | |||
77529ef3b1 | |||
0ba96adbbc | |||
7df277c0bc | |||
c94f7b10bd | |||
83a76f7d6f | |||
6c988241eb | |||
29145dfc2d | |||
4b74e8ec70 | |||
612012aff8 | |||
6ab60d0ccd | |||
6daed83d88 | |||
f25442c036 | |||
ffffd447f2 | |||
4b511c47f0 | |||
158d471a98 | |||
107a90395c | |||
ce5fd53822 | |||
810ca78215 | |||
eb945b3315 | |||
34f24fe331 | |||
743392017d | |||
15b877f005 | |||
0653769156 | |||
3ed319872a | |||
ee124671d8 | |||
1b4dabd37c | |||
fdd253f042 | |||
1a15fdd2f0 | |||
2c66280b3f | |||
778c39d947 | |||
fa15addfb2 | |||
afbb9474b7 | |||
0acb4f8cb1 | |||
08de0938a0 | |||
2c9b80c177 | |||
e8c19df8c9 | |||
7681003512 | |||
dba8db19cb | |||
d199cdf088 | |||
f2840c5ca4 | |||
1c7a0ba4e1 | |||
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 |
@ -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/
|
||||
build-zip/
|
||||
|
@ -1,4 +0,0 @@
|
||||
tests
|
||||
doc
|
||||
lib
|
||||
extras
|
5
.prettierrc
Normal file
5
.prettierrc
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"useTabs": true
|
||||
}
|
19
.travis.yml
Normal file
19
.travis.yml
Normal file
@ -0,0 +1,19 @@
|
||||
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
|
900
CHANGELOG.md
900
CHANGELOG.md
@ -1,13 +1,854 @@
|
||||
# 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/).
|
||||
|
||||
## [4.0.1] - 2016-04-26
|
||||
## v8.0.1 - 2018-10-20
|
||||
|
||||
* Update dockerignore to fix escSL bug [Tim Perry]
|
||||
|
||||
## v8.0.0 - 2018-10-19
|
||||
|
||||
* Support multicontainer local mode in resin push [Cameron Diver]
|
||||
* Stop accepting resin-compose.yml as a build composition definition [Cameron Diver]
|
||||
* Send push source packages as gzipped data [Cameron Diver]
|
||||
* Respect ignore files when tarring sources [Cameron Diver]
|
||||
* Check for correct architecture when preloading, instead of correct device type [Alexis Svinartchouk]
|
||||
* Default preload boolean parameters to false [Alexis Svinartchouk]
|
||||
* Correctly error out on failed remote builds [Cameron Diver]
|
||||
* Bump tsconfig target to es6 [Cameron Diver]
|
||||
|
||||
## v7.10.9 - 2018-10-18
|
||||
|
||||
* Update sdk references in wizzard.coffee [Scott Lowe]
|
||||
|
||||
## v7.10.8 - 2018-10-18
|
||||
|
||||
* Update sdk references in notes.coffee [Scott Lowe]
|
||||
* Update sdk references in device.coffee [Scott Lowe]
|
||||
|
||||
## v7.10.7 - 2018-10-18
|
||||
|
||||
* Update sdk sdk references in auth.coffee [Scott Lowe]
|
||||
|
||||
## v7.10.6 - 2018-10-03
|
||||
|
||||
* Fix formatting of preload examples [Tim Perry]
|
||||
|
||||
## v7.10.5 - 2018-09-25
|
||||
|
||||
* README: Fix typo [Lucian Buzzo]
|
||||
|
||||
## v7.10.4 - 2018-09-24
|
||||
|
||||
* Device: When registering, print the uuid [Pablo Carranza Velez]
|
||||
|
||||
## v7.10.3 - 2018-09-19
|
||||
|
||||
* Include --emulated in the example resin build parameters [Tim Perry]
|
||||
|
||||
## v7.10.2 - 2018-09-18
|
||||
|
||||
* Dependencies: Update resin-semver version to support Balena OS [Lucian Buzzo]
|
||||
|
||||
## v7.10.1 - 2018-09-11
|
||||
|
||||
* Stop Travis deploying to npm (now handled by concourse) [Tim Perry]
|
||||
|
||||
## v7.10.0 - 2018-09-11
|
||||
|
||||
* Update resin-cli-form to 2.x [Pagan Gazzard]
|
||||
|
||||
## v7.9.4 - 2018-09-10
|
||||
|
||||
* Device api keys are no longer used in the registration process [Theodor Gherzan]
|
||||
|
||||
## v7.9.3 - 2018-08-20
|
||||
|
||||
* Fix configuration hangs with some images by expanding the threadpool #952 [Tim Perry]
|
||||
|
||||
## v7.9.2 - 2018-08-15
|
||||
|
||||
* Add warning about re-enabling automatic updates #946 [Pagan Gazzard]
|
||||
|
||||
## v7.9.1 - 2018-08-15
|
||||
|
||||
* Fix errors in `getRequestStream` not being propogated #942 [Pagan Gazzard]
|
||||
|
||||
## v7.9.0 - 2018-08-09
|
||||
|
||||
* Support emulated and nocache options for remote builds #903 [Cameron Diver]
|
||||
|
||||
## v7.8.6 - 2018-08-09
|
||||
|
||||
* Fix bug where the sudo helper failed in os initialize #939 [Tim Perry]
|
||||
|
||||
## v7.8.5 - 2018-08-09
|
||||
|
||||
* Update .resin-sync.yml docs for local push and include example env vars #934 [Tim Perry]
|
||||
|
||||
## v7.8.4 - 2018-08-02
|
||||
|
||||
* Update klaw #936 [Tim Perry]
|
||||
|
||||
## v7.8.3 - 2018-07-25
|
||||
|
||||
* Follow links found during builds #931 [Tim Perry]
|
||||
|
||||
## v7.8.2 - 2018-07-25
|
||||
|
||||
* Update reconfix to fix volume signature errors in local configure #929 [Tim Perry]
|
||||
|
||||
## v7.8.1 - 2018-07-20
|
||||
|
||||
* Be explicit about how much initial history log tailing includes #930 [Tim Perry]
|
||||
|
||||
## v7.8.0 - 2018-07-20
|
||||
|
||||
* Add join/leave commands to promote and move devices between platforms #895 [Akis Kesoglou]
|
||||
|
||||
## v7.7.4 - 2018-07-17
|
||||
|
||||
* Update TypeScript to 2.8.1 #923 [Tim Perry]
|
||||
* Add --version options to os configure & config generate #923 [Tim Perry]
|
||||
* Update OS & config actions to the latest SDK #923 [Tim Perry]
|
||||
|
||||
## v7.7.3 - 2018-07-13
|
||||
|
||||
* Update the deploy key since npm invalidated the old one #927 [Tim Perry]
|
||||
|
||||
## v7.7.2 - 2018-07-13
|
||||
|
||||
* Pin ext2fs to 1.0.7 to avoid temporary deployment issues #926 [Tim Perry]
|
||||
|
||||
## v7.7.1 - 2018-07-12
|
||||
|
||||
* Update CLI to SDK v10 (include new API logs) #925 [Tim Perry]
|
||||
|
||||
## v7.7.0 - 2018-07-11
|
||||
|
||||
* Add --generate-device-api-key parameter to config generate #921 [Tim Perry]
|
||||
|
||||
## v7.6.2 - 2018-06-28
|
||||
|
||||
* Make local commands more resilient to unnamed containers #910 [Tim Perry]
|
||||
|
||||
## v7.6.1 - 2018-06-26
|
||||
|
||||
* Make sure 'resin push' is included in the docs #907 [Tim Perry]
|
||||
|
||||
## v7.6.0 - 2018-06-20
|
||||
|
||||
* Support pinned release preloading #896 [Cameron Diver]
|
||||
|
||||
## v7.5.2 - 2018-06-12
|
||||
|
||||
* Document Pyhton native build dependency #893 [Tim Perry]
|
||||
|
||||
## v7.5.1 - 2018-06-01
|
||||
|
||||
* Add a multicontainer caveat to the env var commands #887 [Tim Perry]
|
||||
|
||||
## v7.5.0 - 2018-05-31
|
||||
|
||||
* Update resin-compose-parse dependency version to 1.10.2 #883 [Ariel Flesler]
|
||||
|
||||
## v7.4.1 - 2018-05-24
|
||||
|
||||
* Update SDK in resin device(s) to ensure the dashboard URL is correct #879 [Tim Perry]
|
||||
|
||||
## v7.4.0 - 2018-05-10
|
||||
|
||||
* Add push command which starts a build on remote resin servers #868 [Cameron Diver]
|
||||
|
||||
## v7.3.8 - 2018-05-03
|
||||
|
||||
* Catch require errors and provide helpful instructions #874 [Tim Perry]
|
||||
* Inline the entire resin-cli-errors module #874 [Tim Perry]
|
||||
|
||||
## v7.3.7 - 2018-04-30
|
||||
|
||||
* Pin node types to v9.0.0 to avoid build errors with transient dependencies #871 [Cameron Diver]
|
||||
|
||||
## v7.3.6 - 2018-04-30
|
||||
|
||||
* Update resin-image-fs to stop non-config commands failing in node 10 #870 [Tim Perry]
|
||||
|
||||
## v7.3.5 - 2018-04-18
|
||||
|
||||
* Don't show Docker container status from devices, as it can be wrong #858 [Tim Perry]
|
||||
|
||||
## v7.3.4 - 2018-04-18
|
||||
|
||||
* Include resin compose schemas in the standalone build #865 [Tim Perry]
|
||||
|
||||
## v7.3.3 - 2018-04-17
|
||||
|
||||
* Don't report lots of user input errors #861 [Tim Perry]
|
||||
* Include Sentry breadcrumbs for context in error reports #861 [Tim Perry]
|
||||
* Update to Sentry 2.x #861 [Tim Perry]
|
||||
|
||||
## v7.3.2 - 2018-04-16
|
||||
|
||||
* Update Dockerode to fix local push issue in standalone builds #862 [Tim Perry]
|
||||
|
||||
## v7.3.1 - 2018-04-13
|
||||
|
||||
* Update resin-compose-parse to v1.8.1 to fix a problem parsing ports #849 [Pablo Carranza Velez]
|
||||
|
||||
## v7.3.0 - 2018-04-12
|
||||
|
||||
* Add 'api-key generate' command #854 [Tim Perry]
|
||||
|
||||
## v7.2.4 - 2018-04-10
|
||||
|
||||
* Explicitly depend on tar-stream #852 [Tim Perry]
|
||||
* Correct documented node version requirement to 6+ #852 [Tim Perry]
|
||||
|
||||
## v7.2.3 - 2018-04-06
|
||||
|
||||
* Add a fast build script to package.json #847 [Cameron Diver]
|
||||
|
||||
## v7.2.2 - 2018-04-04
|
||||
|
||||
* Throw a clear error when logging in with an invalid token #846 [Tim Perry]
|
||||
|
||||
## v7.2.1 - 2018-03-29
|
||||
|
||||
* Update docker-qemu-transpose to avoid the broken 0.4.1 release #839 [Tim Perry]
|
||||
|
||||
## v7.2.0 - 2018-03-29
|
||||
|
||||
* Do not require a login for builds #835 [Tim Perry]
|
||||
* Allow (experimental!) login with API keys #835 [Tim Perry]
|
||||
|
||||
## v7.1.6 - 2018-03-29
|
||||
|
||||
* Fix build emulation for multi-stage builds #838 [Tim Perry]
|
||||
|
||||
## v7.1.5 - 2018-03-27
|
||||
|
||||
* Fix crash when an app is not specified for build command #834 [Akis Kesoglou]
|
||||
|
||||
## v7.1.4 - 2018-03-26
|
||||
|
||||
* Upgrade resin-sync to pull in the fix for #824 #832 [Tim Perry]
|
||||
|
||||
## v7.1.3 - 2018-03-23
|
||||
|
||||
* Prefix all pine options with '$' in preload to avoid pine warnings. #831 [Alexis Svinartchouk]
|
||||
|
||||
## v7.1.2 - 2018-03-23
|
||||
|
||||
* Update resin-preload to 6.2.0 and resin-sdk to 9.0.0-beta16 #830 [Alexis Svinartchouk]
|
||||
|
||||
## v7.1.1 - 2018-03-22
|
||||
|
||||
* Remove explicit anchor links in CLI docs #827 [Zach Walchuk]
|
||||
|
||||
## v7.1.0 - 2018-03-22
|
||||
|
||||
* Warn early if deploying a multicontainer project to an incompatible app #818 [Akis Kesoglou]
|
||||
* Add legacy deploy method back #818 [Akis Kesoglou]
|
||||
|
||||
## v7.0.7 - 2018-03-20
|
||||
|
||||
* Update resin-preload to 6.1.2 #821 [Alexis Svinartchouk]
|
||||
|
||||
## v7.0.6 - 2018-03-20
|
||||
|
||||
* Make sure image name is all lowercase #815 [Akis Kesoglou]
|
||||
* Improve handling of build log output #815 [Akis Kesoglou]
|
||||
|
||||
## v7.0.5 - 2018-03-15
|
||||
|
||||
* Add bash completions #801 [Ronald McCollam]
|
||||
|
||||
## v7.0.4 - 2018-03-15
|
||||
|
||||
* Generate consistent working anchors for both our md output & resin docs #813 [Tim Perry]
|
||||
|
||||
## v7.0.3 - 2018-03-15
|
||||
|
||||
* Fix getting window size when there’s no TTY attached #812 [Akis Kesoglou]
|
||||
|
||||
## v7.0.2 - 2018-03-13
|
||||
|
||||
* Update full CLI docs with recent installation improvements too #807 [Tim Perry]
|
||||
|
||||
## v7.0.1 - 2018-03-12
|
||||
|
||||
* Recommend using unsafe-prem to avoid permission issues on install #805 [Tim Perry]
|
||||
* Remove unnecessary resin-cli-auth dependency #805 [Tim Perry]
|
||||
|
||||
## v7.0.0 - 2018-03-09
|
||||
|
||||
* Add docker-compose-aware builds and deployments #792 [Akis Kesoglou]
|
||||
* *BREAKING*: Remove support for plugins entirely #792 [Tim Perry]
|
||||
* Update dashboard login to use the multicontainer SDK #792 [Alexis Svinartchouk]
|
||||
* Multicontainer preload: Update resin-preload to 6.0.0-beta4 #792 [Alexis Svinartchouk]
|
||||
* Update the keys action to use the multicontainer SDK #792 [Alexis Svinartchouk]
|
||||
* Require multicontainer SDK #792 [Alexis Svinartchouk]
|
||||
|
||||
## v6.13.5 - 2018-03-07
|
||||
|
||||
* Fix prettier configuration to avoid linting errors #802 [Tim Perry]
|
||||
|
||||
## v6.13.4 - 2018-02-22
|
||||
|
||||
* Fix issue where emulated builds broke Docker `ENV` commands #796 [Gergely Imreh]
|
||||
|
||||
## v6.13.3 - 2018-02-20
|
||||
|
||||
* Tweak TS & add missing deps that may cause build failures in some envs #793 [Tim Perry]
|
||||
|
||||
## v6.13.2 - 2018-02-20
|
||||
|
||||
* Ensure login does not wait for the browser process to close #794 [Tim Perry]
|
||||
|
||||
## v6.13.1 - 2018-02-07
|
||||
|
||||
* Move to the correct coffeescript (no hyphen) dependency #786 [Tim Perry]
|
||||
* Add typings for 'ent' #786 [Tim Perry]
|
||||
|
||||
## v6.13.0 - 2018-02-06
|
||||
|
||||
* Add support for Balena in local ssh #777 [Tim Perry]
|
||||
|
||||
## v6.12.9 - 2018-02-05
|
||||
|
||||
* Switch back to upstream global-tunnel-ng #781 [Alexis Svinartchouk]
|
||||
|
||||
## v6.12.8 - 2018-02-03
|
||||
|
||||
* Fix uuid params being parsed a numbers #774 [Thodoris Greasidis]
|
||||
|
||||
## v6.12.7 - 2018-01-30
|
||||
|
||||
* Add 'or mounted resinOS image' #767 [MoranF]
|
||||
|
||||
## v6.12.6 - 2018-01-29
|
||||
|
||||
* Don't use the deprecated 'os configure' format in internal calls #759 [Tim Perry]
|
||||
|
||||
## v6.12.5 - 2018-01-11
|
||||
|
||||
* Fix breakage in deploy command from recent TS conversion #754 [Tim Perry]
|
||||
|
||||
## v6.12.4 - 2018-01-10
|
||||
|
||||
* Start using Prettier #753 [Tim Perry]
|
||||
|
||||
## v6.12.3 - 2018-01-09
|
||||
|
||||
* Lint TypeScript and CoffeeScript with resin-lint #743 [Tim Perry]
|
||||
* Move documentation generation to TypeScript #743 [Tim Perry]
|
||||
* Convert most of utils to TypeScript #743 [Tim Perry]
|
||||
|
||||
## v6.12.2 - 2018-01-09
|
||||
|
||||
* Convert windows paths to posix when passing to tar #748 [Andrew Shirley]
|
||||
|
||||
## v6.12.1 - 2018-01-02
|
||||
|
||||
* Fix deprecation warning for os configure, when passing a bare UUID #744 [Tim Perry]
|
||||
|
||||
## v6.12.0 - 2017-12-19
|
||||
|
||||
* Add ssh option for direct host OS access #737 [Andreas Fitzek]
|
||||
|
||||
## v6.11.0 - 2017-12-18
|
||||
|
||||
* Fix docs generation when building on windows #729 [Tim Perry]
|
||||
* Autodeploy built standalone binaries for all platforms to github #729 [Tim Perry]
|
||||
* Package the CLI into a standalone runnable binary #729 [Tim Perry]
|
||||
* Move from open to opn #729 [Tim Perry]
|
||||
|
||||
## v6.10.3 - 2017-12-15
|
||||
|
||||
* Ensure logout works even with invalid credentials, or if not logged in #730 [Tim Perry]
|
||||
|
||||
## 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.
|
||||
|
||||
## [4.0.0] - 2016-04-26
|
||||
## v4.0.0 - 2016-04-26
|
||||
|
||||
### Added
|
||||
|
||||
@ -15,22 +856,23 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
### Changed
|
||||
|
||||
- `resin sync` no longer supports the `exec after rsync` feature
|
||||
- Upgraded `resin-sync` and `resin-settings-client`
|
||||
- Remove `resin sync` `exec` option.
|
||||
- Upgrade `resin-sync` to v2.0.1.
|
||||
- Upgrade `resin-sdk` to v5.3.0.
|
||||
|
||||
## [3.0.2] - 2016-04-08
|
||||
## v3.0.2 - 2016-04-08
|
||||
|
||||
### Changed
|
||||
|
||||
- Fix `os configure` command not working with shorter uuids.
|
||||
|
||||
## [3.0.1] - 2016-03-29
|
||||
## v3.0.1 - 2016-03-29
|
||||
|
||||
### Changed
|
||||
|
||||
- Log Mixpanel events based on the matching command signature.
|
||||
|
||||
## [3.0.0] - 2016-03-28
|
||||
## v3.0.0 - 2016-03-28
|
||||
|
||||
### Added
|
||||
|
||||
@ -44,20 +886,20 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
- Force update alert to always be shown.
|
||||
- Only throw "Invalid 2FA code" if we're sure that's the cause during `login`.
|
||||
|
||||
## [2.7.0] - 2016-03-07
|
||||
## 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
|
||||
|
||||
@ -72,7 +914,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
|
||||
|
||||
@ -96,7 +938,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
|
||||
|
||||
@ -108,7 +950,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
|
||||
|
||||
@ -119,7 +961,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
|
||||
|
||||
@ -131,13 +973,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
|
||||
|
||||
@ -148,13 +990,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
|
||||
|
||||
@ -180,7 +1022,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
|
||||
|
||||
@ -213,21 +1055,3 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
### Removed
|
||||
|
||||
- Remove outdated information from README.
|
||||
|
||||
[4.0.1]: https://github.com/resin-io/resin-cli/compare/v4.0.0...v4.0.1
|
||||
[4.0.0]: https://github.com/resin-io/resin-cli/compare/v3.0.2...v4.0.0
|
||||
[3.0.2]: https://github.com/resin-io/resin-cli/compare/v3.0.1...v3.0.2
|
||||
[3.0.1]: https://github.com/resin-io/resin-cli/compare/v3.0.0...v3.0.1
|
||||
[3.0.0]: https://github.com/resin-io/resin-cli/compare/v2.7.0...v3.0.0
|
||||
[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
|
||||
|
81
README.md
81
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,36 +10,85 @@ 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) (>= v6)
|
||||
- [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.
|
||||
|
||||
```sh
|
||||
$ npm install --global --production resin-cli
|
||||
$ npm install resin-cli -g --production --unsafe-perm
|
||||
```
|
||||
|
||||
### List available commands
|
||||
`--unsafe-perm` is only required on systems where the global install directory is not user-writable.
|
||||
This allows npm install steps to download and save prebuilt native binaries. You may be able to omit it,
|
||||
especially if you're using a user-managed node install such as [nvm](https://github.com/creationix/nvm).
|
||||
|
||||
In some environments, this process will need to build native modules. This may require a more complex build
|
||||
environment, and notably requires Python 2.7. If you hit any problems with this, we recommend you try the
|
||||
alternative standalone install below instead.
|
||||
|
||||
### 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 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!](https://github.com/resin-io/resin-cli/issues/new)
|
||||
|
||||
### Login
|
||||
|
||||
```sh
|
||||
$ resin help
|
||||
$ resin login
|
||||
```
|
||||
|
||||
### Run the quickstart wizard
|
||||
_(Typically useful, but not strictly required for all commands)_
|
||||
|
||||
```sh
|
||||
$ resin quickstart
|
||||
```
|
||||
### Run commands
|
||||
|
||||
Plugins
|
||||
-------
|
||||
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`.
|
||||
|
||||
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!
|
||||
### Bash completions
|
||||
|
||||
Optionally you can enable tab completions for the bash shell, enabling the shell to provide additional context and automatically complete arguments to`resin`. To enable bash completions, copy the `resin-completion.bash` file to the default bash completions directory (usually `/etc/bash_completion.d/`) or append it to the end of `~/.bash_completion`.
|
||||
|
||||
FAQ
|
||||
---
|
||||
@ -60,16 +109,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
|
||||
-------
|
||||
|
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')
|
36
automation/build-bin.ts
Executable file
36
automation/build-bin.ts
Executable file
@ -0,0 +1,36 @@
|
||||
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'),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
14
automation/capitanodoc/doc-types.d.ts
vendored
Normal file
14
automation/capitanodoc/doc-types.d.ts
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
import { CommandDefinition } from 'capitano';
|
||||
|
||||
export interface Document {
|
||||
title: string;
|
||||
introduction: string;
|
||||
categories: Category[];
|
||||
}
|
||||
|
||||
export interface Category {
|
||||
title: string;
|
||||
commands: CommandDefinition[];
|
||||
}
|
||||
|
||||
export { CommandDefinition as Command };
|
34
automation/capitanodoc/index.ts
Normal file
34
automation/capitanodoc/index.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import capitanodoc = require('../../capitanodoc');
|
||||
import * as _ from 'lodash';
|
||||
import * as path from 'path';
|
||||
import * as markdown from './markdown';
|
||||
import { Document, Category } from './doc-types';
|
||||
|
||||
const result = <Document>{};
|
||||
result.title = capitanodoc.title;
|
||||
result.introduction = capitanodoc.introduction;
|
||||
result.categories = [];
|
||||
|
||||
for (let commandCategory of capitanodoc.categories) {
|
||||
const category = <Category>{};
|
||||
category.title = commandCategory.title;
|
||||
category.commands = [];
|
||||
|
||||
for (let file of commandCategory.files) {
|
||||
// tslint:disable-next-line:no-var-requires
|
||||
const actions: any = require(path.join(process.cwd(), file));
|
||||
|
||||
if (actions.signature) {
|
||||
category.commands.push(_.omit(actions, 'action'));
|
||||
} else {
|
||||
for (let actionName of Object.keys(actions)) {
|
||||
const actionCommand = actions[actionName];
|
||||
category.commands.push(_.omit(actionCommand, 'action'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.categories.push(category);
|
||||
}
|
||||
|
||||
console.log(markdown.render(result));
|
76
automation/capitanodoc/markdown.ts
Normal file
76
automation/capitanodoc/markdown.ts
Normal file
@ -0,0 +1,76 @@
|
||||
import * as _ from 'lodash';
|
||||
import * as ent from 'ent';
|
||||
import * as utils from './utils';
|
||||
import { Document, Category, Command } from './doc-types';
|
||||
|
||||
export function renderCommand(command: Command) {
|
||||
let result = `## ${ent.encode(command.signature)}\n\n${command.help}\n`;
|
||||
|
||||
if (!_.isEmpty(command.options)) {
|
||||
result += '\n### Options';
|
||||
|
||||
for (let option of command.options!) {
|
||||
result += `\n\n#### ${utils.parseSignature(option)}\n\n${
|
||||
option.description
|
||||
}`;
|
||||
}
|
||||
|
||||
result += '\n';
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function renderCategory(category: Category) {
|
||||
let result = `# ${category.title}\n`;
|
||||
|
||||
for (let command of category.commands) {
|
||||
result += `\n${renderCommand(command)}`;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function getAnchor(command: Command) {
|
||||
return (
|
||||
'#' +
|
||||
command.signature
|
||||
.replace(/\s/g, '-')
|
||||
.replace(/</g, '-')
|
||||
.replace(/>/g, '-')
|
||||
.replace(/\[/g, '-')
|
||||
.replace(/\]/g, '-')
|
||||
.replace(/-+/g, '-')
|
||||
.replace(/\.\.\./g, '')
|
||||
.replace(/\|/g, '')
|
||||
.toLowerCase()
|
||||
);
|
||||
}
|
||||
|
||||
export function renderToc(categories: Category[]) {
|
||||
let result = `# Table of contents\n`;
|
||||
|
||||
for (let category of categories) {
|
||||
result += `\n- ${category.title}\n\n`;
|
||||
|
||||
for (let command of category.commands) {
|
||||
result += `\t- [${ent.encode(command.signature)}](${getAnchor(
|
||||
command,
|
||||
)})\n`;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function render(doc: Document) {
|
||||
let result = `# ${doc.title}\n\n${doc.introduction}\n\n${renderToc(
|
||||
doc.categories,
|
||||
)}`;
|
||||
|
||||
for (let category of doc.categories) {
|
||||
result += `\n${renderCategory(category)}`;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
33
automation/capitanodoc/utils.ts
Normal file
33
automation/capitanodoc/utils.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { OptionDefinition } from 'capitano';
|
||||
import * as _ from 'lodash';
|
||||
import * as ent from 'ent';
|
||||
|
||||
export function getOptionPrefix(signature: string) {
|
||||
if (signature.length > 1) {
|
||||
return '--';
|
||||
} else {
|
||||
return '-';
|
||||
}
|
||||
}
|
||||
|
||||
export function getOptionSignature(signature: string) {
|
||||
return `${getOptionPrefix(signature)}${signature}`;
|
||||
}
|
||||
|
||||
export function parseSignature(option: OptionDefinition) {
|
||||
let result = getOptionSignature(option.signature);
|
||||
|
||||
if (_.isArray(option.alias)) {
|
||||
for (let alias of option.alias) {
|
||||
result += `, ${getOptionSignature(alias)}`;
|
||||
}
|
||||
} else if (_.isString(option.alias)) {
|
||||
result += `, ${getOptionSignature(option.alias)}`;
|
||||
}
|
||||
|
||||
if (option.parameter) {
|
||||
result += ` <${option.parameter}>`;
|
||||
}
|
||||
|
||||
return ent.encode(result);
|
||||
}
|
38
automation/custom-types.d.ts
vendored
Normal file
38
automation/custom-types.d.ts
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
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;
|
||||
}
|
67
automation/deploy-bin.ts
Normal file
67
automation/deploy-bin.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import * as Promise from 'bluebird';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import * as fs from 'fs-extra';
|
||||
import * as mkdirp from 'mkdirp';
|
||||
import * as publishRelease from 'publish-release';
|
||||
import * as archiver from 'archiver';
|
||||
import * as packageJSON from '../package.json';
|
||||
|
||||
const publishReleaseAsync = Promise.promisify(publishRelease);
|
||||
const mkdirpAsync = Promise.promisify<string | null, string>(mkdirp);
|
||||
|
||||
const { GITHUB_TOKEN } = process.env;
|
||||
const ROOT = path.join(__dirname, '..');
|
||||
|
||||
const version = 'v' + packageJSON.version;
|
||||
const outputFile = path.join(
|
||||
ROOT,
|
||||
'build-zip',
|
||||
`resin-cli-${version}-${os.platform()}-${os.arch()}.zip`,
|
||||
);
|
||||
|
||||
mkdirpAsync(path.dirname(outputFile))
|
||||
.then(
|
||||
() =>
|
||||
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: <string>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);
|
||||
});
|
16
automation/tsconfig.json
Normal file
16
automation/tsconfig.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es2015",
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"preserveConstEnums": true,
|
||||
"removeComments": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": [
|
||||
"./**/*.ts",
|
||||
"../typings/*.d.ts"
|
||||
]
|
||||
}
|
@ -1,2 +1,7 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// We boost the threadpool size as ext2fs can deadlock with some
|
||||
// operations otherwise, if the pool runs out.
|
||||
process.env.UV_THREADPOOL_SIZE = '64';
|
||||
|
||||
require('../build/app');
|
||||
|
@ -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,241 +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.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.inject = {
|
||||
signature: 'config inject <file>',
|
||||
description: 'inject a device configuration file',
|
||||
help: 'Use this command to inject a config.json file to a provisioned device\n\nExamples:\n\n $ resin config inject my/config.json --type raspberry-pi\n $ resin config inject my/config.json --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, fs, umount, visuals;
|
||||
Promise = require('bluebird');
|
||||
config = require('resin-config-json');
|
||||
visuals = require('resin-cli-visuals');
|
||||
umount = Promise.promisifyAll(require('umount'));
|
||||
fs = Promise.promisifyAll(require('fs'));
|
||||
return Promise["try"](function() {
|
||||
return options.drive || visuals.drive('Select the device drive');
|
||||
}).tap(umount.umountAsync).then(function(drive) {
|
||||
return fs.readFileAsync(params.file, 'utf8').then(JSON.parse).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',
|
||||
description: 'generate a config.json file',
|
||||
help: 'Use this command to generate a config.json for a device or application\n\nExamples:\n\n $ resin config generate --device 7cf02a6\n $ resin config generate --device 7cf02a6 --output config.json\n $ resin config generate --app MyApp\n $ resin config generate --app MyApp --output config.json',
|
||||
options: [
|
||||
commandOptions.optionalApplication, commandOptions.optionalDevice, {
|
||||
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');
|
||||
if ((options.device == null) && (options.application == null)) {
|
||||
throw new Error('You have to pass either a device or an application.\n\nSee the help page for examples:\n\n $ resin help config generate');
|
||||
}
|
||||
return Promise["try"](function() {
|
||||
if (options.device != null) {
|
||||
return resin.models.device.get(options.device);
|
||||
}
|
||||
return resin.models.application.get(options.application);
|
||||
}).then(function(resource) {
|
||||
return resin.models.device.getManifestBySlug(resource.device_type).get('options').then(form.run).then(function(answers) {
|
||||
if (resource.uuid != null) {
|
||||
return deviceConfig.getByDevice(resource.uuid, answers);
|
||||
}
|
||||
return deviceConfig.getByApplication(resource.app_name, answers);
|
||||
});
|
||||
}).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,87 +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 getSubShellCommand;
|
||||
|
||||
getSubShellCommand = function(command) {
|
||||
var os;
|
||||
os = require('os');
|
||||
if (os.platform() === 'win32') {
|
||||
return {
|
||||
program: 'sh',
|
||||
args: ['-c', command]
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
program: '/bin/sh',
|
||||
args: ['-c', command]
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
signature: 'enter <uuid>',
|
||||
description: '(beta) get a shell into the running app container of a device',
|
||||
help: 'Use this command to get a shell into the running application container of\nyour device.\n\nExamples:\n\n $ resin enter 7cf02a6\n $ resin enter 7cf02a6 --port 8080',
|
||||
permission: 'user',
|
||||
primary: true,
|
||||
options: [
|
||||
{
|
||||
signature: 'port',
|
||||
parameter: 'port',
|
||||
description: 'ssh port',
|
||||
alias: 't'
|
||||
}
|
||||
],
|
||||
action: function(params, options, done) {
|
||||
var Promise, child_process, resin, settings;
|
||||
child_process = require('child_process');
|
||||
Promise = require('bluebird');
|
||||
resin = require('resin-sdk');
|
||||
settings = require('resin-settings-client');
|
||||
if (options.port == null) {
|
||||
options.port = 22;
|
||||
}
|
||||
console.info("Connecting with: " + params.uuid);
|
||||
return Promise.props({
|
||||
isOnline: resin.models.device.isOnline(params.uuid),
|
||||
username: resin.auth.whoami(),
|
||||
uuid: resin.models.device.get(params.uuid).get('uuid'),
|
||||
containerId: resin.models.device.getApplicationInfo(params.uuid).get('containerId')
|
||||
}).then(function(arg) {
|
||||
var containerId, isOnline, username, uuid;
|
||||
isOnline = arg.isOnline, username = arg.username, uuid = arg.uuid, containerId = arg.containerId;
|
||||
if (!isOnline) {
|
||||
throw new Error('Device is not online');
|
||||
}
|
||||
if (containerId == null) {
|
||||
throw new Error('Did not find running application container');
|
||||
}
|
||||
return Promise["try"](function() {
|
||||
var command, spawn, subShellCommand;
|
||||
command = "ssh -t -p " + options.port + " " + username + "@" + (settings.get('proxyUrl')) + " enter " + uuid + " " + containerId;
|
||||
subShellCommand = getSubShellCommand(command);
|
||||
return spawn = child_process.spawn(subShellCommand.program, subShellCommand.args, {
|
||||
stdio: 'inherit'
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
}).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,37 +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 = {
|
||||
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'),
|
||||
sync: require('./sync'),
|
||||
ssh: require('./ssh')
|
||||
};
|
||||
|
||||
}).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,87 +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 getSubShellCommand;
|
||||
|
||||
getSubShellCommand = function(command) {
|
||||
var os;
|
||||
os = require('os');
|
||||
if (os.platform() === 'win32') {
|
||||
return {
|
||||
program: 'sh',
|
||||
args: ['-c', command]
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
program: '/bin/sh',
|
||||
args: ['-c', command]
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
signature: 'ssh <uuid>',
|
||||
description: '(beta) get a shell into the running app container of a device',
|
||||
help: 'Use this command to get a shell into the running application container of\nyour device.\n\nExamples:\n\n $ resin ssh 7cf02a6\n $ resin ssh 7cf02a6 --port 8080',
|
||||
permission: 'user',
|
||||
primary: true,
|
||||
options: [
|
||||
{
|
||||
signature: 'port',
|
||||
parameter: 'port',
|
||||
description: 'ssh gateway port',
|
||||
alias: 't'
|
||||
}
|
||||
],
|
||||
action: function(params, options, done) {
|
||||
var Promise, child_process, resin, settings;
|
||||
child_process = require('child_process');
|
||||
Promise = require('bluebird');
|
||||
resin = require('resin-sdk');
|
||||
settings = require('resin-settings-client');
|
||||
if (options.port == null) {
|
||||
options.port = 22;
|
||||
}
|
||||
console.info("Connecting with: " + params.uuid);
|
||||
return Promise.props({
|
||||
isOnline: resin.models.device.isOnline(params.uuid),
|
||||
username: resin.auth.whoami(),
|
||||
uuid: resin.models.device.get(params.uuid).get('uuid'),
|
||||
containerId: resin.models.device.getApplicationInfo(params.uuid).get('containerId')
|
||||
}).then(function(arg) {
|
||||
var containerId, isOnline, username, uuid;
|
||||
isOnline = arg.isOnline, username = arg.username, uuid = arg.uuid, containerId = arg.containerId;
|
||||
if (!isOnline) {
|
||||
throw new Error('Device is not online');
|
||||
}
|
||||
if (containerId == null) {
|
||||
throw new Error('Did not find running application container');
|
||||
}
|
||||
return Promise["try"](function() {
|
||||
var command, spawn, subShellCommand;
|
||||
command = "ssh -t -o LogLevel=QUIET -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p " + options.port + " " + username + "@ssh." + (settings.get('proxyUrl')) + " enter " + uuid + " " + containerId;
|
||||
subShellCommand = getSubShellCommand(command);
|
||||
return spawn = child_process.spawn(subShellCommand.program, subShellCommand.args, {
|
||||
stdio: 'inherit'
|
||||
});
|
||||
});
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
}).call(this);
|
@ -1,72 +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: 'sync [source]',
|
||||
description: '(beta) sync your changes with a device',
|
||||
help: 'Use this command to sync your local changes to a certain device on the fly.\n\nThe `source` argument can be either a device uuid or an application name.\n\nYou can save all the options mentioned below in a `resin-sync.yml` file,\nby using the same option names as keys. For example:\n\n $ cat $PWD/resin-sync.yml\n source: src/\n before: \'echo Hello\'\n ignore:\n - .git\n - node_modules/\n progress: true\n\nNotice that explicitly passed command options override the ones set in the configuration file.\n\nExamples:\n\n $ resin sync MyApp\n $ resin sync 7cf02a6\n $ resin sync 7cf02a6 --port 8080\n $ resin sync 7cf02a6 --ignore foo,bar',
|
||||
permission: 'user',
|
||||
primary: true,
|
||||
options: [
|
||||
{
|
||||
signature: 'source',
|
||||
parameter: 'path',
|
||||
description: 'custom source path',
|
||||
alias: 's'
|
||||
}, {
|
||||
signature: 'ignore',
|
||||
parameter: 'paths',
|
||||
description: 'comma delimited paths to ignore when syncing',
|
||||
alias: 'i'
|
||||
}, {
|
||||
signature: 'before',
|
||||
parameter: 'command',
|
||||
description: 'execute a command before syncing',
|
||||
alias: 'b'
|
||||
}, {
|
||||
signature: 'progress',
|
||||
boolean: true,
|
||||
description: 'show progress',
|
||||
alias: 'p'
|
||||
}, {
|
||||
signature: 'port',
|
||||
parameter: 'port',
|
||||
description: 'ssh port',
|
||||
alias: 't'
|
||||
}
|
||||
],
|
||||
action: function(params, options, done) {
|
||||
var patterns, resin, resinSync;
|
||||
resin = require('resin-sdk');
|
||||
resinSync = require('resin-sync');
|
||||
patterns = require('../utils/patterns');
|
||||
if (options.ignore != null) {
|
||||
options.ignore = options.ignore.split(',');
|
||||
}
|
||||
return resin.models.device.has(params.source).then(function(isValidUUID) {
|
||||
if (isValidUUID) {
|
||||
return params.source;
|
||||
}
|
||||
return patterns.inferOrSelectDevice(params.source);
|
||||
}).then(function(uuid) {
|
||||
return resinSync.sync(uuid, options);
|
||||
}).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);
|
150
build/app.js
150
build/app.js
@ -1,150 +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.inject);
|
||||
|
||||
capitano.command(actions.config.reconfigure);
|
||||
|
||||
capitano.command(actions.config.generate);
|
||||
|
||||
capitano.command(actions.settings.list);
|
||||
|
||||
capitano.command(actions.logs);
|
||||
|
||||
capitano.command(actions.sync);
|
||||
|
||||
capitano.command(actions.ssh);
|
||||
|
||||
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,41 +0,0 @@
|
||||
(function() {
|
||||
var Mixpanel, Promise, _, capitanoState, packageJSON, resin;
|
||||
|
||||
_ = require('lodash');
|
||||
|
||||
Mixpanel = require('mixpanel');
|
||||
|
||||
Promise = require('bluebird');
|
||||
|
||||
resin = require('resin-sdk');
|
||||
|
||||
capitanoState = Promise.promisifyAll(require('capitano').state);
|
||||
|
||||
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 capitanoState.getMatchCommandAsync(capitanoCommand.command).then(function(command) {
|
||||
return data.mixpanel.track("[CLI] " + (command.signature.toString()), {
|
||||
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,225 +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(error) {
|
||||
return resin.auth.logout().then(function() {
|
||||
if (error.name === 'ResinRequestError' && error.statusCode === 401) {
|
||||
throw new Error('Invalid two factor authentication code');
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
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.inferOrSelectDevice = function(applicationName) {
|
||||
return Promise["try"](function() {
|
||||
if (applicationName != null) {
|
||||
return resin.models.device.getAllByApplication(applicationName);
|
||||
}
|
||||
return resin.models.device.getAll();
|
||||
}).then(function(devices) {
|
||||
if (_.isEmpty(devices)) {
|
||||
throw new Error('You don\'t have any devices');
|
||||
}
|
||||
if (devices.length === 1) {
|
||||
return _.first(devices).uuid;
|
||||
}
|
||||
return form.ask({
|
||||
message: 'Select a device',
|
||||
type: 'list',
|
||||
choices: _.map(devices, function(device) {
|
||||
return {
|
||||
name: (device.name || 'Untitled') + " (" + (device.uuid.slice(0, 7)) + ")",
|
||||
value: device.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,50 +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,
|
||||
updateCheckInterval: 0
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
@ -1,96 +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": "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": "Settings",
|
||||
"files": [
|
||||
"lib/actions/settings.coffee"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Wizard",
|
||||
"files": [
|
||||
"lib/actions/wizard.coffee"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
154
capitanodoc.ts
Normal file
154
capitanodoc.ts
Normal file
@ -0,0 +1,154 @@
|
||||
export = {
|
||||
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).
|
||||
|
||||
## Install the CLI
|
||||
|
||||
### Npm install
|
||||
|
||||
The best supported way to install the CLI is from npm:
|
||||
|
||||
$ npm install resin-cli -g --production --unsafe-perm
|
||||
|
||||
\`--unsafe-perm\` is only required on systems where the global install directory is not user-writable.
|
||||
This allows npm install steps to download and save prebuilt native binaries. You may be able to omit it,
|
||||
especially if you're using a user-managed node install such as [nvm](https://github.com/creationix/nvm).
|
||||
|
||||
### Standalone install
|
||||
|
||||
Alternatively, if you don't have a node or 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 works 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!
|
||||
|
||||
## Getting started
|
||||
|
||||
Once you have the CLI installed, you'll need to log in, so it can access everything in your resin.io account.
|
||||
|
||||
To authenticate yourself, run:
|
||||
|
||||
$ resin login
|
||||
|
||||
You now 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: 'Api keys',
|
||||
files: [ 'build/actions/api-key.js' ],
|
||||
},
|
||||
{
|
||||
title: 'Application',
|
||||
files: [ 'build/actions/app.js' ]
|
||||
},
|
||||
{
|
||||
title: 'Authentication',
|
||||
files: [ 'build/actions/auth.js' ]
|
||||
},
|
||||
{
|
||||
title: 'Device',
|
||||
files: [ 'build/actions/device.js' ]
|
||||
},
|
||||
{
|
||||
title: 'Environment Variables',
|
||||
files: [ 'build/actions/environment-variables.js' ]
|
||||
},
|
||||
{
|
||||
title: 'Help',
|
||||
files: [ 'build/actions/help.js' ]
|
||||
},
|
||||
{
|
||||
title: 'Information',
|
||||
files: [ 'build/actions/info.js' ]
|
||||
},
|
||||
{
|
||||
title: 'Keys',
|
||||
files: [ 'build/actions/keys.js' ]
|
||||
},
|
||||
{
|
||||
title: 'Logs',
|
||||
files: [ 'build/actions/logs.js' ]
|
||||
},
|
||||
{
|
||||
title: 'Sync',
|
||||
files: [ 'build/actions/sync.js' ]
|
||||
},
|
||||
{
|
||||
title: 'SSH',
|
||||
files: [ 'build/actions/ssh.js' ]
|
||||
},
|
||||
{
|
||||
title: 'Notes',
|
||||
files: [ 'build/actions/notes.js' ]
|
||||
},
|
||||
{
|
||||
title: 'OS',
|
||||
files: [ 'build/actions/os.js' ]
|
||||
},
|
||||
{
|
||||
title: 'Config',
|
||||
files: [ 'build/actions/config.js' ]
|
||||
},
|
||||
{
|
||||
title: 'Preload',
|
||||
files: [ 'build/actions/preload.js' ]
|
||||
},
|
||||
{
|
||||
title: 'Push',
|
||||
files: [ 'build/actions/push.js' ]
|
||||
},
|
||||
{
|
||||
title: 'Settings',
|
||||
files: [ 'build/actions/settings.js' ]
|
||||
},
|
||||
{
|
||||
title: 'Wizard',
|
||||
files: [ 'build/actions/wizard.js' ]
|
||||
},
|
||||
{
|
||||
title: 'Local',
|
||||
files: [ 'build/actions/local/index.js' ]
|
||||
},
|
||||
{
|
||||
title: 'Deploy',
|
||||
files: [
|
||||
'build/actions/build.js',
|
||||
'build/actions/deploy.js'
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Utilities',
|
||||
files: [ 'build/actions/util.js' ]
|
||||
},
|
||||
]
|
||||
};
|
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.
|
1057
doc/cli.markdown
1057
doc/cli.markdown
File diff suppressed because it is too large
Load Diff
@ -1,46 +0,0 @@
|
||||
_ = require('lodash')
|
||||
path = require('path')
|
||||
capitanodoc = require('../../capitanodoc.json')
|
||||
markdown = require('./markdown')
|
||||
|
||||
result = {}
|
||||
result.title = capitanodoc.title
|
||||
result.introduction = capitanodoc.introduction
|
||||
result.categories = []
|
||||
|
||||
for commandCategory in capitanodoc.categories
|
||||
category = {}
|
||||
category.title = commandCategory.title
|
||||
category.commands = []
|
||||
|
||||
for file in commandCategory.files
|
||||
actions = require(path.join(process.cwd(), file))
|
||||
|
||||
if actions.signature?
|
||||
category.commands.push(_.omit(actions, 'action'))
|
||||
else
|
||||
for actionName, actionCommand of actions
|
||||
category.commands.push(_.omit(actionCommand, 'action'))
|
||||
|
||||
result.categories.push(category)
|
||||
|
||||
result.toc = _.cloneDeep(result.categories)
|
||||
result.toc = _.map result.toc, (category) ->
|
||||
category.commands = _.map category.commands, (command) ->
|
||||
return {
|
||||
signature: command.signature
|
||||
anchor: '#' + command.signature
|
||||
.replace(/\s/g,'-')
|
||||
.replace(/</g, '60-')
|
||||
.replace(/>/g, '-62-')
|
||||
.replace(/\[/g, '')
|
||||
.replace(/\]/g, '-')
|
||||
.replace(/--/g, '-')
|
||||
.replace(/\.\.\./g, '')
|
||||
.replace(/\|/g, '')
|
||||
.toLowerCase()
|
||||
}
|
||||
|
||||
return category
|
||||
|
||||
console.log(markdown.display(result))
|
@ -1,66 +0,0 @@
|
||||
_ = require('lodash')
|
||||
ent = require('ent')
|
||||
utils = require('./utils')
|
||||
|
||||
exports.command = (command) ->
|
||||
result = """
|
||||
## #{ent.encode(command.signature)}
|
||||
|
||||
#{command.help}\n
|
||||
"""
|
||||
|
||||
if not _.isEmpty(command.options)
|
||||
result += '\n### Options'
|
||||
|
||||
for option in command.options
|
||||
result += """
|
||||
\n\n#### #{utils.parseSignature(option)}
|
||||
|
||||
#{option.description}
|
||||
"""
|
||||
|
||||
result += '\n'
|
||||
|
||||
return result
|
||||
|
||||
exports.category = (category) ->
|
||||
result = """
|
||||
# #{category.title}\n
|
||||
"""
|
||||
|
||||
for command in category.commands
|
||||
result += '\n' + exports.command(command)
|
||||
|
||||
return result
|
||||
|
||||
exports.toc = (toc) ->
|
||||
result = '''
|
||||
# Table of contents\n
|
||||
'''
|
||||
|
||||
for category in toc
|
||||
|
||||
result += """
|
||||
\n- #{category.title}\n\n
|
||||
"""
|
||||
|
||||
for command in category.commands
|
||||
result += """
|
||||
\t- [#{ent.encode(command.signature)}](#{command.anchor})\n
|
||||
"""
|
||||
|
||||
return result
|
||||
|
||||
exports.display = (doc) ->
|
||||
result = """
|
||||
# #{doc.title}
|
||||
|
||||
#{doc.introduction}
|
||||
|
||||
#{exports.toc(doc.toc)}
|
||||
"""
|
||||
|
||||
for category in doc.categories
|
||||
result += '\n' + exports.category(category)
|
||||
|
||||
return result
|
@ -1,26 +0,0 @@
|
||||
_ = require('lodash')
|
||||
ent = require('ent')
|
||||
|
||||
exports.getOptionPrefix = (signature) ->
|
||||
if signature.length > 1
|
||||
return '--'
|
||||
else
|
||||
return '-'
|
||||
|
||||
exports.getOptionSignature = (signature) ->
|
||||
return "#{exports.getOptionPrefix(signature)}#{signature}"
|
||||
|
||||
exports.parseSignature = (option) ->
|
||||
result = exports.getOptionSignature(option.signature)
|
||||
|
||||
if not _.isEmpty(option.alias)
|
||||
if _.isString(option.alias)
|
||||
result += ", #{exports.getOptionSignature(option.alias)}"
|
||||
else
|
||||
for alias in option.alias
|
||||
result += ", #{exports.getOptionSignature(option.alias)}"
|
||||
|
||||
if option.parameter?
|
||||
result += " <#{option.parameter}>"
|
||||
|
||||
return ent.encode(result)
|
@ -1,34 +1,40 @@
|
||||
path = require('path')
|
||||
gulp = require('gulp')
|
||||
coffee = require('gulp-coffee')
|
||||
coffeelint = require('gulp-coffeelint')
|
||||
inlinesource = require('gulp-inline-source')
|
||||
mocha = require('gulp-mocha')
|
||||
shell = require('gulp-shell')
|
||||
packageJSON = require('./package.json')
|
||||
|
||||
OPTIONS =
|
||||
config:
|
||||
coffeelint: path.join(__dirname, 'coffeelint.json')
|
||||
files:
|
||||
coffee: [ 'lib/**/*.coffee', 'gulpfile.coffee' ]
|
||||
app: [ 'lib/**/*.coffee', '!lib/**/*.spec.coffee' ]
|
||||
app: 'lib/**/*.coffee'
|
||||
tests: 'tests/**/*.spec.coffee'
|
||||
pages: 'lib/auth/pages/*.ejs'
|
||||
directories:
|
||||
build: 'build/'
|
||||
|
||||
gulp.task 'coffee', [ 'lint' ], ->
|
||||
gulp.task 'pages', ->
|
||||
gulp.src(OPTIONS.files.pages)
|
||||
.pipe(inlinesource())
|
||||
.pipe(gulp.dest('build/auth/pages'))
|
||||
|
||||
gulp.task 'coffee', ->
|
||||
gulp.src(OPTIONS.files.app)
|
||||
.pipe(coffee())
|
||||
.pipe(coffee(bare: true, header: true))
|
||||
.pipe(gulp.dest(OPTIONS.directories.build))
|
||||
|
||||
gulp.task 'lint', ->
|
||||
gulp.src(OPTIONS.files.coffee)
|
||||
.pipe(coffeelint({
|
||||
optFile: OPTIONS.config.coffeelint
|
||||
gulp.task 'test', ->
|
||||
gulp.src(OPTIONS.files.tests, read: false)
|
||||
.pipe(mocha({
|
||||
reporter: 'spec'
|
||||
}))
|
||||
.pipe(coffeelint.reporter())
|
||||
|
||||
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' ])
|
||||
|
36
lib/actions/api-key.ts
Normal file
36
lib/actions/api-key.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { CommandDefinition } from 'capitano';
|
||||
import { stripIndent } from 'common-tags';
|
||||
|
||||
export const generate: CommandDefinition<{
|
||||
name: string;
|
||||
}> = {
|
||||
signature: 'api-key generate <name>',
|
||||
description: 'Generate a new API key with the given name',
|
||||
help: stripIndent`
|
||||
This command generates a new API key for the current user, with the given
|
||||
name. The key will be logged to the console.
|
||||
|
||||
This key can be used to log into the CLI using 'resin login --token <key>',
|
||||
or to authenticate requests to the API with an 'Authorization: Bearer <key>' header.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin api-key generate "Jenkins Key"
|
||||
`,
|
||||
async action(params, _options, done) {
|
||||
const resin = (await import('resin-sdk')).fromSharedOptions();
|
||||
|
||||
resin.models.apiKey
|
||||
.create(params.name)
|
||||
.then(key => {
|
||||
console.log(stripIndent`
|
||||
Registered api key '${params.name}':
|
||||
|
||||
${key}
|
||||
|
||||
This key will not be shown again, so please save it now.
|
||||
`);
|
||||
})
|
||||
.finally(done);
|
||||
},
|
||||
};
|
@ -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
|
||||
@ -53,7 +52,7 @@ exports.create =
|
||||
# https://github.com/resin-io/resin-cli/issues/30
|
||||
resin.models.application.has(params.name).then (hasApplication) ->
|
||||
if hasApplication
|
||||
throw new Error('You already have an application with that name!')
|
||||
patterns.exitWithExpectedError('You already have an application with that name!')
|
||||
|
||||
.then ->
|
||||
return options.type or patterns.selectDeviceType()
|
||||
@ -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.
|
||||
@ -27,7 +27,7 @@ exports.login =
|
||||
|
||||
- Credentials: using email/password and 2FA.
|
||||
|
||||
- Token: using the authentication token from the preferences page.
|
||||
- Token: using a session token or API key (experimental) from the preferences page.
|
||||
|
||||
Examples:
|
||||
|
||||
@ -40,7 +40,7 @@ exports.login =
|
||||
options: [
|
||||
{
|
||||
signature: 'token'
|
||||
description: 'auth token'
|
||||
description: 'session token or API key (experimental)'
|
||||
parameter: 'token'
|
||||
alias: 't'
|
||||
}
|
||||
@ -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').fromSharedOptions()
|
||||
auth = require('../auth')
|
||||
form = require('resin-cli-form')
|
||||
patterns = require('../utils/patterns')
|
||||
messages = require('../utils/messages')
|
||||
@ -85,10 +84,15 @@ exports.login =
|
||||
return Promise.try ->
|
||||
return options.token if _.isString(options.token)
|
||||
return form.ask
|
||||
message: 'Token (from the preferences page)'
|
||||
message: 'Session token or API key (experimental) from the preferences page'
|
||||
name: 'token'
|
||||
type: 'input'
|
||||
.then(resin.auth.loginWithToken)
|
||||
.tap ->
|
||||
resin.auth.whoami()
|
||||
.then (username) ->
|
||||
if !username
|
||||
patterns.exitWithExpectedError('Token authentication failed')
|
||||
else if options.credentials
|
||||
return patterns.authenticate(options)
|
||||
else if options.web
|
||||
@ -98,7 +102,8 @@ exports.login =
|
||||
return patterns.askLoginType().then (loginType) ->
|
||||
|
||||
if loginType is 'register'
|
||||
return capitano.runAsync('signup')
|
||||
{ runCommand } = require('../utils/helpers')
|
||||
return runCommand('signup')
|
||||
|
||||
options[loginType] = true
|
||||
return login(options)
|
||||
@ -112,11 +117,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
|
||||
|
||||
@ -134,9 +135,8 @@ exports.logout =
|
||||
|
||||
$ resin logout
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
resin = require('resin-sdk')
|
||||
resin = require('resin-sdk').fromSharedOptions()
|
||||
resin.auth.logout().nodeify(done)
|
||||
|
||||
exports.signup =
|
||||
@ -150,15 +150,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').fromSharedOptions()
|
||||
form = require('resin-cli-form')
|
||||
validation = require('../utils/validation')
|
||||
|
||||
@ -170,10 +169,6 @@ exports.signup =
|
||||
name: 'email'
|
||||
type: 'input'
|
||||
validate: validation.validateEmail
|
||||
,
|
||||
message: 'Username:'
|
||||
name: 'username'
|
||||
type: 'input'
|
||||
,
|
||||
message: 'Password:'
|
||||
name: 'password'
|
||||
@ -198,7 +193,7 @@ exports.whoami =
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
Promise = require('bluebird')
|
||||
resin = require('resin-sdk')
|
||||
resin = require('resin-sdk').fromSharedOptions()
|
||||
visuals = require('resin-cli-visuals')
|
||||
|
||||
Promise.props
|
||||
|
145
lib/actions/build.coffee
Normal file
145
lib/actions/build.coffee
Normal file
@ -0,0 +1,145 @@
|
||||
# Imported here because it's needed for the setup
|
||||
# of this action
|
||||
Promise = require('bluebird')
|
||||
dockerUtils = require('../utils/docker')
|
||||
compose = require('../utils/compose')
|
||||
|
||||
###
|
||||
Opts must be an object with the following keys:
|
||||
|
||||
app: the app this build is for (optional)
|
||||
arch: the architecture to build for
|
||||
deviceType: the device type to build for
|
||||
buildEmulated
|
||||
buildOpts: arguments to forward to docker build command
|
||||
###
|
||||
buildProject = (docker, logger, composeOpts, opts) ->
|
||||
compose.loadProject(
|
||||
logger
|
||||
composeOpts.projectPath
|
||||
composeOpts.projectName
|
||||
)
|
||||
.then (project) ->
|
||||
appType = opts.app?.application_type?[0]
|
||||
if appType? and project.descriptors.length > 1 and not appType.supports_multicontainer
|
||||
logger.logWarn(
|
||||
'Target application does not support multiple containers.\n' +
|
||||
'Continuing with build, but you will not be able to deploy.'
|
||||
)
|
||||
|
||||
compose.buildProject(
|
||||
docker
|
||||
logger
|
||||
project.path
|
||||
project.name
|
||||
project.composition
|
||||
opts.arch
|
||||
opts.deviceType
|
||||
opts.buildEmulated
|
||||
opts.buildOpts
|
||||
composeOpts.inlineLogs
|
||||
)
|
||||
.then ->
|
||||
logger.logSuccess('Build succeeded!')
|
||||
.tapCatch (e) ->
|
||||
logger.logError('Build failed')
|
||||
|
||||
module.exports =
|
||||
signature: 'build [source]'
|
||||
description: 'Build a single image or a multicontainer project locally'
|
||||
primary: true
|
||||
help: '''
|
||||
Use this command to build an image or a complete multicontainer project
|
||||
with the provided docker daemon.
|
||||
|
||||
You must provide either an application or a device-type/architecture
|
||||
pair to use the resin Dockerfile pre-processor
|
||||
(e.g. Dockerfile.template -> Dockerfile).
|
||||
|
||||
This command will look into the given source directory (or the current working
|
||||
directory if one isn't specified) for a compose file. If one is found, this
|
||||
command will build each service defined in the compose file. If a compose file
|
||||
isn't found, the command will look for a Dockerfile, and if yet that isn't found,
|
||||
it will try to generate one.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin build
|
||||
$ resin build ./source/
|
||||
$ resin build --deviceType raspberrypi3 --arch armhf --emulated
|
||||
$ 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 compose.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) ->
|
||||
# compositions with many services trigger misleading warnings
|
||||
require('events').defaultMaxListeners = 1000
|
||||
|
||||
{ exitWithExpectedError } = require('../utils/patterns')
|
||||
helpers = require('../utils/helpers')
|
||||
Logger = require('../utils/logger')
|
||||
|
||||
logger = new Logger()
|
||||
|
||||
logger.logDebug('Parsing input...')
|
||||
|
||||
Promise.try ->
|
||||
# `build` accepts `[source]` as a parameter, but compose expects it
|
||||
# as an option. swap them here
|
||||
options.source ?= params.source
|
||||
delete params.source
|
||||
|
||||
{ application, arch, deviceType } = options
|
||||
|
||||
if (not (arch? and deviceType?) and not application?) or (application? and (arch? or deviceType?))
|
||||
exitWithExpectedError('You must specify either an application or an arch/deviceType pair to build for')
|
||||
|
||||
if arch? and deviceType?
|
||||
[ undefined, arch, deviceType ]
|
||||
else
|
||||
Promise.join(
|
||||
helpers.getApplication(application)
|
||||
helpers.getArchAndDeviceType(application)
|
||||
(app, { arch, device_type }) ->
|
||||
app.arch = arch
|
||||
app.device_type = device_type
|
||||
return app
|
||||
)
|
||||
.then (app) ->
|
||||
[ app, app.arch, app.device_type ]
|
||||
|
||||
.then ([ app, arch, deviceType ]) ->
|
||||
Promise.join(
|
||||
dockerUtils.getDocker(options)
|
||||
dockerUtils.generateBuildOpts(options)
|
||||
compose.generateOpts(options)
|
||||
(docker, buildOpts, composeOpts) ->
|
||||
buildProject(docker, logger, composeOpts, {
|
||||
app
|
||||
arch
|
||||
deviceType
|
||||
buildEmulated: !!options.emulated
|
||||
buildOpts
|
||||
})
|
||||
)
|
||||
.asCallback(done)
|
@ -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,34 @@ 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.optionalOsVersion =
|
||||
signature: 'version'
|
||||
description: 'a resinOS version'
|
||||
parameter: 'version'
|
||||
|
||||
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 +83,29 @@ 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'
|
||||
|
||||
exports.hostOSAccess =
|
||||
signature: 'host'
|
||||
boolean: true
|
||||
description: 'access host OS (for devices with Resin OS >= 2.7.5)'
|
||||
alias: 's'
|
||||
|
@ -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,12 +15,13 @@ limitations under the License.
|
||||
###
|
||||
|
||||
commandOptions = require('./command-options')
|
||||
{ normalizeUuidProp } = require('../utils/normalization')
|
||||
|
||||
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:
|
||||
|
||||
@ -30,7 +31,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'
|
||||
@ -48,12 +49,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) ->
|
||||
@ -64,7 +65,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:
|
||||
|
||||
@ -75,7 +76,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'
|
||||
@ -94,18 +95,18 @@ 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 ->
|
||||
@ -116,7 +117,8 @@ exports.inject =
|
||||
signature: 'config inject <file>'
|
||||
description: 'inject a device configuration file'
|
||||
help: '''
|
||||
Use this command to inject a config.json file to a provisioned device
|
||||
Use this command to inject a config.json file to the mounted filesystem
|
||||
(e.g. SD card or mounted resinOS image) of a provisioned device"
|
||||
|
||||
Examples:
|
||||
|
||||
@ -126,7 +128,7 @@ exports.inject =
|
||||
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'
|
||||
@ -144,14 +146,14 @@ exports.inject =
|
||||
Promise = require('bluebird')
|
||||
config = require('resin-config-json')
|
||||
visuals = require('resin-cli-visuals')
|
||||
umount = Promise.promisifyAll(require('umount'))
|
||||
fs = Promise.promisifyAll(require('fs'))
|
||||
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(umount.umountAsync)
|
||||
.tap(umountAsync)
|
||||
.then (drive) ->
|
||||
fs.readFileAsync(params.file, 'utf8').then(JSON.parse).then (configJSON) ->
|
||||
readFileAsync(params.file, 'utf8').then(JSON.parse).then (configJSON) ->
|
||||
return config.write(drive, options.type, configJSON)
|
||||
.tap ->
|
||||
console.info('Done')
|
||||
@ -172,7 +174,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'
|
||||
@ -196,21 +198,21 @@ 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'))
|
||||
{ runCommand } = require('../utils/helpers')
|
||||
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}"
|
||||
configureCommand = "os configure #{drive} --device #{uuid}"
|
||||
if options.advanced
|
||||
configureCommand += ' --advanced'
|
||||
return capitano.runAsync(configureCommand)
|
||||
return runCommand(configureCommand)
|
||||
.then ->
|
||||
console.info('Done')
|
||||
.nodeify(done)
|
||||
@ -219,37 +221,79 @@ exports.generate =
|
||||
signature: 'config generate'
|
||||
description: 'generate a config.json file'
|
||||
help: '''
|
||||
Use this command to generate a config.json for a device or application
|
||||
Use this command to generate a config.json for a device or application.
|
||||
|
||||
Calling this command without --version is not recommended, and may fail in
|
||||
future releases if the OS version cannot be inferred.
|
||||
|
||||
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 --device 7cf02a6
|
||||
$ resin config generate --device 7cf02a6 --output config.json
|
||||
$ resin config generate --app MyApp
|
||||
$ resin config generate --app MyApp --output config.json
|
||||
$ resin config generate --device 7cf02a6 --version 2.12.7
|
||||
$ resin config generate --device 7cf02a6 --version 2.12.7 --generate-device-api-key
|
||||
$ resin config generate --device 7cf02a6 --version 2.12.7 --device-api-key <existingDeviceKey>
|
||||
$ resin config generate --device 7cf02a6 --version 2.12.7 --output config.json
|
||||
$ resin config generate --app MyApp --version 2.12.7
|
||||
$ resin config generate --app MyApp --version 2.12.7 --output config.json
|
||||
$ resin config generate --app MyApp --version 2.12.7 \
|
||||
--network wifi --wifiSsid mySsid --wifiKey abcdefgh --appUpdatePollInterval 1
|
||||
'''
|
||||
options: [
|
||||
commandOptions.optionalOsVersion
|
||||
commandOptions.optionalApplication
|
||||
commandOptions.optionalDevice
|
||||
commandOptions.optionalDeviceApiKey
|
||||
{
|
||||
signature: 'generate-device-api-key'
|
||||
description: 'generate a fresh device key for the device'
|
||||
boolean: true
|
||||
}
|
||||
{
|
||||
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) ->
|
||||
normalizeUuidProp(options, 'device')
|
||||
Promise = require('bluebird')
|
||||
fs = Promise.promisifyAll(require('fs'))
|
||||
resin = require('resin-sdk')
|
||||
_ = require('lodash')
|
||||
writeFileAsync = Promise.promisify(require('fs').writeFile)
|
||||
resin = require('resin-sdk').fromSharedOptions()
|
||||
form = require('resin-cli-form')
|
||||
deviceConfig = require('resin-device-config')
|
||||
prettyjson = require('prettyjson')
|
||||
|
||||
{ generateDeviceConfig, generateApplicationConfig } = require('../utils/config')
|
||||
{ exitWithExpectedError } = require('../utils/patterns')
|
||||
|
||||
if not options.device? and not options.application?
|
||||
throw new Error '''
|
||||
exitWithExpectedError '''
|
||||
You have to pass either a device or an application.
|
||||
|
||||
See the help page for examples:
|
||||
@ -263,15 +307,22 @@ exports.generate =
|
||||
return resin.models.application.get(options.application)
|
||||
.then (resource) ->
|
||||
resin.models.device.getManifestBySlug(resource.device_type)
|
||||
.get('options')
|
||||
.then(form.run)
|
||||
.then (answers) ->
|
||||
if resource.uuid?
|
||||
return deviceConfig.getByDevice(resource.uuid, answers)
|
||||
return deviceConfig.getByApplication(resource.app_name, answers)
|
||||
.get('options')
|
||||
.then (formOptions) ->
|
||||
# Pass params as an override: if there is any param with exactly the same name as a
|
||||
# required option, that value is used (and the corresponding question is not asked)
|
||||
form.run(formOptions, override: options)
|
||||
.then (answers) ->
|
||||
answers.version = options.version
|
||||
|
||||
if resource.uuid?
|
||||
generateDeviceConfig(resource, options.deviceApiKey || options['generate-device-api-key'], 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)
|
||||
|
220
lib/actions/deploy.coffee
Normal file
220
lib/actions/deploy.coffee
Normal file
@ -0,0 +1,220 @@
|
||||
# Imported here because it's needed for the setup
|
||||
# of this action
|
||||
Promise = require('bluebird')
|
||||
dockerUtils = require('../utils/docker')
|
||||
compose = require('../utils/compose')
|
||||
|
||||
###
|
||||
Opts must be an object with the following keys:
|
||||
|
||||
app: the application instance to deploy to
|
||||
image: the image to deploy; optional
|
||||
shouldPerformBuild
|
||||
shouldUploadLogs
|
||||
buildEmulated
|
||||
buildOpts: arguments to forward to docker build command
|
||||
###
|
||||
deployProject = (docker, logger, composeOpts, opts) ->
|
||||
_ = require('lodash')
|
||||
doodles = require('resin-doodles')
|
||||
sdk = require('resin-sdk').fromSharedOptions()
|
||||
|
||||
compose.loadProject(
|
||||
logger
|
||||
composeOpts.projectPath
|
||||
composeOpts.projectName
|
||||
opts.image
|
||||
)
|
||||
.then (project) ->
|
||||
if project.descriptors.length > 1 and !opts.app.application_type?[0]?.supports_multicontainer
|
||||
throw new Error('Target application does not support multiple containers. Aborting!')
|
||||
|
||||
# find which services use images that already exist locally
|
||||
Promise.map project.descriptors, (d) ->
|
||||
# unconditionally build (or pull) if explicitly requested
|
||||
return d if opts.shouldPerformBuild
|
||||
docker.getImage(d.image.tag ? d.image).inspect()
|
||||
.return(d.serviceName)
|
||||
.catchReturn()
|
||||
.filter (d) -> !!d
|
||||
.then (servicesToSkip) ->
|
||||
# multibuild takes in a composition and always attempts to
|
||||
# build or pull all services. we workaround that here by
|
||||
# passing a modified composition.
|
||||
compositionToBuild = _.cloneDeep(project.composition)
|
||||
compositionToBuild.services = _.omit(compositionToBuild.services, servicesToSkip)
|
||||
if _.size(compositionToBuild.services) is 0
|
||||
logger.logInfo('Everything is up to date (use --build to force a rebuild)')
|
||||
return {}
|
||||
compose.buildProject(
|
||||
docker
|
||||
logger
|
||||
project.path
|
||||
project.name
|
||||
compositionToBuild
|
||||
opts.app.arch
|
||||
opts.app.device_type
|
||||
opts.buildEmulated
|
||||
opts.buildOpts
|
||||
composeOpts.inlineLogs
|
||||
)
|
||||
.then (builtImages) ->
|
||||
_.keyBy(builtImages, 'serviceName')
|
||||
.then (builtImages) ->
|
||||
project.descriptors.map (d) ->
|
||||
builtImages[d.serviceName] ? {
|
||||
serviceName: d.serviceName,
|
||||
name: d.image.tag ? d.image
|
||||
logs: 'Build skipped; image for service already exists.'
|
||||
props: {}
|
||||
}
|
||||
.then (images) ->
|
||||
if opts.app.application_type?[0]?.is_legacy
|
||||
chalk = require('chalk')
|
||||
legacyDeploy = require('../utils/deploy-legacy')
|
||||
|
||||
msg = chalk.yellow('Target application requires legacy deploy method.')
|
||||
logger.logWarn(msg)
|
||||
|
||||
return Promise.join(
|
||||
docker
|
||||
logger
|
||||
sdk.auth.getToken()
|
||||
sdk.auth.whoami()
|
||||
sdk.settings.get('resinUrl')
|
||||
{
|
||||
appName: opts.app.app_name
|
||||
imageName: images[0].name
|
||||
buildLogs: images[0].logs
|
||||
shouldUploadLogs: opts.shouldUploadLogs
|
||||
}
|
||||
legacyDeploy
|
||||
)
|
||||
.then (releaseId) ->
|
||||
sdk.models.release.get(releaseId, $select: [ 'commit' ])
|
||||
Promise.join(
|
||||
sdk.auth.getUserId()
|
||||
sdk.auth.getToken()
|
||||
sdk.settings.get('apiUrl')
|
||||
(userId, auth, apiEndpoint) ->
|
||||
compose.deployProject(
|
||||
docker
|
||||
logger
|
||||
project.composition
|
||||
images
|
||||
opts.app.id
|
||||
userId
|
||||
"Bearer #{auth}"
|
||||
apiEndpoint
|
||||
!opts.shouldUploadLogs
|
||||
)
|
||||
)
|
||||
.then (release) ->
|
||||
logger.logSuccess('Deploy succeeded!')
|
||||
logger.logSuccess("Release: #{release.commit}")
|
||||
console.log()
|
||||
console.log(doodles.getDoodle()) # Show charlie
|
||||
console.log()
|
||||
.tapCatch (e) ->
|
||||
logger.logError('Deploy failed')
|
||||
|
||||
module.exports =
|
||||
signature: 'deploy <appName> [image]'
|
||||
description: 'Deploy a single image or a multicontainer project to a resin.io application'
|
||||
help: '''
|
||||
Use this command to deploy an image or a complete multicontainer project
|
||||
to an application, optionally building it first.
|
||||
|
||||
Usage: `deploy <appName> ([image] | --build [--source build-dir])`
|
||||
|
||||
Unless an image is specified, this command will look into the current directory
|
||||
(or the one specified by --source) for a compose file. If one is found, this
|
||||
command will deploy each service defined in the compose file, building it first
|
||||
if an image for it doesn't exist. If a compose file isn't found, the command
|
||||
will look for a Dockerfile, and if yet that isn't found, it will try to
|
||||
generate one.
|
||||
|
||||
To deploy to an app on which you're a collaborator, use
|
||||
`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
|
||||
$ resin deploy myApp --build --source myBuildDir/
|
||||
$ resin deploy myApp myApp/myImage
|
||||
'''
|
||||
permission: 'user'
|
||||
primary: true
|
||||
options: dockerUtils.appendOptions compose.appendOptions [
|
||||
{
|
||||
signature: 'source'
|
||||
parameter: 'source'
|
||||
description: 'Specify an alternate source directory; default is the working directory'
|
||||
alias: 's'
|
||||
},
|
||||
{
|
||||
signature: 'build'
|
||||
boolean: true
|
||||
description: 'Force a rebuild before deploy'
|
||||
alias: 'b'
|
||||
},
|
||||
{
|
||||
signature: 'nologupload'
|
||||
description: "Don't upload build logs to the dashboard with image (if building)"
|
||||
boolean: true
|
||||
}
|
||||
]
|
||||
action: (params, options, done) ->
|
||||
# compositions with many services trigger misleading warnings
|
||||
require('events').defaultMaxListeners = 1000
|
||||
|
||||
helpers = require('../utils/helpers')
|
||||
Logger = require('../utils/logger')
|
||||
|
||||
logger = new Logger()
|
||||
|
||||
logger.logDebug('Parsing input...')
|
||||
|
||||
Promise.try ->
|
||||
{ appName, image } = params
|
||||
|
||||
# look into "resin build" options if appName isn't given
|
||||
appName = options.application if not appName?
|
||||
delete options.application
|
||||
|
||||
if not appName?
|
||||
throw new Error('Please specify the name of the application to deploy')
|
||||
|
||||
if image? and options.build
|
||||
throw new Error('Build option is not applicable when specifying an image')
|
||||
|
||||
Promise.join(
|
||||
helpers.getApplication(appName)
|
||||
helpers.getArchAndDeviceType(appName)
|
||||
(app, { arch, device_type }) ->
|
||||
app.arch = arch
|
||||
app.device_type = device_type
|
||||
return app
|
||||
)
|
||||
.then (app) ->
|
||||
[ app, image, !!options.build, !options.nologupload ]
|
||||
|
||||
.then ([ app, image, shouldPerformBuild, shouldUploadLogs ]) ->
|
||||
Promise.join(
|
||||
dockerUtils.getDocker(options)
|
||||
dockerUtils.generateBuildOpts(options)
|
||||
compose.generateOpts(options)
|
||||
(docker, buildOpts, composeOpts) ->
|
||||
deployProject(docker, logger, composeOpts, {
|
||||
app
|
||||
image
|
||||
shouldPerformBuild
|
||||
shouldUploadLogs
|
||||
buildEmulated: !!options.emulated
|
||||
buildOpts
|
||||
})
|
||||
)
|
||||
.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.
|
||||
@ -15,6 +15,12 @@ limitations under the License.
|
||||
###
|
||||
|
||||
commandOptions = require('./command-options')
|
||||
_ = require('lodash')
|
||||
{ normalizeUuidProp } = require('../utils/normalization')
|
||||
|
||||
expandForAppName = {
|
||||
$expand: belongs_to__application: $select: 'app_name'
|
||||
}
|
||||
|
||||
exports.list =
|
||||
signature: 'devices'
|
||||
@ -36,28 +42,32 @@ exports.list =
|
||||
primary: true
|
||||
action: (params, options, done) ->
|
||||
Promise = require('bluebird')
|
||||
_ = require('lodash')
|
||||
resin = require('resin-sdk')
|
||||
resin = require('resin-sdk').fromSharedOptions()
|
||||
visuals = require('resin-cli-visuals')
|
||||
|
||||
Promise.try ->
|
||||
if options.application?
|
||||
return resin.models.device.getAllByApplication(options.application)
|
||||
return resin.models.device.getAll()
|
||||
return resin.models.device.getAllByApplication(options.application, expandForAppName)
|
||||
return resin.models.device.getAll(expandForAppName)
|
||||
|
||||
.tap (devices) ->
|
||||
devices = _.map devices, (device) ->
|
||||
device.dashboard_url = resin.models.device.getDashboardUrl(device.uuid)
|
||||
device.application_name = device.belongs_to__application[0].app_name
|
||||
device.uuid = device.uuid.slice(0, 7)
|
||||
return device
|
||||
|
||||
console.log visuals.table.horizontal devices, [
|
||||
'id'
|
||||
'uuid'
|
||||
'name'
|
||||
'device_name'
|
||||
'device_type'
|
||||
'application_name'
|
||||
'status'
|
||||
'is_online'
|
||||
'supervisor_version'
|
||||
'os_version'
|
||||
'dashboard_url'
|
||||
]
|
||||
.nodeify(done)
|
||||
|
||||
@ -74,16 +84,20 @@ exports.info =
|
||||
permission: 'user'
|
||||
primary: true
|
||||
action: (params, options, done) ->
|
||||
resin = require('resin-sdk')
|
||||
normalizeUuidProp(params)
|
||||
resin = require('resin-sdk').fromSharedOptions()
|
||||
visuals = require('resin-cli-visuals')
|
||||
|
||||
resin.models.device.get(params.uuid).then (device) ->
|
||||
|
||||
resin.models.device.get(params.uuid, expandForAppName)
|
||||
.then (device) ->
|
||||
resin.models.device.getStatus(device).then (status) ->
|
||||
device.status = status
|
||||
device.dashboard_url = resin.models.device.getDashboardUrl(device.uuid)
|
||||
device.application_name = device.belongs_to__application[0].app_name
|
||||
device.commit = device.is_on__commit
|
||||
|
||||
console.log visuals.table.vertical device, [
|
||||
"$#{device.name}$"
|
||||
"$#{device.device_name}$"
|
||||
'id'
|
||||
'device_type'
|
||||
'status'
|
||||
@ -96,9 +110,32 @@ 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').fromSharedOptions()
|
||||
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'
|
||||
@ -108,25 +145,28 @@ exports.register =
|
||||
Examples:
|
||||
|
||||
$ resin device register MyApp
|
||||
$ resin device register MyApp --uuid <uuid>
|
||||
'''
|
||||
permission: 'user'
|
||||
options: [
|
||||
signature: 'uuid'
|
||||
description: 'custom uuid'
|
||||
parameter: 'uuid'
|
||||
alias: 'u'
|
||||
{
|
||||
signature: 'uuid'
|
||||
description: 'custom uuid'
|
||||
parameter: 'uuid'
|
||||
alias: 'u'
|
||||
}
|
||||
]
|
||||
action: (params, options, done) ->
|
||||
Promise = require('bluebird')
|
||||
resin = require('resin-sdk')
|
||||
resin = require('resin-sdk').fromSharedOptions()
|
||||
|
||||
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()
|
||||
(application, uuid) ->
|
||||
console.info("Registering to #{application.app_name}: #{uuid}")
|
||||
return resin.models.device.register(application.app_name, uuid)
|
||||
return resin.models.device.register(application.id, uuid)
|
||||
)
|
||||
.get('uuid')
|
||||
.nodeify(done)
|
||||
|
||||
@ -147,7 +187,8 @@ exports.remove =
|
||||
options: [ commandOptions.yes ]
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
resin = require('resin-sdk')
|
||||
normalizeUuidProp(params)
|
||||
resin = require('resin-sdk').fromSharedOptions()
|
||||
patterns = require('../utils/patterns')
|
||||
|
||||
patterns.confirm(options.yes, 'Are you sure you want to delete the device?').then ->
|
||||
@ -168,7 +209,8 @@ exports.identify =
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
resin = require('resin-sdk')
|
||||
normalizeUuidProp(params)
|
||||
resin = require('resin-sdk').fromSharedOptions()
|
||||
resin.models.device.identify(params.uuid).nodeify(done)
|
||||
|
||||
exports.reboot =
|
||||
@ -181,10 +223,97 @@ 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)
|
||||
normalizeUuidProp(params)
|
||||
resin = require('resin-sdk').fromSharedOptions()
|
||||
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) ->
|
||||
normalizeUuidProp(params)
|
||||
resin = require('resin-sdk').fromSharedOptions()
|
||||
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) ->
|
||||
normalizeUuidProp(params)
|
||||
resin = require('resin-sdk').fromSharedOptions()
|
||||
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) ->
|
||||
normalizeUuidProp(params)
|
||||
resin = require('resin-sdk').fromSharedOptions()
|
||||
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) ->
|
||||
normalizeUuidProp(params)
|
||||
resin = require('resin-sdk').fromSharedOptions()
|
||||
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) ->
|
||||
normalizeUuidProp(params)
|
||||
resin = require('resin-sdk').fromSharedOptions()
|
||||
resin.models.device.hasDeviceUrl(params.uuid).then (hasDeviceUrl) ->
|
||||
console.log(hasDeviceUrl)
|
||||
.nodeify(done)
|
||||
|
||||
exports.rename =
|
||||
signature: 'device rename <uuid> [newName]'
|
||||
@ -201,9 +330,9 @@ exports.rename =
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
normalizeUuidProp(params)
|
||||
Promise = require('bluebird')
|
||||
_ = require('lodash')
|
||||
resin = require('resin-sdk')
|
||||
resin = require('resin-sdk').fromSharedOptions()
|
||||
form = require('resin-cli-form')
|
||||
|
||||
Promise.try ->
|
||||
@ -232,15 +361,15 @@ exports.move =
|
||||
permission: 'user'
|
||||
options: [ commandOptions.optionalApplication ]
|
||||
action: (params, options, done) ->
|
||||
resin = require('resin-sdk')
|
||||
_ = require('lodash')
|
||||
normalizeUuidProp(params)
|
||||
resin = require('resin-sdk').fromSharedOptions()
|
||||
patterns = require('../utils/patterns')
|
||||
|
||||
resin.models.device.get(params.uuid).then (device) ->
|
||||
resin.models.device.get(params.uuid, expandForAppName).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
|
||||
device.belongs_to__application[0].app_name isnt application.app_name
|
||||
]
|
||||
.tap (application) ->
|
||||
return resin.models.device.move(params.uuid, application)
|
||||
@ -250,7 +379,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,24 +394,26 @@ 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'))
|
||||
rimraf = Promise.promisify(require('rimraf'))
|
||||
tmp = Promise.promisifyAll(require('tmp'))
|
||||
tmp = require('tmp')
|
||||
tmpNameAsync = Promise.promisify(tmp.tmpName)
|
||||
tmp.setGracefulCleanup()
|
||||
|
||||
resin = require('resin-sdk')
|
||||
helpers = require('../utils/helpers')
|
||||
resin = require('resin-sdk').fromSharedOptions()
|
||||
patterns = require('../utils/patterns')
|
||||
{ runCommand } = require('../utils/helpers')
|
||||
|
||||
Promise.try ->
|
||||
return options.application if options.application?
|
||||
@ -291,26 +422,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'
|
||||
runCommand("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) ->
|
||||
runCommand("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 #{device.uuid}"
|
||||
if options.config
|
||||
configureCommand += " --config '#{options.config}'"
|
||||
else if options.advanced
|
||||
configureCommand += ' --advanced'
|
||||
runCommand(configureCommand)
|
||||
.then ->
|
||||
osInitCommand = "os initialize '#{tempPath}' --type #{application.device_type}"
|
||||
if options.yes
|
||||
osInitCommand += ' --yes'
|
||||
if options.drive
|
||||
osInitCommand += " --drive #{options.drive}"
|
||||
runCommand(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.
|
||||
@ -15,6 +15,7 @@ limitations under the License.
|
||||
###
|
||||
|
||||
commandOptions = require('./command-options')
|
||||
{ normalizeUuidProp } = require('../utils/normalization')
|
||||
|
||||
exports.list =
|
||||
signature: 'envs'
|
||||
@ -27,6 +28,10 @@ exports.list =
|
||||
If you want to see all environment variables, including private
|
||||
ones used by resin, use the verbose option.
|
||||
|
||||
At the moment the CLI doesn't fully support multi-container applications,
|
||||
so the following commands will only show service variables,
|
||||
without showing which service they belong to.
|
||||
|
||||
Example:
|
||||
|
||||
$ resin envs --application MyApp
|
||||
@ -46,22 +51,25 @@ exports.list =
|
||||
]
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
normalizeUuidProp(options, 'device')
|
||||
Promise = require('bluebird')
|
||||
_ = require('lodash')
|
||||
resin = require('resin-sdk')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
visuals = require('resin-cli-visuals')
|
||||
|
||||
{ exitWithExpectedError } = require('../utils/patterns')
|
||||
|
||||
Promise.try ->
|
||||
if options.application?
|
||||
return resin.models.environmentVariables.getAllByApplication(options.application)
|
||||
else if options.device?
|
||||
return resin.models.environmentVariables.device.getAll(options.device)
|
||||
else
|
||||
throw new Error('You must specify an application or device')
|
||||
exitWithExpectedError('You must specify an application or device')
|
||||
|
||||
.tap (environmentVariables) ->
|
||||
if _.isEmpty(environmentVariables)
|
||||
throw new Error('No environment variables found')
|
||||
exitWithExpectedError('No environment variables found')
|
||||
if not options.verbose
|
||||
isSystemVariable = resin.models.environmentVariables.isSystemVariable
|
||||
environmentVariables = _.reject(environmentVariables, isSystemVariable)
|
||||
@ -98,7 +106,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 ->
|
||||
@ -114,6 +122,10 @@ exports.add =
|
||||
help: '''
|
||||
Use this command to add an enviroment variable to an application.
|
||||
|
||||
At the moment the CLI doesn't fully support multi-container applications,
|
||||
so the following commands will only set service variables for the first
|
||||
service in your application.
|
||||
|
||||
If value is omitted, the tool will attempt to use the variable's value
|
||||
as defined in your host machine.
|
||||
|
||||
@ -135,8 +147,11 @@ exports.add =
|
||||
]
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
normalizeUuidProp(options, 'device')
|
||||
Promise = require('bluebird')
|
||||
resin = require('resin-sdk')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
|
||||
{ exitWithExpectedError } = require('../utils/patterns')
|
||||
|
||||
Promise.try ->
|
||||
if not params.value?
|
||||
@ -152,7 +167,7 @@ exports.add =
|
||||
else if options.device?
|
||||
resin.models.environmentVariables.device.create(options.device, params.key, params.value)
|
||||
else
|
||||
throw new Error('You must specify an application or device')
|
||||
exitWithExpectedError('You must specify an application or device')
|
||||
.nodeify(done)
|
||||
|
||||
exports.rename =
|
||||
@ -172,7 +187,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,13 @@ limitations under the License.
|
||||
###
|
||||
|
||||
_ = require('lodash')
|
||||
_.str = require('underscore.string')
|
||||
capitano = require('capitano')
|
||||
columnify = require('columnify')
|
||||
messages = require('../utils/messages')
|
||||
{ exitWithExpectedError } = require('../utils/patterns')
|
||||
|
||||
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 +36,7 @@ parse = (object) ->
|
||||
]
|
||||
|
||||
indent = (text) ->
|
||||
text = _.map _.str.lines(text), (line) ->
|
||||
text = _.map text.split('\n'), (line) ->
|
||||
return ' ' + line
|
||||
return text.join('\n')
|
||||
|
||||
@ -47,29 +47,22 @@ 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'
|
||||
|
||||
print(parse(groupedCommands.primary))
|
||||
|
||||
if options.verbose
|
||||
if not _.isEmpty(groupedCommands.plugins)
|
||||
console.log('\nInstalled plugins:\n')
|
||||
print(parse(groupedCommands.plugins))
|
||||
|
||||
console.log('\nAdditional commands:\n')
|
||||
print(parse(groupedCommands.secondary))
|
||||
else
|
||||
@ -86,14 +79,14 @@ command = (params, options, done) ->
|
||||
return done(error) if error?
|
||||
|
||||
if not command? or command.isWildcard()
|
||||
return done(new Error("Command not found: #{params.command}"))
|
||||
exitWithExpectedError("Command not found: #{params.command}")
|
||||
|
||||
console.log("Usage: #{command.signature}")
|
||||
|
||||
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.
|
||||
@ -16,13 +16,15 @@ limitations under the License.
|
||||
|
||||
module.exports =
|
||||
wizard: require('./wizard')
|
||||
apiKey: require('./api-key')
|
||||
app: require('./app')
|
||||
info: require('./info')
|
||||
auth: require('./auth')
|
||||
info: require('./info')
|
||||
device: require('./device')
|
||||
env: require('./environment-variables')
|
||||
keys: require('./keys')
|
||||
logs: require('./logs')
|
||||
local: require('./local')
|
||||
notes: require('./notes')
|
||||
help: require('./help')
|
||||
os: require('./os')
|
||||
@ -30,3 +32,11 @@ module.exports =
|
||||
config: require('./config')
|
||||
sync: require('./sync')
|
||||
ssh: require('./ssh')
|
||||
internal: require('./internal')
|
||||
build: require('./build')
|
||||
deploy: require('./deploy')
|
||||
util: require('./util')
|
||||
preload: require('./preload')
|
||||
push: require('./push')
|
||||
join: require('./join')
|
||||
leave: require('./leave')
|
||||
|
@ -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.
|
||||
@ -12,23 +12,19 @@ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
###
|
||||
*/
|
||||
|
||||
exports.list =
|
||||
signature: 'settings'
|
||||
description: 'print current settings'
|
||||
help: '''
|
||||
Use this command to display detected settings
|
||||
import { CommandDefinition } from 'capitano';
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin settings
|
||||
'''
|
||||
action: (params, options, done) ->
|
||||
resin = require('resin-sdk')
|
||||
prettyjson = require('prettyjson')
|
||||
|
||||
resin.settings.getAll()
|
||||
.then(prettyjson.render)
|
||||
.then(console.log)
|
||||
.nodeify(done)
|
||||
export const version: CommandDefinition = {
|
||||
signature: 'version',
|
||||
description: 'output the version number',
|
||||
help: `\
|
||||
Display the Resin CLI version.\
|
||||
`,
|
||||
async action(_params, _options, done) {
|
||||
const packageJSON = await import('../../package.json');
|
||||
console.log(packageJSON.version);
|
||||
return done();
|
||||
},
|
||||
};
|
87
lib/actions/internal.coffee
Normal file
87
lib/actions/internal.coffee
Normal file
@ -0,0 +1,87 @@
|
||||
###
|
||||
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)
|
||||
|
||||
exports.scanDevices =
|
||||
signature: 'internal scandevices'
|
||||
description: 'scan for local resin-enabled devices and show a picker to choose one'
|
||||
help: '''
|
||||
Don't use this command directly!
|
||||
'''
|
||||
hidden: true
|
||||
root: true
|
||||
action: (params, options, done) ->
|
||||
Promise = require('bluebird')
|
||||
{ forms } = require('resin-sync')
|
||||
|
||||
return Promise.try ->
|
||||
forms.selectLocalResinOsDevice()
|
||||
.then (hostnameOrIp) ->
|
||||
console.error("==> Selected device: #{hostnameOrIp}")
|
||||
.nodeify(done)
|
||||
|
||||
exports.sudo =
|
||||
signature: 'internal sudo <command>'
|
||||
description: 'execute arbitrary commands in a privileged subprocess'
|
||||
help: '''
|
||||
Don't use this command directly!
|
||||
|
||||
<command> must be passed as a single argument. That means, you need to make sure
|
||||
you enclose <command> in quotes (eg. resin internal sudo 'ls -alF') if for
|
||||
whatever reason you invoke the command directly or, typically, pass <command>
|
||||
as a single argument to spawn (eg. `spawn('resin', [ 'internal', 'sudo', 'ls -alF' ])`).
|
||||
|
||||
Furthermore, this command will naively split <command> on whitespace and directly
|
||||
forward the parts as arguments to `sudo`, so be careful.
|
||||
'''
|
||||
hidden: true
|
||||
action: (params, options, done) ->
|
||||
os = require('os')
|
||||
Promise = require('bluebird')
|
||||
|
||||
return Promise.try ->
|
||||
if os.platform() is 'win32'
|
||||
windosu = require('windosu')
|
||||
windosu.exec(params.command, {})
|
||||
else
|
||||
{ spawn } = require('child_process')
|
||||
{ wait } = require('rindle')
|
||||
cmd = params.command.split(' ')
|
||||
ps = spawn('sudo', cmd, stdio: 'inherit', env: process.env)
|
||||
wait(ps)
|
||||
|
||||
.nodeify(done)
|
62
lib/actions/join.ts
Normal file
62
lib/actions/join.ts
Normal file
@ -0,0 +1,62 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
import * as Bluebird from 'bluebird';
|
||||
import { CommandDefinition } from 'capitano';
|
||||
import { stripIndent } from 'common-tags';
|
||||
|
||||
interface Args {
|
||||
deviceIp?: string;
|
||||
}
|
||||
|
||||
interface Options {
|
||||
application?: string;
|
||||
}
|
||||
|
||||
export const join: CommandDefinition<Args, Options> = {
|
||||
signature: 'join [deviceIp]',
|
||||
description:
|
||||
'Promote a local device running unmanaged resinOS to join a resin.io application',
|
||||
help: stripIndent`
|
||||
Examples:
|
||||
|
||||
$ resin join
|
||||
$ resin join resin.local
|
||||
$ resin join resin.local --application MyApp
|
||||
$ resin join 192.168.1.25
|
||||
$ resin join 192.168.1.25 --application MyApp
|
||||
`,
|
||||
options: [
|
||||
{
|
||||
signature: 'application',
|
||||
parameter: 'application',
|
||||
alias: 'a',
|
||||
description: 'The name of the application the device should join',
|
||||
},
|
||||
],
|
||||
|
||||
primary: true,
|
||||
|
||||
async action(params, options, done) {
|
||||
const resin = await import('resin-sdk');
|
||||
const Logger = await import('../utils/logger');
|
||||
const promote = await import('../utils/promote');
|
||||
const sdk = resin.fromSharedOptions();
|
||||
const logger = new Logger();
|
||||
return Bluebird.try(() => {
|
||||
return promote.join(logger, sdk, params.deviceIp, options.application);
|
||||
}).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').fromSharedOptions()
|
||||
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').fromSharedOptions()
|
||||
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').fromSharedOptions()
|
||||
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').fromSharedOptions()
|
||||
|
||||
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)
|
||||
|
49
lib/actions/leave.ts
Normal file
49
lib/actions/leave.ts
Normal file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
import * as Bluebird from 'bluebird';
|
||||
import { CommandDefinition } from 'capitano';
|
||||
import { stripIndent } from 'common-tags';
|
||||
|
||||
interface Args {
|
||||
deviceIp?: string;
|
||||
}
|
||||
|
||||
export const leave: CommandDefinition<Args, {}> = {
|
||||
signature: 'leave [deviceIp]',
|
||||
description: 'Detach a local device from its resin.io application',
|
||||
help: stripIndent`
|
||||
Examples:
|
||||
|
||||
$ resin leave
|
||||
$ resin leave resin.local
|
||||
$ resin leave 192.168.1.25
|
||||
`,
|
||||
options: [],
|
||||
|
||||
permission: 'user',
|
||||
primary: true,
|
||||
|
||||
async action(params, _options, done) {
|
||||
const resin = await import('resin-sdk');
|
||||
const Logger = await import('../utils/logger');
|
||||
const promote = await import('../utils/promote');
|
||||
const sdk = resin.fromSharedOptions();
|
||||
const logger = new Logger();
|
||||
return Bluebird.try(() => {
|
||||
return promote.leave(logger, sdk, params.deviceIp);
|
||||
}).nodeify(done);
|
||||
},
|
||||
};
|
62
lib/actions/local/common.coffee
Normal file
62
lib/actions/local/common.coffee
Normal file
@ -0,0 +1,62 @@
|
||||
Promise = require('bluebird')
|
||||
_ = require('lodash')
|
||||
form = require('resin-cli-form')
|
||||
chalk = require('chalk')
|
||||
|
||||
dockerUtils = require('../../utils/docker')
|
||||
{ exitWithExpectedError } = require('../../utils/patterns')
|
||||
|
||||
exports.dockerPort = dockerPort = 2375
|
||||
exports.dockerTimeout = dockerTimeout = 2000
|
||||
|
||||
exports.filterOutSupervisorContainer = filterOutSupervisorContainer = (container) ->
|
||||
for name in container.Names
|
||||
return false if name.includes('resin_supervisor')
|
||||
return true
|
||||
|
||||
exports.selectContainerFromDevice = Promise.method (deviceIp, filterSupervisor = false) ->
|
||||
docker = dockerUtils.createClient(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)
|
||||
exitWithExpectedError("No containers found in #{deviceIp}")
|
||||
|
||||
return form.ask
|
||||
message: 'Select a container'
|
||||
type: 'list'
|
||||
choices: _.map containers, (container) ->
|
||||
containerName = container.Names?[0] or 'Untitled'
|
||||
shortContainerId = ('' + container.Id).substr(0, 11)
|
||||
|
||||
return {
|
||||
name: "#{containerName} (#{shortContainerId})"
|
||||
value: container.Id
|
||||
}
|
||||
|
||||
exports.pipeContainerStream = Promise.method ({ deviceIp, name, outStream, follow = false }) ->
|
||||
docker = dockerUtils.createClient(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
|
230
lib/actions/local/configure.coffee
Normal file
230
lib/actions/local/configure.coffee
Normal file
@ -0,0 +1,230 @@
|
||||
###
|
||||
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 = 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)
|
||||
# Reconfix still uses the older resin-image-fs, so still needs an
|
||||
# object-based partition definition.
|
||||
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,5 +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.
|
||||
@ -14,13 +14,10 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
###
|
||||
|
||||
exports.version =
|
||||
signature: 'version'
|
||||
description: 'output the version number'
|
||||
help: '''
|
||||
Display the Resin CLI version.
|
||||
'''
|
||||
action: (params, options, done) ->
|
||||
packageJSON = require('../../package.json')
|
||||
console.log(packageJSON.version)
|
||||
return done()
|
||||
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']
|
78
lib/actions/local/push.coffee
Normal file
78
lib/actions/local/push.coffee
Normal file
@ -0,0 +1,78 @@
|
||||
###
|
||||
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
|
||||
local_resinos:
|
||||
app-name: local-app
|
||||
build-triggers:
|
||||
- Dockerfile: file-hash-abcdefabcdefabcdefabcdefabcdefabcdef
|
||||
- package.json: file-hash-abcdefabcdefabcdefabcdefabcdefabcdef
|
||||
environment:
|
||||
- MY_VARIABLE=123
|
||||
|
||||
|
||||
Command line options have precedence over the ones saved in '.resin-sync.yml'.
|
||||
|
||||
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
|
100
lib/actions/local/scan.coffee
Normal file
100
lib/actions/local/scan.coffee
Normal file
@ -0,0 +1,100 @@
|
||||
###
|
||||
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')
|
||||
{ discover } = require('resin-sync')
|
||||
{ SpinnerPromise } = require('resin-cli-visuals')
|
||||
{ dockerPort, dockerTimeout } = require('./common')
|
||||
dockerUtils = require('../../utils/docker')
|
||||
{ exitWithExpectedError } = require('../../utils/patterns')
|
||||
|
||||
if options.timeout?
|
||||
options.timeout *= 1000
|
||||
|
||||
Promise.try ->
|
||||
new SpinnerPromise
|
||||
promise: discover.discoverLocalResinOsDevices(options.timeout)
|
||||
startMessage: 'Scanning for local resinOS devices..'
|
||||
stopMessage: 'Reporting scan results'
|
||||
.filter ({ address }) ->
|
||||
Promise.try ->
|
||||
docker = dockerUtils.createClient(host: address, port: dockerPort, timeout: dockerTimeout)
|
||||
docker.pingAsync()
|
||||
.return(true)
|
||||
.catchReturn(false)
|
||||
.tap (devices) ->
|
||||
if _.isEmpty(devices)
|
||||
exitWithExpectedError('Could not find any resinOS devices in the local network')
|
||||
.map ({ host, address }) ->
|
||||
docker = dockerUtils.createClient(host: address, port: dockerPort, timeout: dockerTimeout)
|
||||
Promise.props
|
||||
dockerInfo: docker.infoAsync().catchReturn('Could not get Docker info')
|
||||
dockerVersion: docker.versionAsync().catchReturn('Could not get Docker version')
|
||||
.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)
|
114
lib/actions/local/ssh.coffee
Normal file
114
lib/actions/local/ssh.coffee
Normal file
@ -0,0 +1,114 @@
|
||||
###
|
||||
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.
|
||||
###
|
||||
|
||||
{ hostOSAccess } = require('../command-options')
|
||||
_ = require('lodash')
|
||||
|
||||
localHostOSAccessOption = _.cloneDeep(hostOSAccess)
|
||||
localHostOSAccessOption.description = 'get a shell into the host OS'
|
||||
|
||||
module.exports =
|
||||
signature: 'local ssh [deviceIp]'
|
||||
description: 'Get a shell into a resinOS device'
|
||||
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'
|
||||
,
|
||||
localHostOSAccessOption,
|
||||
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')
|
||||
{ exitWithExpectedError } = require('../../utils/patterns')
|
||||
|
||||
if (options.host is true and options.container?)
|
||||
exitWithExpectedError('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'"'''
|
||||
dockerCmd = "'$(if [ -f /usr/bin/balena ]; then echo \"balena\"; else echo \"docker\"; fi)'"
|
||||
command += " #{dockerCmd} exec -ti #{container} #{shellCmd}"
|
||||
|
||||
subShellCommand = getSubShellCommand(command)
|
||||
child_process.spawn subShellCommand.program, subShellCommand.args,
|
||||
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.
|
||||
@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
###
|
||||
|
||||
{ normalizeUuidProp } = require('../utils/normalization')
|
||||
|
||||
module.exports =
|
||||
signature: 'logs <uuid>'
|
||||
description: 'show device logs'
|
||||
@ -24,10 +26,6 @@ module.exports =
|
||||
|
||||
To continuously stream output, and see new logs in real time, use the `--tail` option.
|
||||
|
||||
Note that for now you need to provide the whole UUID for this command to work correctly.
|
||||
|
||||
This is due to some technical limitations that we plan to address soon.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin logs 23c73a1
|
||||
@ -44,26 +42,20 @@ module.exports =
|
||||
permission: 'user'
|
||||
primary: true
|
||||
action: (params, options, done) ->
|
||||
_ = require('lodash')
|
||||
resin = require('resin-sdk')
|
||||
normalizeUuidProp(params)
|
||||
resin = require('resin-sdk').fromSharedOptions()
|
||||
moment = require('moment')
|
||||
|
||||
printLine = (line) ->
|
||||
timestamp = moment(line.timestamp).format('DD.MM.YY HH:mm:ss (ZZ)')
|
||||
console.log("#{timestamp} #{line.message}")
|
||||
|
||||
promise = resin.logs.history(params.uuid).each(printLine)
|
||||
|
||||
if not options.tail
|
||||
|
||||
# PubNub keeps the process alive after a history query.
|
||||
# Until this is fixed, we force the process to exit.
|
||||
# This of course prevents this command to be used programatically
|
||||
return promise.catch(done).finally ->
|
||||
process.exit(0)
|
||||
|
||||
promise.then ->
|
||||
resin.logs.subscribe(params.uuid).then (logs) ->
|
||||
if options.tail
|
||||
resin.logs.subscribe(params.uuid, { count: 100 }).then (logs) ->
|
||||
logs.on('line', printLine)
|
||||
logs.on('error', done)
|
||||
.catch(done)
|
||||
.catch(done)
|
||||
else
|
||||
resin.logs.history(params.uuid)
|
||||
.each(printLine)
|
||||
.catch(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.
|
||||
@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
###
|
||||
|
||||
{ normalizeUuidProp } = require('../utils/normalization')
|
||||
|
||||
exports.set =
|
||||
signature: 'note <|note>'
|
||||
description: 'set a device note'
|
||||
@ -38,13 +40,16 @@ exports.set =
|
||||
]
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
normalizeUuidProp(options, 'device')
|
||||
Promise = require('bluebird')
|
||||
_ = require('lodash')
|
||||
resin = require('resin-sdk')
|
||||
resin = require('resin-sdk').fromSharedOptions()
|
||||
|
||||
{ exitWithExpectedError } = require('../utils/patterns')
|
||||
|
||||
Promise.try ->
|
||||
if _.isEmpty(params.note)
|
||||
throw new Error('Missing note content')
|
||||
exitWithExpectedError('Missing note content')
|
||||
|
||||
resin.models.device.note(options.device, params.note)
|
||||
.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.
|
||||
@ -15,26 +15,91 @@ limitations under the License.
|
||||
###
|
||||
|
||||
commandOptions = require('./command-options')
|
||||
_ = require('lodash')
|
||||
{ normalizeUuidProp } = require('../utils/normalization')
|
||||
|
||||
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').fromSharedOptions()
|
||||
|
||||
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').fromSharedOptions()
|
||||
|
||||
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 +108,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 +147,195 @@ 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.
|
||||
|
||||
Calling this command without --version is not recommended, and may fail in
|
||||
future releases if the OS version cannot be inferred.
|
||||
|
||||
Note that device api keys are only supported on ResinOS 2.0.3+.
|
||||
|
||||
This command 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 --version 2.12.7
|
||||
$ resin os configure ../path/rpi.img --device 7cf02a6 --version 2.12.7 --device-api-key <existingDeviceKey>
|
||||
$ resin os configure ../path/rpi.img --app MyApp --version 2.12.7
|
||||
'''
|
||||
permission: 'user'
|
||||
options: [
|
||||
commandOptions.advancedConfig
|
||||
commandOptions.optionalApplication
|
||||
commandOptions.optionalDevice
|
||||
commandOptions.optionalDeviceApiKey
|
||||
commandOptions.optionalOsVersion
|
||||
{
|
||||
signature: 'config'
|
||||
description: 'path to the config JSON file, see `resin os build-config`'
|
||||
parameter: 'config'
|
||||
}
|
||||
]
|
||||
action: (params, options, done) ->
|
||||
normalizeUuidProp(params)
|
||||
normalizeUuidProp(options, 'device')
|
||||
fs = require('fs')
|
||||
Promise = require('bluebird')
|
||||
readFileAsync = Promise.promisify(fs.readFile)
|
||||
resin = require('resin-sdk').fromSharedOptions()
|
||||
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.exitWithExpectedError '''
|
||||
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, and will stop working in future.\n' +
|
||||
'Pass your device UUID with --device <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)
|
||||
answers.version = options.version
|
||||
|
||||
(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 +344,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)
|
||||
|
299
lib/actions/preload.coffee
Normal file
299
lib/actions/preload.coffee
Normal file
@ -0,0 +1,299 @@
|
||||
###
|
||||
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'
|
||||
|
||||
allDeviceTypes = undefined
|
||||
|
||||
getDeviceTypes = ->
|
||||
Bluebird = require('bluebird')
|
||||
if allDeviceTypes != undefined
|
||||
return Bluebird.resolve(allDeviceTypes)
|
||||
resin = require('resin-sdk').fromSharedOptions()
|
||||
resin.models.config.getDeviceTypes()
|
||||
.tap (dt) ->
|
||||
allDeviceTypes = dt
|
||||
|
||||
getDeviceTypesWithSameArch = (deviceTypeSlug) ->
|
||||
_ = require('lodash')
|
||||
|
||||
getDeviceTypes()
|
||||
.then (deviceTypes) ->
|
||||
deviceType = _.find(deviceTypes, slug: deviceTypeSlug)
|
||||
_(deviceTypes).filter(arch: deviceType.arch).map('slug').value()
|
||||
|
||||
getApplicationsWithSuccessfulBuilds = (deviceType) ->
|
||||
preload = require('resin-preload')
|
||||
resin = require('resin-sdk').fromSharedOptions()
|
||||
|
||||
getDeviceTypesWithSameArch(deviceType)
|
||||
.then (deviceTypes) ->
|
||||
resin.pine.get
|
||||
resource: 'my_application'
|
||||
options:
|
||||
$filter:
|
||||
device_type:
|
||||
$in: deviceTypes
|
||||
owns__release:
|
||||
$any:
|
||||
$alias: 'r'
|
||||
$expr:
|
||||
r:
|
||||
status: 'success'
|
||||
$expand: preload.applicationExpandOptions
|
||||
$select: [ 'id', 'app_name', 'device_type', 'commit', 'should_track_latest_release' ]
|
||||
$orderby: 'app_name asc'
|
||||
|
||||
selectApplication = (deviceType) ->
|
||||
visuals = require('resin-cli-visuals')
|
||||
form = require('resin-cli-form')
|
||||
{ exitWithExpectedError } = require('../utils/patterns')
|
||||
|
||||
applicationInfoSpinner = new visuals.Spinner('Downloading list of applications and releases.')
|
||||
applicationInfoSpinner.start()
|
||||
|
||||
getApplicationsWithSuccessfulBuilds(deviceType)
|
||||
.then (applications) ->
|
||||
applicationInfoSpinner.stop()
|
||||
if applications.length == 0
|
||||
exitWithExpectedError("You have no apps with successful releases for a '#{deviceType}' device type.")
|
||||
form.ask
|
||||
message: 'Select an application'
|
||||
type: 'list'
|
||||
choices: applications.map (app) ->
|
||||
name: app.app_name
|
||||
value: app
|
||||
|
||||
selectApplicationCommit = (releases) ->
|
||||
form = require('resin-cli-form')
|
||||
{ exitWithExpectedError } = require('../utils/patterns')
|
||||
|
||||
if releases.length == 0
|
||||
exitWithExpectedError('This application has no successful releases.')
|
||||
DEFAULT_CHOICE = { 'name': LATEST, 'value': LATEST }
|
||||
choices = [ DEFAULT_CHOICE ].concat releases.map (release) ->
|
||||
name: "#{release.end_timestamp} - #{release.commit}"
|
||||
value: release.commit
|
||||
return form.ask
|
||||
message: 'Select a release'
|
||||
type: 'list'
|
||||
default: LATEST
|
||||
choices: choices
|
||||
|
||||
offerToDisableAutomaticUpdates = (application, commit) ->
|
||||
Promise = require('bluebird')
|
||||
resin = require('resin-sdk').fromSharedOptions()
|
||||
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 release once it is online.
|
||||
|
||||
Do you want to disable automatic updates for this application?
|
||||
|
||||
Warning: To re-enable this requires direct api calls,
|
||||
see https://docs.resin.io/reference/api/resources/device/#set-device-to-release
|
||||
'''
|
||||
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 release from Resin.io.
|
||||
|
||||
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: '''
|
||||
the commit hash for a specific application release to preload, use "latest" to specify the latest release
|
||||
(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-arch'
|
||||
boolean: true
|
||||
description: 'Disables check for matching architecture in image and application'
|
||||
}
|
||||
{
|
||||
signature: 'pin-device-to-release'
|
||||
boolean: true
|
||||
description: 'Pin the preloaded device to the preloaded release on provision'
|
||||
alias: 'p'
|
||||
}
|
||||
]
|
||||
action: (params, options, done) ->
|
||||
_ = require('lodash')
|
||||
Promise = require('bluebird')
|
||||
resin = require('resin-sdk').fromSharedOptions()
|
||||
preload = require('resin-preload')
|
||||
visuals = require('resin-cli-visuals')
|
||||
nodeCleanup = require('node-cleanup')
|
||||
{ exitWithExpectedError } = 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']
|
||||
|
||||
options.dontCheckArch = options['dont-check-arch'] || false
|
||||
delete options['dont-check-arch']
|
||||
if options.dontCheckArch and not options.appId
|
||||
exitWithExpectedError('You need to specify an app id if you disable the architecture check.')
|
||||
|
||||
options.pinDevice = options['pin-device-to-release'] || false
|
||||
delete options['pin-device-to-release']
|
||||
|
||||
# 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
|
||||
options.dontCheckArch
|
||||
options.pinDevice
|
||||
)
|
||||
|
||||
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.prepare()
|
||||
.then ->
|
||||
# If no appId was provided, show a list of matching apps
|
||||
Promise.try ->
|
||||
if not preloader.appId
|
||||
selectApplication(preloader.config.deviceType)
|
||||
.then (application) ->
|
||||
preloader.setApplication(application)
|
||||
.then ->
|
||||
# Use the commit given as --commit or show an interactive commit selection menu
|
||||
Promise.try ->
|
||||
if options.commit
|
||||
if options.commit == LATEST and preloader.application.commit
|
||||
# handle `--commit latest`
|
||||
return LATEST
|
||||
release = _.find preloader.application.owns__release, (release) ->
|
||||
release.commit.startsWith(options.commit)
|
||||
if not release
|
||||
exitWithExpectedError('There is no release matching this commit')
|
||||
return release.commit
|
||||
selectApplicationCommit(preloader.application.owns__release)
|
||||
.then (commit) ->
|
||||
if commit == LATEST
|
||||
preloader.commit = preloader.application.commit
|
||||
else
|
||||
preloader.commit = commit
|
||||
|
||||
# Propose to disable automatic app updates if the commit is not the latest
|
||||
offerToDisableAutomaticUpdates(preloader.application, commit)
|
||||
.then ->
|
||||
# All options are ready: preload the image.
|
||||
preloader.preload()
|
||||
.catch(resin.errors.ResinError, exitWithExpectedError)
|
||||
.then(resolve)
|
||||
.catch(reject)
|
||||
.then(done)
|
||||
.finally ->
|
||||
if not gotSignal
|
||||
preloader.cleanup()
|
229
lib/actions/push.ts
Normal file
229
lib/actions/push.ts
Normal file
@ -0,0 +1,229 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
import { CommandDefinition } from 'capitano';
|
||||
import { stripIndent } from 'common-tags';
|
||||
import { ResinSDK } from 'resin-sdk';
|
||||
|
||||
import { BuildError } from '../utils/device/errors';
|
||||
|
||||
// An regex to detect an IP address, from https://www.regular-expressions.info/ip.html
|
||||
const IP_REGEX = new RegExp(
|
||||
/\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b/,
|
||||
);
|
||||
|
||||
enum BuildTarget {
|
||||
Cloud,
|
||||
Device,
|
||||
}
|
||||
|
||||
function getBuildTarget(appOrDevice: string): BuildTarget | null {
|
||||
// First try the application regex from the api
|
||||
if (/^[a-zA-Z0-9_-]+$/.test(appOrDevice)) {
|
||||
return BuildTarget.Cloud;
|
||||
}
|
||||
|
||||
if (IP_REGEX.test(appOrDevice)) {
|
||||
return BuildTarget.Device;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async function getAppOwner(sdk: ResinSDK, appName: string) {
|
||||
const {
|
||||
exitWithExpectedError,
|
||||
selectFromList,
|
||||
} = await import('../utils/patterns');
|
||||
const _ = await import('lodash');
|
||||
|
||||
const applications = await sdk.models.application.getAll({
|
||||
$expand: {
|
||||
user: {
|
||||
$select: ['username'],
|
||||
},
|
||||
},
|
||||
$filter: {
|
||||
app_name: appName,
|
||||
},
|
||||
$select: ['id'],
|
||||
});
|
||||
|
||||
if (applications == null || applications.length === 0) {
|
||||
exitWithExpectedError(
|
||||
stripIndent`
|
||||
No applications found with name: ${appName}.
|
||||
|
||||
This could mean that the application does not exist, or you do
|
||||
not have the permissions required to access it.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (applications.length === 1) {
|
||||
return _.get(applications, '[0].user[0].username');
|
||||
}
|
||||
|
||||
// If we got more than one application with the same name it means that the
|
||||
// user has access to a collab app with the same name as a personal app. We
|
||||
// present a list to the user which shows the fully qualified application
|
||||
// name (user/appname) and allows them to select
|
||||
const entries = _.map(applications, app => {
|
||||
const username = _.get(app, 'user[0].username');
|
||||
return {
|
||||
name: `${username}/${appName}`,
|
||||
extra: username,
|
||||
};
|
||||
});
|
||||
|
||||
const selected = await selectFromList(
|
||||
`${
|
||||
entries.length
|
||||
} applications found with that name, please select the application you would like to push to`,
|
||||
entries,
|
||||
);
|
||||
|
||||
return selected.extra;
|
||||
}
|
||||
|
||||
export const push: CommandDefinition<
|
||||
{
|
||||
applicationOrDevice: string;
|
||||
},
|
||||
{
|
||||
source: string;
|
||||
emulated: boolean;
|
||||
nocache: boolean;
|
||||
}
|
||||
> = {
|
||||
signature: 'push <applicationOrDevice>',
|
||||
description:
|
||||
'Start a remote build on the resin.io cloud build servers or a local mode device',
|
||||
help: stripIndent`
|
||||
This command can be used to start a build on the remote
|
||||
resin.io cloud builders, or a local mode resin device.
|
||||
|
||||
When building on the resin cloud the given source directory will be sent to the
|
||||
resin.io builder, and the build will proceed. This can be used as a drop-in
|
||||
replacement for git push to deploy.
|
||||
|
||||
When building on a local mode device, the given source directory will be built on
|
||||
device, and the resulting containers will be run on the device. Logs will be
|
||||
streamed back from the device as part of the same invocation.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin push myApp
|
||||
$ resin push myApp --source <source directory>
|
||||
$ resin push myApp -s <source directory>
|
||||
|
||||
$ resin push 10.0.0.1
|
||||
$ resin push 10.0.0.1 --source <source directory>
|
||||
$ resin push 10.0.0.1 -s <source directory>
|
||||
`,
|
||||
permission: 'user',
|
||||
options: [
|
||||
{
|
||||
signature: 'source',
|
||||
alias: 's',
|
||||
description:
|
||||
'The source that should be sent to the resin builder to be built (defaults to the current directory)',
|
||||
parameter: 'source',
|
||||
},
|
||||
{
|
||||
signature: 'emulated',
|
||||
alias: 'e',
|
||||
description: 'Force an emulated build to occur on the remote builder',
|
||||
boolean: true,
|
||||
},
|
||||
{
|
||||
signature: 'nocache',
|
||||
alias: 'c',
|
||||
description: "Don't use cache when building this project",
|
||||
boolean: true,
|
||||
},
|
||||
],
|
||||
async action(params, options, done) {
|
||||
const sdk = (await import('resin-sdk')).fromSharedOptions();
|
||||
const Bluebird = await import('bluebird');
|
||||
const remote = await import('../utils/remote-build');
|
||||
const deviceDeploy = await import('../utils/device/deploy');
|
||||
const { exitWithExpectedError } = await import('../utils/patterns');
|
||||
|
||||
const appOrDevice: string | null = params.applicationOrDevice;
|
||||
if (appOrDevice == null) {
|
||||
exitWithExpectedError('You must specify an application or a device');
|
||||
}
|
||||
|
||||
const source = options.source || '.';
|
||||
if (process.env.DEBUG) {
|
||||
console.log(`[debug] Using ${source} as build source`);
|
||||
}
|
||||
|
||||
const buildTarget = getBuildTarget(appOrDevice);
|
||||
switch (buildTarget) {
|
||||
case BuildTarget.Cloud:
|
||||
const app = appOrDevice;
|
||||
Bluebird.join(
|
||||
sdk.auth.getToken(),
|
||||
sdk.settings.get('resinUrl'),
|
||||
getAppOwner(sdk, app),
|
||||
(token, baseUrl, owner) => {
|
||||
const opts = {
|
||||
emulated: options.emulated,
|
||||
nocache: options.nocache,
|
||||
};
|
||||
const args = {
|
||||
app,
|
||||
owner,
|
||||
source,
|
||||
auth: token,
|
||||
baseUrl,
|
||||
sdk,
|
||||
opts,
|
||||
};
|
||||
|
||||
return remote.startRemoteBuild(args);
|
||||
},
|
||||
).nodeify(done);
|
||||
break;
|
||||
case BuildTarget.Device:
|
||||
const device = appOrDevice;
|
||||
// TODO: Support passing a different port
|
||||
Bluebird.resolve(
|
||||
deviceDeploy.deployToDevice({
|
||||
source,
|
||||
deviceHost: device,
|
||||
}),
|
||||
)
|
||||
.catch(BuildError, e => {
|
||||
exitWithExpectedError(e.toString());
|
||||
})
|
||||
.nodeify(done);
|
||||
break;
|
||||
default:
|
||||
exitWithExpectedError(
|
||||
stripIndent`
|
||||
Build target not recognised. Please provide either an application name or device address.
|
||||
|
||||
The only supported device addresses currently are IP addresses.
|
||||
|
||||
If you believe your build target should have been detected, and this is an error, please
|
||||
create an issue.`,
|
||||
);
|
||||
break;
|
||||
}
|
||||
},
|
||||
};
|
39
lib/actions/settings.ts
Normal file
39
lib/actions/settings.ts
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
import { CommandDefinition } from 'capitano';
|
||||
|
||||
export const list: CommandDefinition = {
|
||||
signature: 'settings',
|
||||
description: 'print current settings',
|
||||
help: `\
|
||||
Use this command to display detected settings
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin settings\
|
||||
`,
|
||||
async action(_params, _options, done) {
|
||||
const resin = (await import('resin-sdk')).fromSharedOptions();
|
||||
const prettyjson = await import('prettyjson');
|
||||
|
||||
return resin.settings
|
||||
.getAll()
|
||||
.then(prettyjson.render)
|
||||
.then(console.log)
|
||||
.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.
|
||||
@ -14,32 +14,27 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
###
|
||||
|
||||
getSubShellCommand = (command) ->
|
||||
os = require('os')
|
||||
|
||||
# Assume Cygwin
|
||||
if os.platform() is 'win32'
|
||||
return {
|
||||
program: 'sh'
|
||||
args: [ '-c', command ]
|
||||
}
|
||||
else
|
||||
return {
|
||||
program: '/bin/sh'
|
||||
args: [ '-c', command ]
|
||||
}
|
||||
commandOptions = require('./command-options')
|
||||
{ normalizeUuidProp } = require('../utils/normalization')
|
||||
|
||||
module.exports =
|
||||
signature: 'ssh <uuid>'
|
||||
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
|
||||
$ resin ssh 7cf02a6 -s
|
||||
'''
|
||||
permission: 'user'
|
||||
primary: true
|
||||
@ -47,32 +42,100 @@ module.exports =
|
||||
signature: 'port'
|
||||
parameter: 'port'
|
||||
description: 'ssh gateway port'
|
||||
alias: 't'
|
||||
alias: 'p'
|
||||
,
|
||||
signature: 'verbose'
|
||||
boolean: true
|
||||
description: 'increase verbosity'
|
||||
alias: 'v'
|
||||
commandOptions.hostOSAccess,
|
||||
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) ->
|
||||
normalizeUuidProp(params)
|
||||
child_process = require('child_process')
|
||||
Promise = require 'bluebird'
|
||||
resin = require('resin-sdk')
|
||||
settings = require('resin-settings-client')
|
||||
Promise = require('bluebird')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
_ = require('lodash')
|
||||
bash = require('bash')
|
||||
hasbin = require('hasbin')
|
||||
{ getSubShellCommand } = require('../utils/helpers')
|
||||
patterns = require('../utils/patterns')
|
||||
|
||||
if not options.port?
|
||||
options.port = 22
|
||||
options.port ?= 22
|
||||
|
||||
console.info("Connecting with: #{params.uuid}")
|
||||
verbose = if options.verbose then '-vvv' else ''
|
||||
|
||||
Promise.props
|
||||
isOnline: resin.models.device.isOnline(params.uuid)
|
||||
username: resin.auth.whoami()
|
||||
uuid: resin.models.device.get(params.uuid).get('uuid') # get full uuid
|
||||
containerId: resin.models.device.getApplicationInfo(params.uuid).get('containerId')
|
||||
.then ({ isOnline, username, uuid, containerId }) ->
|
||||
throw new Error('Device is not online') if not isOnline
|
||||
throw new Error('Did not find running application container') if not containerId?
|
||||
Promise.try ->
|
||||
command = "ssh -t -o LogLevel=QUIET -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
|
||||
-p #{options.port} #{username}@ssh.#{settings.get('proxyUrl')} enter #{uuid} #{containerId}"
|
||||
proxyConfig = global.PROXY_CONFIG
|
||||
useProxy = !!proxyConfig and not options.noproxy
|
||||
|
||||
subShellCommand = getSubShellCommand(command)
|
||||
spawn = child_process.spawn subShellCommand.program, subShellCommand.args,
|
||||
stdio: 'inherit'
|
||||
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) ->
|
||||
patterns.exitWithExpectedError('Device is not online') if not device.is_online
|
||||
|
||||
Promise.props
|
||||
username: resin.auth.whoami()
|
||||
uuid: device.uuid
|
||||
# get full uuid
|
||||
containerId: if options.host then '' else 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)
|
||||
|
||||
if options.host
|
||||
accessCommand = "host #{uuid}"
|
||||
else
|
||||
accessCommand = "enter #{uuid} #{containerId}"
|
||||
|
||||
command = "ssh #{verbose} -t \
|
||||
-o LogLevel=ERROR \
|
||||
-o StrictHostKeyChecking=no \
|
||||
-o UserKnownHostsFile=/dev/null \
|
||||
#{sshProxyCommand} \
|
||||
-p #{options.port} #{username}@ssh.#{proxyUrl} #{accessCommand}"
|
||||
|
||||
subShellCommand = getSubShellCommand(command)
|
||||
child_process.spawn subShellCommand.program, subShellCommand.args,
|
||||
stdio: 'inherit'
|
||||
.nodeify(done)
|
||||
|
@ -1,89 +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.
|
||||
###
|
||||
|
||||
module.exports =
|
||||
signature: 'sync [source]'
|
||||
description: '(beta) sync your changes with a device'
|
||||
help: '''
|
||||
Use this command to sync your local changes to a certain device on the fly.
|
||||
|
||||
The `source` argument can be either a device uuid or an application name.
|
||||
|
||||
You can save all the options mentioned below in a `resin-sync.yml` file,
|
||||
by using the same option names as keys. For example:
|
||||
|
||||
$ cat $PWD/resin-sync.yml
|
||||
source: src/
|
||||
before: 'echo Hello'
|
||||
ignore:
|
||||
- .git
|
||||
- node_modules/
|
||||
progress: true
|
||||
|
||||
Notice that explicitly passed command options override the ones set in the configuration file.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin sync MyApp
|
||||
$ resin sync 7cf02a6
|
||||
$ resin sync 7cf02a6 --port 8080
|
||||
$ resin sync 7cf02a6 --ignore foo,bar
|
||||
'''
|
||||
permission: 'user'
|
||||
primary: true
|
||||
options: [
|
||||
signature: 'source'
|
||||
parameter: 'path'
|
||||
description: 'custom source path'
|
||||
alias: 's'
|
||||
,
|
||||
signature: 'ignore'
|
||||
parameter: 'paths'
|
||||
description: 'comma delimited paths to ignore when syncing'
|
||||
alias: 'i'
|
||||
,
|
||||
signature: 'before'
|
||||
parameter: 'command'
|
||||
description: 'execute a command before syncing'
|
||||
alias: 'b'
|
||||
,
|
||||
signature: 'progress'
|
||||
boolean: true
|
||||
description: 'show progress'
|
||||
alias: 'p'
|
||||
,
|
||||
signature: 'port'
|
||||
parameter: 'port'
|
||||
description: 'ssh port'
|
||||
alias: 't'
|
||||
]
|
||||
action: (params, options, done) ->
|
||||
resin = require('resin-sdk')
|
||||
resinSync = require('resin-sync')
|
||||
patterns = require('../utils/patterns')
|
||||
|
||||
# TODO: Add comma separated options to Capitano
|
||||
if options.ignore?
|
||||
options.ignore = options.ignore.split(',')
|
||||
|
||||
resin.models.device.has(params.source).then (isValidUUID) ->
|
||||
if isValidUUID
|
||||
return params.source
|
||||
|
||||
return patterns.inferOrSelectDevice(params.source)
|
||||
.then (uuid) ->
|
||||
resinSync.sync(uuid, options)
|
||||
.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,19 +12,7 @@ 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);
|
||||
import * as ResinSync from 'resin-sync';
|
||||
export = ResinSync.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.
|
||||
@ -34,29 +34,28 @@ exports.wizard =
|
||||
'''
|
||||
primary: true
|
||||
action: (params, options, done) ->
|
||||
Promise = require('bluebird')
|
||||
capitano = Promise.promisifyAll(require('capitano'))
|
||||
resin = require('resin-sdk')
|
||||
resin = require('resin-sdk').fromSharedOptions()
|
||||
patterns = require('../utils/patterns')
|
||||
{ runCommand } = require('../utils/helpers')
|
||||
|
||||
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')
|
||||
console.info("Let's go through a quick wizard to get you started.\n")
|
||||
return runCommand('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}")
|
||||
runCommand("app create #{applicationName}")
|
||||
.then (applicationName) ->
|
||||
params.name = applicationName
|
||||
.then ->
|
||||
return capitano.runAsync("device init --application #{params.name}")
|
||||
return runCommand("device init --application #{params.name}")
|
||||
.tap(patterns.awaitDevice)
|
||||
.then (uuid) ->
|
||||
return capitano.runAsync("device #{uuid}")
|
||||
return runCommand("device #{uuid}")
|
||||
.then ->
|
||||
return resin.models.application.get(params.name)
|
||||
.then (application) ->
|
||||
|
144
lib/app.coffee
144
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,26 +14,87 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
###
|
||||
|
||||
_ = require('lodash')
|
||||
Raven = require('raven')
|
||||
Raven.disableConsoleAlerts()
|
||||
Raven.config require('./config').sentryDsn,
|
||||
captureUnhandledRejections: true,
|
||||
autoBreadcrumbs: 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
|
||||
|
||||
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
|
||||
ResinSdk = require('resin-sdk')
|
||||
ResinSdk.setSharedOptions(
|
||||
apiUrl: settings.get('apiUrl')
|
||||
imageMakerUrl: settings.get('imageMakerUrl')
|
||||
dataDirectory: settings.get('dataDirectory')
|
||||
retries: 2
|
||||
)
|
||||
|
||||
resin = ResinSdk.fromSharedOptions()
|
||||
|
||||
actions = require('./actions')
|
||||
errors = require('./errors')
|
||||
events = require('./events')
|
||||
plugins = require('./utils/plugins')
|
||||
update = require('./utils/update')
|
||||
{ exitWithExpectedError } = require('./utils/patterns')
|
||||
|
||||
# Assign bluebird as the global promise library
|
||||
# stream-to-promise will produce native promises if not
|
||||
# for this module, which could wreak havoc in this
|
||||
# bluebird-only codebase.
|
||||
require('any-promise/register/bluebird')
|
||||
|
||||
capitano.permission 'user', (done) ->
|
||||
resin.auth.isLoggedIn().then (isLoggedIn) ->
|
||||
if not isLoggedIn
|
||||
throw new Error '''
|
||||
exitWithExpectedError('''
|
||||
You have to log in to continue
|
||||
|
||||
Run the following command to go through the login wizard:
|
||||
|
||||
$ resin login
|
||||
'''
|
||||
''')
|
||||
.nodeify(done)
|
||||
|
||||
capitano.command
|
||||
@ -41,6 +102,11 @@ capitano.command
|
||||
action: ->
|
||||
capitano.execute(command: 'help')
|
||||
|
||||
capitano.globalOption
|
||||
signature: 'help'
|
||||
boolean: true
|
||||
alias: 'h'
|
||||
|
||||
# ---------- Info Module ----------
|
||||
capitano.command(actions.info.version)
|
||||
|
||||
@ -50,11 +116,8 @@ capitano.command(actions.help.help)
|
||||
# ---------- Wizard Module ----------
|
||||
capitano.command(actions.wizard.wizard)
|
||||
|
||||
# ---------- Auth Module ----------
|
||||
capitano.command(actions.auth.login)
|
||||
capitano.command(actions.auth.logout)
|
||||
capitano.command(actions.auth.signup)
|
||||
capitano.command(actions.auth.whoami)
|
||||
# ---------- Api key module ----------
|
||||
capitano.command(actions.apiKey.generate)
|
||||
|
||||
# ---------- App Module ----------
|
||||
capitano.command(actions.app.create)
|
||||
@ -63,13 +126,25 @@ capitano.command(actions.app.remove)
|
||||
capitano.command(actions.app.restart)
|
||||
capitano.command(actions.app.info)
|
||||
|
||||
# ---------- Auth Module ----------
|
||||
capitano.command(actions.auth.login)
|
||||
capitano.command(actions.auth.logout)
|
||||
capitano.command(actions.auth.signup)
|
||||
capitano.command(actions.auth.whoami)
|
||||
|
||||
# ---------- Device Module ----------
|
||||
capitano.command(actions.device.list)
|
||||
capitano.command(actions.device.supported)
|
||||
capitano.command(actions.device.rename)
|
||||
capitano.command(actions.device.init)
|
||||
capitano.command(actions.device.remove)
|
||||
capitano.command(actions.device.identify)
|
||||
capitano.command(actions.device.reboot)
|
||||
capitano.command(actions.device.shutdown)
|
||||
capitano.command(actions.device.enableDeviceUrl)
|
||||
capitano.command(actions.device.disableDeviceUrl)
|
||||
capitano.command(actions.device.getDeviceUrl)
|
||||
capitano.command(actions.device.hasDeviceUrl)
|
||||
capitano.command(actions.device.register)
|
||||
capitano.command(actions.device.move)
|
||||
capitano.command(actions.device.info)
|
||||
@ -90,7 +165,9 @@ 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)
|
||||
|
||||
@ -110,15 +187,48 @@ 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)
|
||||
capitano.command(actions.internal.scanDevices)
|
||||
capitano.command(actions.internal.sudo)
|
||||
|
||||
#------------ Local build and deploy -------
|
||||
capitano.command(actions.build)
|
||||
capitano.command(actions.deploy)
|
||||
|
||||
#------------ Push/remote builds -------
|
||||
capitano.command(actions.push.push)
|
||||
|
||||
#------------ Join/Leave -------
|
||||
capitano.command(actions.join.join)
|
||||
capitano.command(actions.leave.leave)
|
||||
|
||||
update.notify()
|
||||
|
||||
plugins.register(/^resin-plugin-(.+)$/).then ->
|
||||
cli = capitano.parse(process.argv)
|
||||
|
||||
events.trackCommand(cli).then ->
|
||||
capitano.executeAsync(cli)
|
||||
cli = capitano.parse(process.argv)
|
||||
runCommand = ->
|
||||
if cli.global?.help
|
||||
capitanoExecuteAsync(command: "help #{cli.command ? ''}")
|
||||
else
|
||||
capitanoExecuteAsync(cli)
|
||||
|
||||
Promise.all([events.trackCommand(cli), runCommand()])
|
||||
.catch(errors.handle)
|
||||
|
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').fromSharedOptions()
|
||||
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, { wait: false })
|
||||
, 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.loginIfTokenValid(token)
|
||||
.tap (loggedIn) ->
|
||||
if not loggedIn
|
||||
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({})
|
80
lib/auth/utils.coffee
Normal file
80
lib/auth/utils.coffee
Normal file
@ -0,0 +1,80 @@
|
||||
###
|
||||
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').fromSharedOptions()
|
||||
_ = 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 Log in using a token, but only if the 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.
|
||||
#
|
||||
# If authenticated, the token is persisted, if not then the previous
|
||||
# login state is restored.
|
||||
#
|
||||
# @param {String} token - session token or api key
|
||||
# @fulfil {Boolean} - whether the login was successful or not
|
||||
# @returns {Promise}
|
||||
#
|
||||
# utils.loginIfTokenValid('...').then (loggedIn) ->
|
||||
# if loggedIn
|
||||
# console.log('Token is valid!')
|
||||
###
|
||||
exports.loginIfTokenValid = (token) ->
|
||||
if not token? or _.isEmpty(token.trim())
|
||||
return Promise.resolve(false)
|
||||
|
||||
return resin.auth.getToken()
|
||||
.catchReturn(undefined)
|
||||
.then (currentToken) ->
|
||||
resin.auth.loginWithToken(token)
|
||||
.return(token)
|
||||
.then(resin.auth.isLoggedIn)
|
||||
.tap (isLoggedIn) ->
|
||||
return if isLoggedIn
|
||||
|
||||
if currentToken?
|
||||
return resin.auth.loginWithToken(currentToken)
|
||||
else
|
||||
return resin.auth.logout()
|
2
lib/config.ts
Normal file
2
lib/config.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export const sentryDsn =
|
||||
'https://56d2a46124614b01b0f4086897e96110:6e175465accc41b595a96947155f61fb@sentry.io/149239';
|
125
lib/errors.ts
Normal file
125
lib/errors.ts
Normal file
@ -0,0 +1,125 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
import * as _ from 'lodash';
|
||||
import * as os from 'os';
|
||||
import * as Raven from 'raven';
|
||||
import * as Promise from 'bluebird';
|
||||
import { stripIndent } from 'common-tags';
|
||||
|
||||
import * as patterns from './utils/patterns';
|
||||
|
||||
const captureException = Promise.promisify<string, Error>(
|
||||
Raven.captureException,
|
||||
{ context: Raven },
|
||||
);
|
||||
|
||||
function hasCode(error: any): error is Error & { code: string } {
|
||||
return error.code != null;
|
||||
}
|
||||
|
||||
function treatFailedBindingAsMissingModule(error: any): void {
|
||||
if (error.message.startsWith('Could not locate the bindings file.')) {
|
||||
error.code = 'MODULE_NOT_FOUND';
|
||||
}
|
||||
}
|
||||
|
||||
function interpret(error: any): string | undefined {
|
||||
if (!(error instanceof Error)) {
|
||||
return;
|
||||
}
|
||||
|
||||
treatFailedBindingAsMissingModule(error);
|
||||
|
||||
if (hasCode(error)) {
|
||||
const errorCodeHandler = messages[error.code];
|
||||
const message = errorCodeHandler && errorCodeHandler(error);
|
||||
|
||||
if (message) {
|
||||
return message;
|
||||
}
|
||||
|
||||
if (!_.isEmpty(error.message)) {
|
||||
return `${error.code}: ${error.message}`;
|
||||
}
|
||||
} else {
|
||||
return error.message;
|
||||
}
|
||||
}
|
||||
|
||||
const messages: {
|
||||
[key: string]: (error: Error & { path?: string }) => string;
|
||||
} = {
|
||||
EISDIR: error => `File is a directory: ${error.path}`,
|
||||
|
||||
ENOENT: error => `No such file or directory: ${error.path}`,
|
||||
|
||||
ENOGIT: () => stripIndent`
|
||||
Git is not installed on this system.
|
||||
Head over to http://git-scm.com to install it and run this command again.`,
|
||||
|
||||
EPERM: () => stripIndent`
|
||||
You don't have enough privileges to run this operation.
|
||||
${
|
||||
os.platform() === 'win32'
|
||||
? 'Run a new Command Prompt as administrator and try running this command again.'
|
||||
: 'Try running this command again prefixing it with `sudo`.'
|
||||
}
|
||||
|
||||
If this is not the case, and you're trying to burn an SDCard, check that the write lock is not set.`,
|
||||
|
||||
EACCES: e => messages.EPERM(e),
|
||||
|
||||
ETIMEDOUT: () =>
|
||||
'Oops something went wrong, please check your connection and try again.',
|
||||
|
||||
MODULE_NOT_FOUND: () => stripIndent`
|
||||
Part of the CLI could not be loaded. This typically means your CLI install is in a broken state.
|
||||
${
|
||||
os.arch() === 'x64'
|
||||
? 'You can normally fix this by uninstalling and reinstalling the CLI.'
|
||||
: stripIndent`
|
||||
You're using an unsupported architecture (${os.arch()}), so this is typically caused by missing native modules.
|
||||
Reinstalling may help, but pay attention to errors in native module build steps en route.
|
||||
`
|
||||
}
|
||||
`,
|
||||
|
||||
ResinExpiredToken: () => stripIndent`
|
||||
Looks like your session token is expired.
|
||||
Please try logging in again with:
|
||||
$ resin login`,
|
||||
};
|
||||
|
||||
exports.handle = function(error: any) {
|
||||
let message = interpret(error);
|
||||
if (message == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (process.env.DEBUG) {
|
||||
message = error.stack;
|
||||
}
|
||||
|
||||
patterns.printErrorMessage(message!);
|
||||
|
||||
return captureException(error)
|
||||
.timeout(1000)
|
||||
.catch(function() {
|
||||
// Ignore any errors (from error logging, or timeouts)
|
||||
})
|
||||
.finally(() => process.exit(error.exitCode || 1));
|
||||
};
|
@ -1,26 +0,0 @@
|
||||
_ = require('lodash')
|
||||
Mixpanel = require('mixpanel')
|
||||
Promise = require('bluebird')
|
||||
resin = require('resin-sdk')
|
||||
capitanoState = Promise.promisifyAll(require('capitano').state)
|
||||
packageJSON = require('../package.json')
|
||||
|
||||
exports.getLoggerInstance = _.memoize ->
|
||||
return resin.models.config.getMixpanelToken().then(Mixpanel.init)
|
||||
|
||||
exports.trackCommand = (capitanoCommand) ->
|
||||
return Promise.props
|
||||
resinUrl: resin.settings.get('resinUrl')
|
||||
username: resin.auth.whoami()
|
||||
mixpanel: exports.getLoggerInstance()
|
||||
.then (data) ->
|
||||
return capitanoState.getMatchCommandAsync(capitanoCommand.command).then (command) ->
|
||||
data.mixpanel.track "[CLI] #{command.signature.toString()}",
|
||||
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
|
48
lib/events.ts
Normal file
48
lib/events.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import * as Capitano from 'capitano';
|
||||
|
||||
import _ = require('lodash');
|
||||
import Mixpanel = require('mixpanel');
|
||||
import Raven = require('raven');
|
||||
import Promise = require('bluebird');
|
||||
import ResinSdk = require('resin-sdk');
|
||||
import packageJSON = require('../package.json');
|
||||
|
||||
const resin = ResinSdk.fromSharedOptions();
|
||||
const getMatchCommandAsync = Promise.promisify(Capitano.state.getMatchCommand);
|
||||
const getMixpanel = _.memoize<any>(() =>
|
||||
resin.models.config
|
||||
.getAll()
|
||||
.get('mixpanelToken')
|
||||
.then(Mixpanel.init),
|
||||
);
|
||||
|
||||
export function trackCommand(capitanoCli: Capitano.Cli) {
|
||||
return Promise.props({
|
||||
resinUrl: resin.settings.get('resinUrl'),
|
||||
username: resin.auth.whoami().catchReturn(undefined),
|
||||
mixpanel: getMixpanel(),
|
||||
})
|
||||
.then(({ username, resinUrl, mixpanel }) => {
|
||||
return getMatchCommandAsync(capitanoCli.command).then(command => {
|
||||
Raven.mergeContext({
|
||||
user: {
|
||||
id: username,
|
||||
username,
|
||||
},
|
||||
});
|
||||
|
||||
return mixpanel.track(`[CLI] ${command.signature.toString()}`, {
|
||||
distinct_id: username,
|
||||
argv: process.argv.join(' '),
|
||||
version: packageJSON.version,
|
||||
node: process.version,
|
||||
arch: process.arch,
|
||||
resinUrl,
|
||||
platform: process.platform,
|
||||
command: capitanoCli,
|
||||
});
|
||||
});
|
||||
})
|
||||
.timeout(100)
|
||||
.catchReturn(undefined);
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user