mirror of
https://github.com/balena-io/balena-cli.git
synced 2025-06-24 18:45:07 +00:00
Compare commits
1321 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 | |||
e8fa76266f | |||
3db3baeb7c | |||
17550f9bc9 | |||
44f80f7a39 | |||
372fa01cf3 | |||
9a515ef4e3 | |||
6beb6ff44e | |||
c2cd10f6a7 | |||
4d3769def8 | |||
42bfb3b0cc | |||
d9c2717fe4 | |||
8e93577f90 | |||
5b7e1b656e | |||
4a05ce3f53 | |||
61574a8522 | |||
9400d4027a | |||
b5ec49dda1 | |||
1c66efb4fa | |||
68fca3d030 | |||
325304aebe | |||
a50cf5b198 | |||
1b7aeeafc1 | |||
6e3c2ef168 | |||
d9b4753690 | |||
ca40d7ca65 | |||
4aa8362be9 | |||
d68b61a913 | |||
20969ef249 | |||
469d35fcc1 | |||
e9b8c38eeb | |||
d4c44bf350 | |||
2d8cf7c479 | |||
ca6e715bfa | |||
3a839c947e | |||
9896de3c34 | |||
03d7520de2 | |||
4fc8b130f8 | |||
c5692f8b13 | |||
85561b5d50 | |||
88c0833ae2 | |||
7df78a0c9e | |||
30663b0301 | |||
4ffba0ed56 | |||
a522c70f92 | |||
f295837840 | |||
091ae8eb03 | |||
6405c6bb6f | |||
797122ce37 | |||
f81db5a775 | |||
0f84aea47d | |||
84ed20d3ec | |||
9870727e36 | |||
db3de2137b | |||
92c6af91ca | |||
d578f62dca | |||
d4527220c3 | |||
d5797124f5 | |||
654c3c627d | |||
3953b00e77 | |||
13f33da280 | |||
356d2ef6b2 | |||
36a8179e0c | |||
aedb9c732f | |||
c3bd433532 | |||
d72750de65 | |||
ccd8e73c4e | |||
6c677fe8cd | |||
eaa1a798c5 | |||
da1b446b3b | |||
18b4509fef | |||
35bba04b16 | |||
dd382158dd | |||
40f015de93 | |||
bee523828a | |||
474635401a | |||
a8c30bb395 | |||
9e00fdaf31 | |||
5f83c870ed | |||
5d89533afc | |||
a346c3f043 | |||
de0649c980 | |||
b7300deab7 | |||
70b2ba3ab9 | |||
8cbf792786 | |||
bca9a7f51f | |||
4bf079377b | |||
ebefd816b6 | |||
fb1ef0df63 | |||
4489b1daa0 | |||
96f0b5fbd6 | |||
f6897ad41f | |||
69e031da28 | |||
c61a7ef94a | |||
65bc22c02c | |||
1d55ea4dcf | |||
add30b33a1 | |||
999120e711 | |||
67ce0f7f2d | |||
4645ad06bc | |||
612437ae58 | |||
35a821b904 | |||
e4359834d6 | |||
ef7e39450c | |||
c3a5998d5c | |||
78ab2af8ba | |||
11354de596 | |||
86cac606e4 | |||
9b052c9aa5 | |||
8d709aea7d | |||
70ea8dd1a3 | |||
11c0d2a847 | |||
301b8a6ba3 | |||
acb0aa445c | |||
a90d578c85 | |||
d859228aa9 | |||
2be105d329 | |||
6d48fcfd6f | |||
551a315432 | |||
351dfdb892 | |||
dc6727fbf1 | |||
af88e48c39 | |||
9cfce68489 | |||
8393ff647c | |||
63122a5f51 | |||
0ad4598575 | |||
b71c28cec0 | |||
b0ab23dad4 | |||
de9297c351 | |||
26e3cb7957 | |||
bb78a3ca09 | |||
210680c9c9 | |||
6810eb31fd | |||
f5b6df483d | |||
7d0da7adc0 | |||
785f2b4ef5 | |||
b668a8c7d0 | |||
2e247faae4 | |||
3997a61b78 | |||
8ef27f0525 | |||
20855be968 | |||
800d13e3cd | |||
abc8399260 | |||
9ad2ba1131 | |||
0f8d6a98e3 | |||
81af8c74b4 | |||
16ab74294f | |||
f8bcc9d1ea | |||
9a89e3c3ca | |||
359c37f259 | |||
1ba5697986 | |||
647ed1e7aa | |||
b881e23c1c | |||
ae8621dc81 | |||
e08c3752f9 | |||
65646d1206 | |||
e42d3e8c4c | |||
a4642f6184 | |||
038c871911 | |||
f52dd2976f | |||
d079a57da4 | |||
43697a3476 | |||
b893bd1e39 | |||
c3b5a768e1 | |||
111ea44b40 | |||
d1b25c17b6 | |||
ba318f2939 | |||
7ac0291c53 | |||
f64676ab98 | |||
d522cbe1ca | |||
4fc7a4e436 | |||
79f2b4f0d5 | |||
880a7b1e25 | |||
89c5bb3080 | |||
a71fb8ca4d | |||
3b35aed3bf | |||
0bc3b9460f | |||
5509a3e9fd | |||
f84d0d0980 | |||
c866f6e46c | |||
3192f9d2ef | |||
de83a06db8 | |||
9260c8dce2 | |||
bea5f22732 | |||
363f12f81b | |||
1cbd33679f | |||
e962371b59 | |||
77a3d4d6f6 | |||
fc5fe6cf68 | |||
f921488e8c | |||
8fe08642f5 | |||
822632718f | |||
f66cd00646 | |||
5498e45a35 | |||
38479b3191 | |||
965fd8fc19 | |||
7a4f551a47 | |||
303fd74012 | |||
6a7d1b0c70 | |||
c1e6a28640 | |||
ec72f93480 | |||
8913fb515b | |||
6f91ff898f | |||
aa949103f6 | |||
14bd4ad74e | |||
f2507daa09 | |||
ff81c1e514 | |||
7ce92b47a0 | |||
bde5cc65da | |||
ec9347c3a4 | |||
ceb8dada1d | |||
f4186acf80 | |||
05ce54eac1 | |||
d28ecf3230 | |||
8562f723c5 | |||
f6d2043747 | |||
485818f3b5 | |||
ec28bd9c9e | |||
ad68dcf692 | |||
0b7e2a2c8c | |||
b6ebd0631a | |||
7ace4bdfa6 | |||
1cfbd4197d | |||
b2425d2c0e | |||
0ba914236a | |||
71ee0a6cf7 | |||
4326ad4d9c | |||
055bac6ff4 | |||
58713dc291 | |||
adf4aef517 | |||
a17bb2cc09 | |||
48d620f7cd | |||
fa7b104762 | |||
faf2fa167f | |||
ad1e68427c | |||
e9147f0f6f | |||
cddf630907 | |||
494a286cae | |||
e5e871ddcd | |||
5a3166e3de | |||
3149464c7a | |||
97d9b7816f | |||
ec77437080 | |||
d65882639a | |||
f8470287c1 | |||
3cc41ed62a | |||
445e37ccaf | |||
ed6427c541 | |||
90be01b05d | |||
1124293b9a | |||
0e804fdfd8 | |||
0443a35f2b | |||
3a148217e0 | |||
79d1892b66 | |||
0e06ac464f | |||
5ae83d8337 | |||
8694ee2c59 | |||
8234f7675a | |||
15cb0c4889 | |||
a3ebd9827f | |||
30d84f015a | |||
686414b03d | |||
6377618c12 | |||
f17e9c97b8 | |||
21fcdfaff6 | |||
4072edcced | |||
d704c10197 | |||
dea2a7f055 | |||
7e6eb4b9e4 | |||
61474fba5c | |||
42256384be | |||
e25f232fe5 | |||
f6d8f12ba2 | |||
d2b9e6fd8c | |||
cf7effacc0 | |||
1eb7aba293 | |||
21e916679c | |||
d574743c21 | |||
d8a2b82662 | |||
f4ebd890df | |||
b0be5de83a | |||
ba21ddd010 | |||
eecd7a0fd8 | |||
eb4c2f62a7 | |||
dc1e5e6512 | |||
adc0b183cd | |||
828b4f73d1 | |||
82a0761f49 | |||
904b9f07fb | |||
3c0acaa7da | |||
64c8420c9d | |||
26e3dc1aa7 | |||
3bc22a7b7c | |||
7e5b2695fe | |||
79afa79fd9 | |||
cdaaddb826 | |||
ec5f6a7cd8 | |||
72f34031a9 | |||
dc257b5cab | |||
4bdcd3d2ee | |||
b0650530cc | |||
454669ada2 | |||
a65975596e | |||
91f878cfc0 | |||
8c3e832cdc | |||
a5c670902d | |||
14be1d5f9f | |||
41d6d5c670 | |||
a090e6c21d | |||
d78c1ffa47 | |||
5808d20d88 | |||
7f4065e3da | |||
07daa51051 | |||
d10d4ce185 | |||
7f763e7881 | |||
5de0f66d7a | |||
354921ca92 | |||
fb28cc7d2f | |||
7cf89a9bf6 | |||
a3cbc549d8 | |||
1c48328347 | |||
9b69fe3c3c | |||
dc513a08f6 | |||
fcc44949a7 | |||
006764af66 | |||
b879d3f9ea | |||
7f4863da86 | |||
ba6f50d171 | |||
a803d4f646 | |||
85d940df66 | |||
6f9535ca34 | |||
019e2ac357 | |||
433916e18a | |||
f19588032f | |||
0595452c3d | |||
d6305df48e | |||
f7084580b2 | |||
3dd5f5858a | |||
02a06e1e7c | |||
50daf8ef73 | |||
fd5a34a1c4 | |||
51fda13684 | |||
a698b25fda | |||
89bd861d8e | |||
e5b7aae4ae | |||
031168ceed | |||
09a5788902 | |||
713664d103 | |||
f63391acf9 | |||
79ee4302ec | |||
9adda22921 | |||
25311f2a18 | |||
70c060b124 | |||
7a8a3c851b | |||
1096b2d212 | |||
ee286c5690 | |||
1da1d2e6fc | |||
64be9f936d | |||
30f24333c0 | |||
30f6a78282 | |||
8c9a0e0ff1 | |||
8268bbf700 | |||
e712e2f266 | |||
0807b6a2d9 | |||
83382cc8f7 | |||
8401aaeae2 | |||
abf5740950 | |||
606777508d | |||
e9ec6c67b2 | |||
69566f7fc3 | |||
6e4b299c7d | |||
ef35ebf79d | |||
1bc78edf71 | |||
d5204a09f7 | |||
4647aa70c0 | |||
2e8ec3ac64 | |||
25c6246e9f | |||
5408938007 | |||
50cb04b6f7 | |||
09fe4b11ad | |||
4157f21e06 | |||
4900230881 | |||
e60c0605e5 | |||
085781fa18 | |||
08ad8bd5d7 | |||
3d36e5f5d3 | |||
57319f26a6 | |||
11033683fd | |||
e5166c6c8e | |||
5c96663d1e | |||
ffb48c8669 | |||
12145a2393 | |||
f379866c1c | |||
dc030f4cd1 | |||
b726a2d778 | |||
a715ec9dc1 | |||
d24b871964 | |||
bd0c4e6034 | |||
95637e5608 | |||
93b394ed76 | |||
b515e427ff | |||
26b1acf5ef | |||
f31eb7c2b5 | |||
d423a6ea24 | |||
4211333e4e | |||
f220e380a7 | |||
9564b4e478 | |||
e2125b8ce9 | |||
0bb0e6ea4b | |||
cf512cc01b | |||
fe3e68c3af | |||
0bbfbe36c7 | |||
d6ae689593 | |||
5b5d1be52f | |||
cb808869dd | |||
64d83dccfb | |||
122253bb25 | |||
1d53db2854 | |||
30dc5ea1ea | |||
58c7ff1f1b | |||
57b9d634be | |||
6fb25dea88 | |||
10478e389a | |||
46fa4ee2a2 | |||
a7cefe44bf | |||
3d03585318 | |||
076c3428ee | |||
9d4ac46985 | |||
24edb867fa | |||
e926ac46c9 | |||
9ffd657dbb | |||
aa3cb39551 | |||
0acbdac66f | |||
5619bdbb67 | |||
381e63bfc9 | |||
e779347ff2 | |||
8fa906dd48 | |||
7e5ecd634d | |||
29cf4c1e89 | |||
73736abdea | |||
9ab411bade | |||
ef33156de7 | |||
6d8fd6e547 | |||
bab90a8bf2 | |||
43f0288c6c | |||
47e6371e2e | |||
5e400ed335 | |||
56a78b57af | |||
2bfeb7f42c | |||
37e0f12f89 | |||
fdd0e4a966 | |||
38df7e0038 | |||
e9efb78280 | |||
0424d7b640 | |||
2b80c7c91f | |||
142e2c0a65 | |||
1ed9ae7d60 | |||
329bf25dbd | |||
e18ffba183 | |||
ae3f0b429d | |||
34736c4e9b | |||
806678ee5f | |||
1e70401bdd | |||
3c7615f20d | |||
054d5e4879 | |||
b04ed43bed | |||
c2bbb952c5 | |||
63a9e1859b | |||
68ef069e6a | |||
72d4b21cb4 | |||
e8b931680d | |||
218b407f30 | |||
773cc27145 | |||
1c76e2e15b | |||
e466cfd6ff | |||
992b04d521 | |||
5c16c86871 | |||
78af9bbb10 | |||
b78c48d89f | |||
1f8c610bb0 | |||
d220728380 | |||
db58e9986c | |||
2a5e66eca6 | |||
e7e8ec296c | |||
80e69c56d0 | |||
ce3c6aecff | |||
300f76ba83 | |||
a4e8a38bf4 | |||
9bb04f43a8 | |||
9b0c08bd46 | |||
679d48e86e | |||
4b6bf60531 | |||
d8ce6648e2 | |||
402bc2d3a8 | |||
f3e193be0f | |||
f5b461612b | |||
d2514a3fc3 | |||
2019f9fdeb | |||
656f3e5cd9 | |||
741acfbba3 | |||
4b7eca02a0 | |||
c7788a80e1 | |||
08648894e3 | |||
4c5d5697bc | |||
c758a5b9ea | |||
2af54e2e28 | |||
cf9b23b987 | |||
f8380ec8e6 | |||
cd3245a631 | |||
144a155208 | |||
7f7ca13001 | |||
361e149063 | |||
fab85b381a | |||
faecf103bf | |||
a0f8c1aa8c | |||
24726b575d | |||
688b89a099 | |||
2e39110cc0 | |||
193cedae26 | |||
3d1f023ef5 | |||
9bb51d7146 | |||
84900aa588 | |||
b2bce3c4ac | |||
8c11620a42 | |||
7c1e0ac2e9 | |||
9a3c844336 | |||
69039d21c2 | |||
34c9cdd8bb | |||
0a8b30e824 | |||
ea5b521262 | |||
3bc71577b5 | |||
0fb24162fc | |||
d0d63f5bbb | |||
8041905144 | |||
37d96e238d | |||
ae8c941bfe | |||
4c16835ca4 | |||
7bd8922a4e | |||
42b2e2fcf0 | |||
f7256e9927 | |||
85444a5a6a | |||
ac115c7ea3 | |||
3c2a440553 |
@ -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:**
|
18
.gitignore
vendored
18
.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,9 +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,5 +1,5 @@
|
||||
coffee_script:
|
||||
config_file: coffeelint.json
|
||||
|
||||
java_script:
|
||||
javascript:
|
||||
enabled: false
|
||||
|
5
.prettierrc
Normal file
5
.prettierrc
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"useTabs": true
|
||||
}
|
19
.travis.yml
19
.travis.yml
@ -1,4 +1,19 @@
|
||||
language: node_js
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
node_js:
|
||||
- "0.11"
|
||||
- "0.10"
|
||||
- "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
|
||||
|
1057
CHANGELOG.md
Normal file
1057
CHANGELOG.md
Normal file
File diff suppressed because it is too large
Load Diff
190
LICENSE
190
LICENSE
@ -1,21 +1,177 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2014 Resin.io, Inc. https://resin.io
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
1. Definitions.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
133
README.md
133
README.md
@ -1,65 +1,126 @@
|
||||
# Resin CLI
|
||||
Resin CLI
|
||||
=========
|
||||
|
||||
> The official resin.io CLI tool.
|
||||
|
||||
[](http://badge.fury.io/js/resin-cli)
|
||||
[](https://david-dm.org/resin-io/resin-cli.png)
|
||||
[](https://travis-ci.org/resin-io/resin-cli)
|
||||
[](https://david-dm.org/resin-io/resin-cli)
|
||||
[](https://gitter.im/resin-io/chat)
|
||||
|
||||
The official Resin CLI tool.
|
||||
Requisites
|
||||
----------
|
||||
|
||||
## Installing
|
||||
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
|
||||
---------------
|
||||
|
||||
### 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 -g resin-cli
|
||||
$ npm install resin-cli -g --production --unsafe-perm
|
||||
```
|
||||
|
||||
### Running locally
|
||||
`--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
|
||||
$ ./bin/resin
|
||||
$ resin login
|
||||
```
|
||||
|
||||
## Tests
|
||||
_(Typically useful, but not strictly required for all commands)_
|
||||
|
||||
You can run the [Mocha](http://mochajs.org/) test suite, you can do:
|
||||
### Run commands
|
||||
|
||||
```sh
|
||||
$ gulp test
|
||||
```
|
||||
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`.
|
||||
|
||||
## Development mode
|
||||
### Bash completions
|
||||
|
||||
The following command will watch for any changes and will run a linter and the whole test suite:
|
||||
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`.
|
||||
|
||||
```sh
|
||||
$ gulp watch
|
||||
```
|
||||
FAQ
|
||||
---
|
||||
|
||||
If you set `DEBUG` environment variable, errors will print with a stack trace:
|
||||
### Where is my configuration file?
|
||||
|
||||
```sh
|
||||
$ DEBUG=true resin ...
|
||||
```
|
||||
The per-user configuration file lives in `$HOME/.resinrc.yml` or `%UserProfile%\_resinrc.yml`, in Unix based operating systems and Windows respectively.
|
||||
|
||||
## Documentation
|
||||
The Resin CLI also attempts to read a `resinrc.yml` file in the current directory, which takes precedence over the per-user configuration file.
|
||||
|
||||
You can renegerate the documentation with:
|
||||
### How do I point the Resin CLI to staging?
|
||||
|
||||
```sh
|
||||
$ npm run-script doc
|
||||
```
|
||||
The easiest way is to set the `RESINRC_RESIN_URL=resinstaging.io` environment variable.
|
||||
|
||||
## Manual pages
|
||||
Alternatively, you can edit your configuration file and set `resinUrl: resinstaging.io` to persist this setting.
|
||||
|
||||
UNIX manual pages reside in `man/`
|
||||
### How do I make the Resin CLI persist data in another directory?
|
||||
|
||||
You can regenerate UNIX `roff` manual pages from markdown with:
|
||||
The Resin CLI persists your session token, as well as cached images in `$HOME/.resin` or `%UserProfile%\_resin`.
|
||||
|
||||
```sh
|
||||
$ gulp man
|
||||
```
|
||||
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.
|
||||
|
||||
If you add a new `man` page, remember to add the generated filename to the `man` array in `package.json`.
|
||||
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.
|
||||
|
||||
## Caveats
|
||||
Support
|
||||
-------
|
||||
|
||||
- Some interactive widgets don't work on [Cygwin](https://cygwin.com/). If you're running Windows, it's preferrable that you use `cmd.exe`, as `Cygwin` is [not official supported by Node.js](https://github.com/chjj/blessed/issues/56#issuecomment-42671945).
|
||||
If you're having any problems, check our [troubleshooting guide](https://github.com/resin-io/resin-cli/blob/master/TROUBLESHOOTING.md) and if your problem is not addressed there, please [raise an issue](https://github.com/resin-io/resin-cli/issues/new) on GitHub and the resin.io team will be happy to help.
|
||||
|
||||
You can also get in touch with us in the resin.io [forums](https://forums.resin.io/).
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
The project is licensed under the Apache 2.0 license.
|
||||
|
59
TROUBLESHOOTING.md
Normal file
59
TROUBLESHOOTING.md
Normal file
@ -0,0 +1,59 @@
|
||||
Troubleshooting
|
||||
===============
|
||||
|
||||
This document contains common issues related to the Resin CLI, and how to fix them.
|
||||
|
||||
### After burning to an sdcard, my device doesn't boot
|
||||
|
||||
- The downloaded image is not complete (download was interrupted).
|
||||
|
||||
Please clean the cache (`%HOME/.resin/cache` or `C:\Users\<user>\_resin\cache`) and run the command again. In the future, the CLI will check that the image is not complete and clean the cache for you.
|
||||
|
||||
### I get a permission error when burning to an sdcard
|
||||
|
||||
- The SDCard is locked.
|
||||
|
||||
### I get EINVAL errors on Cygwin
|
||||
|
||||
The errors look something like this:
|
||||
|
||||
```
|
||||
net.js:156
|
||||
this._handle.open(options.fd);
|
||||
^
|
||||
Error: EINVAL, invalid argument
|
||||
at new Socket (net.js:156:18)
|
||||
at process.stdin (node.js:664:19)
|
||||
at Object.Interface.createInterface (C:\cygwin\home\Juan Cruz Viotti\Projects\resin-cli\node_modules\inquirer\node_modules\readline2\index.js:31:43)
|
||||
at PromptUI.UI (C:\cygwin\home\Juan Cruz Viotti\Projects\resin-cli\node_modules\inquirer\lib\ui\baseUI.js:23:40)
|
||||
at new PromptUI (C:\cygwin\home\Juan Cruz Viotti\Projects\resin-cli\node_modules\inquirer\lib\ui\prompt.js:26:8)
|
||||
at Object.promptModule [as prompt] (C:\cygwin\home\Juan Cruz Viotti\Projects\resin-cli\node_modules\inquirer\lib\inquirer.js:27:14)
|
||||
```
|
||||
|
||||
- Some interactive widgets don't work on `Cygwin`. If you're running Windows, it's preferrable that you use `cmd.exe`, as `Cygwin` is [not official supported by Node.js](https://github.com/chjj/blessed/issues/56#issuecomment-42671945).
|
||||
|
||||
### I get `Invalid MBR boot signature` when configuring a device
|
||||
|
||||
This error, accompanied with something like: `Expected 0xAA55, but saw 0x29FE` usually indicates a corrupted device operating system image in the cache, due to bad a internet connection during the download process.
|
||||
|
||||
Try clearing the cache with the following command and trying again:
|
||||
|
||||
```sh
|
||||
$ rm -rf $HOME/.resin/cache
|
||||
```
|
||||
|
||||
Or in Windows:
|
||||
|
||||
```sh
|
||||
> del /s /q %UserProfile%\_resin\cache
|
||||
```
|
||||
|
||||
### I get `EACCES: permission denied` when logging in
|
||||
|
||||
The Resin CLI stores the session token in `$HOME/.resin` or `C:\Users\<user>\_resin` in UNIX based operating systems and Windows respectively. This error usually indicates that the user doesn't have permissions over that directory, which can happen if you ran the Resin CLI as `root`, and thus the directory got owned by him.
|
||||
|
||||
Try resetting the ownership by running:
|
||||
|
||||
```sh
|
||||
$ sudo chown -R <user> $HOME/.resin
|
||||
```
|
34
appveyor.yml
Normal file
34
appveyor.yml
Normal file
@ -0,0 +1,34 @@
|
||||
# appveyor file
|
||||
# http://www.appveyor.com/docs/appveyor-yml
|
||||
|
||||
init:
|
||||
- git config --global core.autocrlf input
|
||||
|
||||
cache:
|
||||
- C:\Users\appveyor\.node-gyp
|
||||
- '%AppData%\npm-cache'
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
|
||||
# what combinations to test
|
||||
environment:
|
||||
matrix:
|
||||
- nodejs_version: 6
|
||||
|
||||
install:
|
||||
- ps: Install-Product node $env:nodejs_version x64
|
||||
- npm install -g npm@4
|
||||
- set PATH=%APPDATA%\npm;%PATH%
|
||||
- npm install
|
||||
|
||||
build: off
|
||||
|
||||
test_script:
|
||||
- node --version
|
||||
- npm --version
|
||||
- cmd: npm test
|
||||
|
||||
deploy_script:
|
||||
- IF "%APPVEYOR_REPO_TAG%" == "true" (npm run release)
|
||||
- IF NOT "%APPVEYOR_REPO_TAG%" == "true" (echo 'Not tagged, skipping deploy')
|
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,16 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
var os = require('../build/actions/os');
|
||||
var errors = require('../build/errors');
|
||||
|
||||
// TODO: Do some error handling when image or device are incorrect
|
||||
|
||||
os.install.action({
|
||||
image: process.argv[2],
|
||||
device: process.argv[3]
|
||||
}, {
|
||||
yes: true,
|
||||
fromScript: true
|
||||
}, function(error) {
|
||||
return errors.handle(error);
|
||||
});
|
@ -1,162 +0,0 @@
|
||||
(function() {
|
||||
var _, async, commandOptions, path, resin, vcs, visuals;
|
||||
|
||||
path = require('path');
|
||||
|
||||
_ = require('lodash-contrib');
|
||||
|
||||
async = require('async');
|
||||
|
||||
resin = require('resin-sdk');
|
||||
|
||||
visuals = require('resin-cli-visuals');
|
||||
|
||||
commandOptions = require('./command-options');
|
||||
|
||||
vcs = require('resin-vcs');
|
||||
|
||||
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',
|
||||
action: function(params, options, done) {
|
||||
return async.waterfall([
|
||||
function(callback) {
|
||||
if (options.type != null) {
|
||||
return callback(null, options.type);
|
||||
}
|
||||
return resin.models.device.getSupportedDeviceTypes(function(error, deviceTypes) {
|
||||
if (error != null) {
|
||||
return callback(error);
|
||||
}
|
||||
return visuals.widgets.select('Select a type', deviceTypes, callback);
|
||||
});
|
||||
}, function(type, callback) {
|
||||
return resin.models.application.create(params.name, type, callback);
|
||||
}
|
||||
], 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',
|
||||
action: function(params, options, done) {
|
||||
return resin.models.application.getAll(function(error, applications) {
|
||||
if (error != null) {
|
||||
return done(error);
|
||||
}
|
||||
console.log(visuals.widgets.table.horizontal(applications, ['id', 'app_name', 'device_type', 'online_devices', 'devices_length']));
|
||||
return 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',
|
||||
action: function(params, options, done) {
|
||||
return resin.models.application.get(params.name, function(error, application) {
|
||||
if (error != null) {
|
||||
return done(error);
|
||||
}
|
||||
console.log(visuals.widgets.table.vertical(application, ['id', 'app_name', 'device_type', 'git_repository', 'commit']));
|
||||
return 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) {
|
||||
return resin.models.application.restart(params.name, 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) {
|
||||
return visuals.patterns.remove('application', options.yes, function(callback) {
|
||||
return resin.models.application.remove(params.name, callback);
|
||||
}, done);
|
||||
}
|
||||
};
|
||||
|
||||
exports.associate = {
|
||||
signature: 'app associate <name>',
|
||||
description: 'associate a resin project',
|
||||
help: 'Use this command to associate a project directory with a resin application.\n\nThis command adds a \'resin\' git remote to the directory and runs git init if necessary.\n\nExamples:\n\n $ resin app associate MyApp\n $ resin app associate MyApp --project my/app/directory',
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
var currentDirectory;
|
||||
currentDirectory = process.cwd();
|
||||
return async.waterfall([
|
||||
function(callback) {
|
||||
return vcs.initialize(currentDirectory, callback);
|
||||
}, function(callback) {
|
||||
return resin.models.application.get(params.name, callback);
|
||||
}, function(application, callback) {
|
||||
return vcs.addRemote(currentDirectory, application.git_repository, callback);
|
||||
}
|
||||
], function(error, remoteUrl) {
|
||||
if (error != null) {
|
||||
return done(error);
|
||||
}
|
||||
console.info("git repository added: " + remoteUrl);
|
||||
return done(null, remoteUrl);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.init = {
|
||||
signature: 'init',
|
||||
description: 'init an application',
|
||||
help: 'Use this command to initialise a directory as a resin application.\n\nThis command performs the following steps:\n - Create a resin.io application.\n - Initialize the current directory as a git repository.\n - Add the corresponding git remote to the application.\n\nExamples:\n\n $ resin init\n $ resin init --project my/app/directory',
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
var currentDirectory;
|
||||
currentDirectory = process.cwd();
|
||||
return async.waterfall([
|
||||
function(callback) {
|
||||
var currentDirectoryBasename;
|
||||
currentDirectoryBasename = path.basename(currentDirectory);
|
||||
return visuals.widgets.ask('What is the name of your application?', currentDirectoryBasename, callback);
|
||||
}, function(applicationName, callback) {
|
||||
return exports.create.action({
|
||||
name: applicationName
|
||||
}, options, function(error) {
|
||||
if (error != null) {
|
||||
return callback(error);
|
||||
}
|
||||
return callback(null, applicationName);
|
||||
});
|
||||
}, function(applicationName, callback) {
|
||||
return exports.associate.action({
|
||||
name: applicationName
|
||||
}, options, callback);
|
||||
}
|
||||
], done);
|
||||
}
|
||||
};
|
||||
|
||||
}).call(this);
|
@ -1,140 +0,0 @@
|
||||
(function() {
|
||||
var TOKEN_URL, _, async, open, resin, settings, url, visuals;
|
||||
|
||||
open = require('open');
|
||||
|
||||
_ = require('lodash-contrib');
|
||||
|
||||
url = require('url');
|
||||
|
||||
async = require('async');
|
||||
|
||||
resin = require('resin-sdk');
|
||||
|
||||
settings = require('resin-settings-client');
|
||||
|
||||
visuals = require('resin-cli-visuals');
|
||||
|
||||
exports.whoami = {
|
||||
signature: 'whoami',
|
||||
description: 'whoami',
|
||||
help: 'Use this command to get the logged in user name.\n\nExamples:\n\n $ resin whoami',
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
return resin.auth.whoami(function(error, username) {
|
||||
if (error != null) {
|
||||
return done(error);
|
||||
}
|
||||
console.log(username);
|
||||
return done();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
TOKEN_URL = url.resolve(settings.get('remoteUrl'), '/preferences');
|
||||
|
||||
exports.login = {
|
||||
signature: 'login [token]',
|
||||
description: 'login to resin.io',
|
||||
help: "Use this command to login to your resin.io account.\n\nTo login, you need your token, which is accesible from the preferences page:\n\n " + TOKEN_URL + "\n\nExamples:\n\n $ resin login\n $ resin login \"eyJ0eXAiOiJKV1Qi...\"",
|
||||
action: function(params, options, done) {
|
||||
console.info("To login to the Resin CLI, you need your unique token, which is accesible from\nthe preferences page at " + TOKEN_URL + "\n\nAttempting to open a browser at that location...");
|
||||
return async.waterfall([
|
||||
function(callback) {
|
||||
return open(TOKEN_URL, function(error) {
|
||||
if (error != null) {
|
||||
console.error("Unable to open a web browser in the current environment.\nPlease visit " + TOKEN_URL + " manually.");
|
||||
}
|
||||
return callback();
|
||||
});
|
||||
}, function(callback) {
|
||||
if (params.token != null) {
|
||||
return callback(null, params.token);
|
||||
}
|
||||
return visuals.widgets.ask('What\'s your token? (visible in the preferences page)', null, callback);
|
||||
}, function(token, callback) {
|
||||
return resin.auth.loginWithToken(token, done);
|
||||
}
|
||||
], 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) {
|
||||
return resin.auth.logout(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 signup --email me@mycompany.com --username johndoe --password ***********\n\n $ resin whoami\n johndoe',
|
||||
options: [
|
||||
{
|
||||
signature: 'email',
|
||||
parameter: 'email',
|
||||
description: 'user email',
|
||||
alias: 'e'
|
||||
}, {
|
||||
signature: 'username',
|
||||
parameter: 'username',
|
||||
description: 'user name',
|
||||
alias: 'u'
|
||||
}, {
|
||||
signature: 'password',
|
||||
parameter: 'user password',
|
||||
description: 'user password',
|
||||
alias: 'p'
|
||||
}
|
||||
],
|
||||
action: function(params, options, done) {
|
||||
var hasOptionCredentials;
|
||||
hasOptionCredentials = !_.isEmpty(options);
|
||||
if (hasOptionCredentials) {
|
||||
if (options.email == null) {
|
||||
return done(new Error('Missing email'));
|
||||
}
|
||||
if (options.username == null) {
|
||||
return done(new Error('Missing username'));
|
||||
}
|
||||
if (options.password == null) {
|
||||
return done(new Error('Missing password'));
|
||||
}
|
||||
}
|
||||
return async.waterfall([
|
||||
function(callback) {
|
||||
if (hasOptionCredentials) {
|
||||
return callback(null, options);
|
||||
}
|
||||
return visuals.widgets.register(callback);
|
||||
}, function(credentials, callback) {
|
||||
return resin.auth.register(credentials, function(error, token) {
|
||||
return callback(error, credentials);
|
||||
});
|
||||
}, function(credentials, callback) {
|
||||
return resin.auth.login(credentials, callback);
|
||||
}
|
||||
], done);
|
||||
}
|
||||
};
|
||||
|
||||
exports.whoami = {
|
||||
signature: 'whoami',
|
||||
description: 'get current username',
|
||||
help: 'Use this command to find out the current logged in username.\n\nExamples:\n\n $ resin whoami',
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
return resin.auth.whoami(function(error, username) {
|
||||
if (username == null) {
|
||||
return done(new Error('Username not found'));
|
||||
}
|
||||
return console.log(username);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
}).call(this);
|
@ -1,45 +0,0 @@
|
||||
(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.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,150 +0,0 @@
|
||||
(function() {
|
||||
var _, async, commandOptions, osAction, path, resin, vcs, visuals;
|
||||
|
||||
_ = require('lodash-contrib');
|
||||
|
||||
path = require('path');
|
||||
|
||||
async = require('async');
|
||||
|
||||
resin = require('resin-sdk');
|
||||
|
||||
visuals = require('resin-cli-visuals');
|
||||
|
||||
vcs = require('resin-vcs');
|
||||
|
||||
commandOptions = require('./command-options');
|
||||
|
||||
osAction = require('./os');
|
||||
|
||||
exports.list = {
|
||||
signature: 'devices',
|
||||
description: 'list all devices',
|
||||
help: 'Use this command to list all devices that belong to a certain application.\n\nExamples:\n\n $ resin devices --application MyApp',
|
||||
options: [commandOptions.application],
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
return resin.models.device.getAllByApplication(options.application, function(error, devices) {
|
||||
if (error != null) {
|
||||
return done(error);
|
||||
}
|
||||
console.log(visuals.widgets.table.horizontal(devices, ['id', 'name', 'device_type', 'is_online', 'application_name', 'status', 'last_seen']));
|
||||
return done();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.info = {
|
||||
signature: 'device <name>',
|
||||
description: 'list a single device',
|
||||
help: 'Use this command to show information about a single device.\n\nExamples:\n\n $ resin device MyDevice',
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
return resin.models.device.get(params.name, function(error, device) {
|
||||
if (error != null) {
|
||||
return done(error);
|
||||
}
|
||||
console.log(visuals.widgets.table.vertical(device, ['id', 'name', 'device_type', 'is_online', 'ip_address', 'application_name', 'status', 'last_seen', 'uuid', 'commit', 'supervisor_version', 'is_web_accessible', 'note']));
|
||||
return done();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.remove = {
|
||||
signature: 'device rm <name>',
|
||||
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 MyDevice\n $ resin device rm MyDevice --yes',
|
||||
options: [commandOptions.yes],
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
return visuals.patterns.remove('device', options.yes, function(callback) {
|
||||
return resin.models.device.remove(params.name, callback);
|
||||
}, 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 23c73a12e3527df55c60b9ce647640c1b7da1b32d71e6a39849ac0f00db828',
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
return resin.models.device.identify(params.uuid, done);
|
||||
}
|
||||
};
|
||||
|
||||
exports.rename = {
|
||||
signature: 'device rename <name> [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 MyDevice MyPi\n $ resin device rename MyDevice',
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
return async.waterfall([
|
||||
function(callback) {
|
||||
if (!_.isEmpty(params.newName)) {
|
||||
return callback(null, params.newName);
|
||||
}
|
||||
return visuals.widgets.ask('How do you want to name this device?', null, callback);
|
||||
}, function(newName, callback) {
|
||||
return resin.models.device.rename(params.name, newName, callback);
|
||||
}
|
||||
], done);
|
||||
}
|
||||
};
|
||||
|
||||
exports.supported = {
|
||||
signature: 'devices supported',
|
||||
description: 'list all supported devices',
|
||||
help: 'Use this command to get the list of all supported devices\n\nExamples:\n\n $ resin devices supported',
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
return resin.models.device.getSupportedDeviceTypes(function(error, devices) {
|
||||
if (error != null) {
|
||||
return done(error);
|
||||
}
|
||||
_.each(devices, _.unary(console.log));
|
||||
return done();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.init = {
|
||||
signature: 'device init [device]',
|
||||
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\nNote that this command requires admin privileges.\n\nIf `device` is omitted, you will be prompted to select a device interactively.\n\nNotice this command asks for confirmation interactively.\nYou can avoid this by passing the `--yes` boolean option.\n\nYou can quiet the progress bar by passing the `--quiet` boolean option.\n\nYou may have to unmount the device before attempting this operation.\n\nYou need to configure the network type and other settings:\n\nEthernet:\n You can setup the device OS to use ethernet by setting the `--network` option to "ethernet".\n\nWifi:\n You can setup the device OS to use wifi by setting the `--network` option to "wifi".\n If you set "network" to "wifi", you will need to specify the `--ssid` and `--key` option as well.\n\nYou can omit network related options to be asked about them interactively.\n\nExamples:\n\n $ resin device init\n $ resin device init --application 91\n $ resin device init --application 91 --network ethernet\n $ resin device init /dev/disk2 --application 91 --network wifi --ssid MyNetwork --key secret',
|
||||
options: [commandOptions.optionalApplication, commandOptions.network, commandOptions.wifiSsid, commandOptions.wifiKey],
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
params.id = options.application;
|
||||
return async.waterfall([
|
||||
function(callback) {
|
||||
if (options.application != null) {
|
||||
return callback(null, options.application);
|
||||
}
|
||||
return vcs.getApplicationId(process.cwd(), callback);
|
||||
}, function(applicationId, callback) {
|
||||
params.id = applicationId;
|
||||
if (params.device != null) {
|
||||
return callback(null, params.device);
|
||||
}
|
||||
return visuals.patterns.selectDrive(callback);
|
||||
}, function(device, callback) {
|
||||
var message;
|
||||
params.device = device;
|
||||
message = "This will completely erase " + params.device + ". Are you sure you want to continue?";
|
||||
return visuals.patterns.confirm(options.yes, message, callback);
|
||||
}, function(confirmed, callback) {
|
||||
if (!confirmed) {
|
||||
return done();
|
||||
}
|
||||
options.yes = confirmed;
|
||||
return osAction.download.action(params, options, callback);
|
||||
}, function(outputFile, callback) {
|
||||
params.image = outputFile;
|
||||
return osAction.install.action(params, options, callback);
|
||||
}
|
||||
], done);
|
||||
}
|
||||
};
|
||||
|
||||
}).call(this);
|
@ -1,33 +0,0 @@
|
||||
(function() {
|
||||
var _, async, drivelist, visuals;
|
||||
|
||||
_ = require('lodash');
|
||||
|
||||
async = require('async');
|
||||
|
||||
visuals = require('resin-cli-visuals');
|
||||
|
||||
drivelist = require('drivelist');
|
||||
|
||||
exports.list = {
|
||||
signature: 'drives',
|
||||
description: 'list available drives',
|
||||
help: 'Use this command to list all drives that are connected to your machine.\n\nExamples:\n\n $ resin drives',
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
return drivelist.list(function(error, drives) {
|
||||
if (error != null) {
|
||||
return done(error);
|
||||
}
|
||||
return async.reject(drives, drivelist.isSystem, function(removableDrives) {
|
||||
if (_.isEmpty(removableDrives)) {
|
||||
return done(new Error('No removable devices available'));
|
||||
}
|
||||
console.log(visuals.widgets.table.horizontal(removableDrives, ['device', 'description', 'size']));
|
||||
return done();
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
}).call(this);
|
@ -1,81 +0,0 @@
|
||||
(function() {
|
||||
var _, commandOptions, resin, visuals;
|
||||
|
||||
_ = require('lodash-contrib');
|
||||
|
||||
resin = require('resin-sdk');
|
||||
|
||||
visuals = require('resin-cli-visuals');
|
||||
|
||||
commandOptions = require('./command-options');
|
||||
|
||||
exports.list = {
|
||||
signature: 'envs',
|
||||
description: 'list all environment variables',
|
||||
help: 'Use this command to list all environment variables for a particular application.\nNotice we will support per-device environment variables soon.\n\nThis command lists all custom environment variables set on the devices running\nthe application. If you want to see all environment variables, including private\nones used by resin, use the verbose option.\n\nExample:\n\n $ resin envs --application 91\n $ resin envs --application 91 --verbose',
|
||||
options: [
|
||||
commandOptions.application, {
|
||||
signature: 'verbose',
|
||||
description: 'show private environment variables',
|
||||
boolean: true,
|
||||
alias: 'v'
|
||||
}
|
||||
],
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
return resin.models.environmentVariables.getAllByApplication(options.application, function(error, environmentVariables) {
|
||||
if (error != null) {
|
||||
return done(error);
|
||||
}
|
||||
if (!options.verbose) {
|
||||
environmentVariables = _.reject(environmentVariables, resin.models.environmentVariables.isSystemVariable);
|
||||
}
|
||||
console.log(visuals.widgets.table.horizontal(environmentVariables, ['id', 'name', 'value']));
|
||||
return 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\nExamples:\n\n $ resin env rm 215\n $ resin env rm 215 --yes',
|
||||
options: [commandOptions.yes],
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
return visuals.patterns.remove('environment variable', options.yes, function(callback) {
|
||||
return resin.models.environmentVariables.remove(params.id, callback);
|
||||
}, 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\nYou need to pass the `--application` option.\n\nIf value is omitted, the tool will attempt to use the variable\'s value\nas defined in your host machine.\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 -a 91\n $ resin env add TERM -a 91',
|
||||
options: [commandOptions.application],
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
if (params.value == null) {
|
||||
params.value = process.env[params.key];
|
||||
if (params.value == null) {
|
||||
return done(new Error("Environment value not found for key: " + params.key));
|
||||
} else {
|
||||
console.info("Warning: using " + params.key + "=" + params.value + " from host environment");
|
||||
}
|
||||
}
|
||||
return resin.models.environmentVariables.create(options.application, params.key, params.value, 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\nExamples:\n\n $ resin env rename 376 emacs',
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
return resin.models.environmentVariables.update(params.id, params.value, done);
|
||||
}
|
||||
};
|
||||
|
||||
}).call(this);
|
@ -1,78 +0,0 @@
|
||||
(function() {
|
||||
var _, async, examplesData, fs, path, resin, vcs, visuals;
|
||||
|
||||
async = require('async');
|
||||
|
||||
fs = require('fs');
|
||||
|
||||
path = require('path');
|
||||
|
||||
_ = require('lodash');
|
||||
|
||||
resin = require('resin-sdk');
|
||||
|
||||
visuals = require('resin-cli-visuals');
|
||||
|
||||
vcs = require('resin-vcs');
|
||||
|
||||
examplesData = require('../data/examples.json');
|
||||
|
||||
exports.list = {
|
||||
signature: 'examples',
|
||||
description: 'list all example applications',
|
||||
help: 'Use this command to list available example applications from resin.io\n\nExample:\n\n $ resin examples',
|
||||
permission: 'user',
|
||||
action: function() {
|
||||
examplesData = _.map(examplesData, function(example, index) {
|
||||
example.id = index + 1;
|
||||
return example;
|
||||
});
|
||||
examplesData = _.map(examplesData, function(example) {
|
||||
if (example.author == null) {
|
||||
example.author = 'Unknown';
|
||||
}
|
||||
return example;
|
||||
});
|
||||
return console.log(visuals.widgets.table.horizontal(examplesData, ['id', 'display_name', 'repository', 'author']));
|
||||
}
|
||||
};
|
||||
|
||||
exports.info = {
|
||||
signature: 'example <id>',
|
||||
description: 'list a single example application',
|
||||
help: 'Use this command to show information of a single example application\n\nExample:\n\n $ resin example 3',
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
var example, id;
|
||||
id = params.id - 1;
|
||||
example = examplesData[id];
|
||||
if (example == null) {
|
||||
return done(new Error("Unknown example: " + id));
|
||||
}
|
||||
example.id = id;
|
||||
if (example.author == null) {
|
||||
example.author = 'Unknown';
|
||||
}
|
||||
console.log(visuals.widgets.table.vertical(example, ['id', 'display_name', 'description', 'author', 'repository']));
|
||||
return done();
|
||||
}
|
||||
};
|
||||
|
||||
exports.clone = {
|
||||
signature: 'example clone <id>',
|
||||
description: 'clone an example application',
|
||||
help: 'Use this command to clone an example application to the current directory\n\nThis command outputs information about the cloning process.\nUse `--quiet` to remove that output.\n\nExample:\n\n $ resin example clone 3',
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
var currentDirectory, example;
|
||||
example = examplesData[params.id - 1];
|
||||
if (example == null) {
|
||||
return done(new Error("Unknown example: " + id));
|
||||
}
|
||||
currentDirectory = process.cwd();
|
||||
console.info("Cloning " + example.display_name + " to " + currentDirectory);
|
||||
return vcs.clone(example.repository, currentDirectory, done);
|
||||
}
|
||||
};
|
||||
|
||||
}).call(this);
|
@ -1,162 +0,0 @@
|
||||
(function() {
|
||||
var PADDING_INITIAL, PADDING_MIDDLE, _, addAlias, addOptionPrefix, buildHelpString, buildOptionSignatureHelp, capitano, command, general, getCommandHelp, getFieldMaxLength, getOptionHelp, getOptionsParsedSignatures, resin;
|
||||
|
||||
_ = require('lodash');
|
||||
|
||||
_.str = require('underscore.string');
|
||||
|
||||
resin = require('resin-sdk');
|
||||
|
||||
capitano = require('capitano');
|
||||
|
||||
PADDING_INITIAL = ' ';
|
||||
|
||||
PADDING_MIDDLE = '\t';
|
||||
|
||||
getFieldMaxLength = function(array, field) {
|
||||
return _.max(_.map(array, function(item) {
|
||||
return item[field].toString().length;
|
||||
}));
|
||||
};
|
||||
|
||||
buildHelpString = function(firstColumn, secondColumn) {
|
||||
var result;
|
||||
result = "" + PADDING_INITIAL + firstColumn;
|
||||
result += "" + PADDING_MIDDLE + secondColumn;
|
||||
return result;
|
||||
};
|
||||
|
||||
addOptionPrefix = function(option) {
|
||||
if (option.length <= 0) {
|
||||
return;
|
||||
}
|
||||
if (option.length === 1) {
|
||||
return "-" + option;
|
||||
} else {
|
||||
return "--" + option;
|
||||
}
|
||||
};
|
||||
|
||||
addAlias = function(alias) {
|
||||
return ", " + (addOptionPrefix(alias));
|
||||
};
|
||||
|
||||
buildOptionSignatureHelp = function(option) {
|
||||
var alias, i, len, ref, result;
|
||||
result = addOptionPrefix(option.signature.toString());
|
||||
if (_.isString(option.alias)) {
|
||||
result += addAlias(option.alias);
|
||||
} else if (_.isArray(option.alias)) {
|
||||
ref = option.alias;
|
||||
for (i = 0, len = ref.length; i < len; i++) {
|
||||
alias = ref[i];
|
||||
result += addAlias(alias);
|
||||
}
|
||||
}
|
||||
if (option.parameter != null) {
|
||||
result += " <" + option.parameter + ">";
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
getCommandHelp = function(command) {
|
||||
var commandSignature, maxSignatureLength;
|
||||
maxSignatureLength = getFieldMaxLength(capitano.state.commands, 'signature');
|
||||
commandSignature = _.str.rpad(command.signature.toString(), maxSignatureLength, ' ');
|
||||
return buildHelpString(commandSignature, command.description);
|
||||
};
|
||||
|
||||
getOptionsParsedSignatures = function(optionsHelp) {
|
||||
var maxLength;
|
||||
maxLength = _.max(_.map(optionsHelp, function(signature) {
|
||||
return signature.length;
|
||||
}));
|
||||
return _.map(optionsHelp, function(signature) {
|
||||
return _.str.rpad(signature, maxLength, ' ');
|
||||
});
|
||||
};
|
||||
|
||||
getOptionHelp = function(option, maxLength) {
|
||||
var result;
|
||||
result = PADDING_INITIAL;
|
||||
result += _.str.rpad(option.signature, maxLength, ' ');
|
||||
result += PADDING_MIDDLE;
|
||||
result += option.description;
|
||||
return result;
|
||||
};
|
||||
|
||||
general = function() {
|
||||
var command, i, j, len, len1, option, optionSignatureMaxLength, options, ref;
|
||||
console.log('Usage: resin [COMMAND] [OPTIONS]\n');
|
||||
console.log('Commands:\n');
|
||||
ref = capitano.state.commands;
|
||||
for (i = 0, len = ref.length; i < len; i++) {
|
||||
command = ref[i];
|
||||
if (command.isWildcard()) {
|
||||
continue;
|
||||
}
|
||||
console.log(getCommandHelp(command));
|
||||
}
|
||||
console.log('\nGlobal Options:\n');
|
||||
options = _.map(capitano.state.globalOptions, function(option) {
|
||||
option.signature = buildOptionSignatureHelp(option);
|
||||
return option;
|
||||
});
|
||||
optionSignatureMaxLength = _.max(_.map(options, function(option) {
|
||||
return option.signature.length;
|
||||
}));
|
||||
for (j = 0, len1 = options.length; j < len1; j++) {
|
||||
option = options[j];
|
||||
console.log(getOptionHelp(option, optionSignatureMaxLength));
|
||||
}
|
||||
return console.log();
|
||||
};
|
||||
|
||||
command = function(params, options, done) {
|
||||
return capitano.state.getMatchCommand(params.command, function(error, command) {
|
||||
var i, len, option, optionSignatureMaxLength;
|
||||
if (error != null) {
|
||||
return done(error);
|
||||
}
|
||||
if ((command == null) || command.isWildcard()) {
|
||||
return capitano.defaults.actions.commandNotFound(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');
|
||||
options = _.map(command.options, function(option) {
|
||||
option.signature = buildOptionSignatureHelp(option);
|
||||
return option;
|
||||
});
|
||||
optionSignatureMaxLength = _.max(_.map(options, function(option) {
|
||||
return option.signature.toString().length;
|
||||
}));
|
||||
for (i = 0, len = options.length; i < len; i++) {
|
||||
option = options[i];
|
||||
console.log(getOptionHelp(option, optionSignatureMaxLength));
|
||||
}
|
||||
console.log();
|
||||
}
|
||||
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',
|
||||
action: function(params, options, done) {
|
||||
if (params.command != null) {
|
||||
return command(params, options, done);
|
||||
} else {
|
||||
return general(params, options, done);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}).call(this);
|
@ -1,20 +0,0 @@
|
||||
(function() {
|
||||
module.exports = {
|
||||
app: require('./app'),
|
||||
info: require('./info'),
|
||||
auth: require('./auth'),
|
||||
drive: require('./drive'),
|
||||
device: require('./device'),
|
||||
env: require('./environment-variables'),
|
||||
keys: require('./keys'),
|
||||
logs: require('./logs'),
|
||||
notes: require('./notes'),
|
||||
preferences: require('./preferences'),
|
||||
os: require('./os'),
|
||||
help: require('./help'),
|
||||
examples: require('./examples'),
|
||||
plugin: require('./plugin'),
|
||||
update: require('./update')
|
||||
};
|
||||
|
||||
}).call(this);
|
@ -1,15 +0,0 @@
|
||||
(function() {
|
||||
var packageJSON;
|
||||
|
||||
packageJSON = require('../../package.json');
|
||||
|
||||
exports.version = {
|
||||
signature: 'version',
|
||||
description: 'output the version number',
|
||||
help: 'Display the Resin CLI version.',
|
||||
action: function() {
|
||||
return console.log(packageJSON.version);
|
||||
}
|
||||
};
|
||||
|
||||
}).call(this);
|
@ -1,92 +0,0 @@
|
||||
(function() {
|
||||
var SSH_KEY_WIDTH, _, async, capitano, commandOptions, fs, resin, visuals;
|
||||
|
||||
_ = require('lodash');
|
||||
|
||||
_.str = require('underscore.string');
|
||||
|
||||
async = require('async');
|
||||
|
||||
fs = require('fs');
|
||||
|
||||
resin = require('resin-sdk');
|
||||
|
||||
capitano = require('capitano');
|
||||
|
||||
visuals = require('resin-cli-visuals');
|
||||
|
||||
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) {
|
||||
return resin.models.key.getAll(function(error, keys) {
|
||||
if (error != null) {
|
||||
return done(error);
|
||||
}
|
||||
console.log(visuals.widgets.table.horizontal(keys, ['id', 'title']));
|
||||
return done();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
SSH_KEY_WIDTH = 43;
|
||||
|
||||
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) {
|
||||
return resin.models.key.get(params.id, function(error, key) {
|
||||
if (error != null) {
|
||||
return done(error);
|
||||
}
|
||||
key.public_key = '\n' + visuals.helpers.chop(key.public_key, SSH_KEY_WIDTH);
|
||||
console.log(visuals.widgets.table.vertical(key, ['id', 'title', 'public_key']));
|
||||
return 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) {
|
||||
return visuals.patterns.remove('key', options.yes, function(callback) {
|
||||
return resin.models.key.remove(params.id, callback);
|
||||
}, 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) {
|
||||
return async.waterfall([
|
||||
function(callback) {
|
||||
if (params.path != null) {
|
||||
return fs.readFile(params.path, {
|
||||
encoding: 'utf8'
|
||||
}, callback);
|
||||
} else {
|
||||
return capitano.utils.getStdin(function(data) {
|
||||
return callback(null, data);
|
||||
});
|
||||
}
|
||||
}, function(key, callback) {
|
||||
return resin.models.key.create(params.name, key, callback);
|
||||
}
|
||||
], done);
|
||||
}
|
||||
};
|
||||
|
||||
}).call(this);
|
@ -1,48 +0,0 @@
|
||||
(function() {
|
||||
var LOGS_HISTORY_COUNT, _, resin;
|
||||
|
||||
_ = require('lodash');
|
||||
|
||||
resin = require('resin-sdk');
|
||||
|
||||
LOGS_HISTORY_COUNT = 200;
|
||||
|
||||
exports.logs = {
|
||||
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 limit the output to the n last lines, use the `--num` option along with a number.\nThis is similar to doing `resin logs <uuid> | tail -n X`.\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,\nand the tool won\'t notice if you\'re using an invalid UUID.\n\nThis is due to some technical limitations that we plan to address soon.\n\nExamples:\n\n $ resin logs 23c73a12e3527df55c60b9ce647640c1b7da1b32d71e6a39849ac0f00db828\n $ resin logs 23c73a12e3527df55c60b9ce647640c1b7da1b32d71e6a39849ac0f00db828 --num 20\n $ resin logs 23c73a12e3527df55c60b9ce647640c1b7da1b32d71e6a39849ac0f00db828 --tail',
|
||||
options: [
|
||||
{
|
||||
signature: 'num',
|
||||
parameter: 'num',
|
||||
description: 'number of lines to display',
|
||||
alias: 'n'
|
||||
}, {
|
||||
signature: 'tail',
|
||||
description: 'continuously stream output',
|
||||
boolean: true,
|
||||
alias: 't'
|
||||
}
|
||||
],
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
return resin.logs.subscribe(params.uuid, {
|
||||
history: options.num || LOGS_HISTORY_COUNT,
|
||||
tail: options.tail
|
||||
}, function(error, message) {
|
||||
if (error != null) {
|
||||
return done(error);
|
||||
}
|
||||
if (_.isArray(message)) {
|
||||
_.each(message, function(line) {
|
||||
return console.log(line);
|
||||
});
|
||||
} else {
|
||||
console.log(message);
|
||||
}
|
||||
return done();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
}).call(this);
|
@ -1,27 +0,0 @@
|
||||
(function() {
|
||||
var async, resin;
|
||||
|
||||
async = require('async');
|
||||
|
||||
resin = require('resin-sdk');
|
||||
|
||||
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 <name>.\n\nExamples:\n\n $ resin note "My useful note" --device MyDevice\n $ cat note.txt | resin note --device MyDevice',
|
||||
options: [
|
||||
{
|
||||
signature: 'device',
|
||||
parameter: 'device',
|
||||
description: 'device name',
|
||||
alias: ['d', 'dev'],
|
||||
required: 'You have to specify a device'
|
||||
}
|
||||
],
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
return resin.models.device.note(options.device, params.note, done);
|
||||
}
|
||||
};
|
||||
|
||||
}).call(this);
|
@ -1,152 +0,0 @@
|
||||
(function() {
|
||||
var _, async, commandOptions, elevate, mkdirp, npm, os, packageJSON, path, resin, updateActions, visuals;
|
||||
|
||||
_ = require('lodash-contrib');
|
||||
|
||||
os = require('os');
|
||||
|
||||
async = require('async');
|
||||
|
||||
path = require('path');
|
||||
|
||||
mkdirp = require('mkdirp');
|
||||
|
||||
resin = require('resin-sdk');
|
||||
|
||||
visuals = require('resin-cli-visuals');
|
||||
|
||||
commandOptions = require('./command-options');
|
||||
|
||||
npm = require('../npm');
|
||||
|
||||
packageJSON = require('../../package.json');
|
||||
|
||||
updateActions = require('./update');
|
||||
|
||||
elevate = require('../elevate');
|
||||
|
||||
exports.download = {
|
||||
signature: 'os download <id>',
|
||||
description: 'download device OS',
|
||||
help: 'Use this command to download the device OS configured to a specific network.\n\nEthernet:\n You can setup the device OS to use ethernet by setting the `--network` option to "ethernet".\n\nWifi:\n You can setup the device OS to use wifi by setting the `--network` option to "wifi".\n If you set "network" to "wifi", you will need to specify the `--ssid` and `--key` option as well.\n\nAlternatively, you can omit all kind of network configuration options to configure interactively.\n\nYou have to specify an output location with the `--output` option.\n\nExamples:\n\n $ resin os download 91 --output ~/MyResinOS.zip\n $ resin os download 91 --network ethernet --output ~/MyResinOS.zip\n $ resin os download 91 --network wifi --ssid MyNetwork --key secreykey123 --output ~/MyResinOS.zip\n $ resin os download 91 --network ethernet --output ~/MyResinOS.zip',
|
||||
options: [
|
||||
commandOptions.network, commandOptions.wifiSsid, commandOptions.wifiKey, {
|
||||
signature: 'output',
|
||||
parameter: 'output',
|
||||
description: 'output file',
|
||||
alias: 'o',
|
||||
required: 'You need to specify an output file'
|
||||
}
|
||||
],
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
var osParams;
|
||||
osParams = {
|
||||
network: options.network,
|
||||
wifiSsid: options.ssid,
|
||||
wifiKey: options.key,
|
||||
appId: params.id
|
||||
};
|
||||
return async.waterfall([
|
||||
function(callback) {
|
||||
if (osParams.network != null) {
|
||||
return callback();
|
||||
}
|
||||
return visuals.patterns.selectNetworkParameters(function(error, parameters) {
|
||||
if (error != null) {
|
||||
return callback(error);
|
||||
}
|
||||
_.extend(osParams, parameters);
|
||||
return callback();
|
||||
});
|
||||
}, function(callback) {
|
||||
return mkdirp(path.dirname(options.output), _.unary(callback));
|
||||
}, function(callback) {
|
||||
var bar, spinner;
|
||||
console.info("Destination file: " + options.output + "\n");
|
||||
bar = new visuals.widgets.Progress('Downloading Device OS');
|
||||
spinner = new visuals.widgets.Spinner('Downloading Device OS (size unknown)');
|
||||
return resin.models.os.download(osParams, options.output, function(error) {
|
||||
spinner.stop();
|
||||
if (error != null) {
|
||||
return callback(error);
|
||||
}
|
||||
}, function(state) {
|
||||
if (state != null) {
|
||||
return bar.update(state);
|
||||
} else {
|
||||
return spinner.start();
|
||||
}
|
||||
});
|
||||
}
|
||||
], function(error) {
|
||||
if (error != null) {
|
||||
return done(error);
|
||||
}
|
||||
console.info("\nFinished downloading " + options.output);
|
||||
return done(null, options.output);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.install = {
|
||||
signature: 'os install <image> [device]',
|
||||
description: 'write an operating system image to a device',
|
||||
help: 'Use this command to write an operating system image to a device.\n\nNote that this command requires admin privileges.\n\nIf `device` is omitted, you will be prompted to select a device interactively.\n\nNotice this command asks for confirmation interactively.\nYou can avoid this by passing the `--yes` boolean option.\n\nYou can quiet the progress bar by passing the `--quiet` boolean option.\n\nYou may have to unmount the device before attempting this operation.\n\nSee the `drives` command to get a list of all connected devices to your machine and their respective ids.\n\nIn Mac OS X:\n\n $ sudo diskutil unmountDisk /dev/xxx\n\nIn GNU/Linux:\n\n $ sudo umount /dev/xxx\n\nExamples:\n\n $ resin os install rpi.iso /dev/disk2',
|
||||
options: [commandOptions.yes],
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
var bundle;
|
||||
bundle = require('../devices/raspberry-pi');
|
||||
return async.waterfall([
|
||||
function(callback) {
|
||||
return npm.isUpdated(packageJSON.name, packageJSON.version, callback);
|
||||
}, function(isUpdated, callback) {
|
||||
if (isUpdated) {
|
||||
return callback();
|
||||
}
|
||||
console.info('Resin CLI is outdated.\n\nIn order to avoid device compatibility issues, this command\nrequires that you have the Resin CLI updated.\n\nUpdating now...');
|
||||
return updateActions.update.action(params, options, _.unary(callback));
|
||||
}, function(callback) {
|
||||
if (params.device != null) {
|
||||
return callback(null, params.device);
|
||||
}
|
||||
return visuals.patterns.selectDrive(function(error, device) {
|
||||
if (error != null) {
|
||||
return callback(error);
|
||||
}
|
||||
if (device == null) {
|
||||
return callback(new Error('No removable devices available'));
|
||||
}
|
||||
return callback(null, device);
|
||||
});
|
||||
}, function(device, callback) {
|
||||
var message;
|
||||
params.device = device;
|
||||
message = "This will completely erase " + params.device + ". Are you sure you want to continue?";
|
||||
return visuals.patterns.confirm(options.yes, message, callback);
|
||||
}, function(confirmed, callback) {
|
||||
var bar;
|
||||
if (!confirmed) {
|
||||
return done();
|
||||
}
|
||||
bar = new visuals.widgets.Progress('Writing Device OS');
|
||||
params.progress = _.bind(bar.update, bar);
|
||||
return bundle.write(params, callback);
|
||||
}
|
||||
], function(error) {
|
||||
var resinWritePath;
|
||||
if (error == null) {
|
||||
return done();
|
||||
}
|
||||
if (elevate.shouldElevate(error) && !options.fromScript) {
|
||||
resinWritePath = "\"" + (path.join(__dirname, '..', '..', 'bin', 'resin-write')) + "\"";
|
||||
return elevate.run("\"" + process.argv[0] + "\" " + resinWritePath + " \"" + params.image + "\" \"" + params.device + "\"");
|
||||
} else {
|
||||
return done(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
}).call(this);
|
@ -1,83 +0,0 @@
|
||||
(function() {
|
||||
var _, commandOptions, plugins, visuals;
|
||||
|
||||
_ = require('lodash');
|
||||
|
||||
visuals = require('resin-cli-visuals');
|
||||
|
||||
commandOptions = require('./command-options');
|
||||
|
||||
plugins = require('../plugins');
|
||||
|
||||
exports.list = {
|
||||
signature: 'plugins',
|
||||
description: 'list all plugins',
|
||||
help: 'Use this command to list all the installed resin plugins.\n\nExamples:\n\n $ resin plugins',
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
return plugins.list(function(error, resinPlugins) {
|
||||
if (error != null) {
|
||||
return done(error);
|
||||
}
|
||||
if (_.isEmpty(resinPlugins)) {
|
||||
console.log('You don\'t have any plugins yet');
|
||||
return done();
|
||||
}
|
||||
console.log(visuals.widgets.table.horizontal(resinPlugins, ['name', 'version', 'description', 'license']));
|
||||
return done();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.install = {
|
||||
signature: 'plugin install <name>',
|
||||
description: 'install a plugin',
|
||||
help: 'Use this command to install a resin plugin\n\nUse `--quiet` to prevent information logging.\n\nExamples:\n\n $ resin plugin install hello',
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
return plugins.install(params.name, function(error) {
|
||||
if (error != null) {
|
||||
return done(error);
|
||||
}
|
||||
console.info("Plugin installed: " + params.name);
|
||||
return done();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.update = {
|
||||
signature: 'plugin update <name>',
|
||||
description: 'update a plugin',
|
||||
help: 'Use this command to update a resin plugin\n\nUse `--quiet` to prevent information logging.\n\nExamples:\n\n $ resin plugin update hello',
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
return plugins.update(params.name, function(error, version) {
|
||||
if (error != null) {
|
||||
return done(error);
|
||||
}
|
||||
console.info("Plugin updated: " + params.name + "@" + version);
|
||||
return done();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.remove = {
|
||||
signature: 'plugin rm <name>',
|
||||
description: 'remove a plugin',
|
||||
help: 'Use this command to remove a resin.io plugin.\n\nNotice this command asks for confirmation interactively.\nYou can avoid this by passing the `--yes` boolean option.\n\nExamples:\n\n $ resin plugin rm hello\n $ resin plugin rm hello --yes',
|
||||
options: [commandOptions.yes],
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
return visuals.patterns.remove('plugin', options.yes, function(callback) {
|
||||
return plugins.remove(params.name, callback);
|
||||
}, function(error) {
|
||||
if (error != null) {
|
||||
return done(error);
|
||||
}
|
||||
console.info("Plugin removed: " + params.name);
|
||||
return done();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
}).call(this);
|
@ -1,22 +0,0 @@
|
||||
(function() {
|
||||
var open, settings, url;
|
||||
|
||||
open = require('open');
|
||||
|
||||
url = require('url');
|
||||
|
||||
settings = require('resin-settings-client');
|
||||
|
||||
exports.preferences = {
|
||||
signature: 'preferences',
|
||||
description: 'open preferences form',
|
||||
help: 'Use this command to open the preferences form.\n\nIn the future, we will allow changing all preferences directly from the terminal.\nFor now, we open your default web browser and point it to the web based preferences form.\n\nExamples:\n\n $ resin preferences',
|
||||
permission: 'user',
|
||||
action: function() {
|
||||
var absUrl;
|
||||
absUrl = url.resolve(settings.get('remoteUrl'), '/preferences');
|
||||
return open(absUrl);
|
||||
}
|
||||
};
|
||||
|
||||
}).call(this);
|
@ -1,51 +0,0 @@
|
||||
(function() {
|
||||
var _, child_process, npm, packageJSON, president;
|
||||
|
||||
_ = require('lodash');
|
||||
|
||||
child_process = require('child_process');
|
||||
|
||||
president = require('president');
|
||||
|
||||
npm = require('../npm');
|
||||
|
||||
packageJSON = require('../../package.json');
|
||||
|
||||
exports.update = {
|
||||
signature: 'update',
|
||||
description: 'update the resin cli',
|
||||
help: 'Use this command to update the Resin CLI\n\nThis command outputs information about the update process.\nUse `--quiet` to remove that output.\n\nThe Resin CLI checks for updates once per day.\n\nMajor updates require a manual update with this update command,\nwhile minor updates are applied automatically.\n\nExamples:\n\n $ resin update',
|
||||
action: function(params, options, done) {
|
||||
return npm.isUpdated(packageJSON.name, packageJSON.version, function(error, isUpdated) {
|
||||
var command, onUpdate;
|
||||
if (error != null) {
|
||||
return done(error);
|
||||
}
|
||||
if (isUpdated) {
|
||||
return done(new Error('You\'re already running the latest version.'));
|
||||
}
|
||||
onUpdate = function(error, stdout, stderr) {
|
||||
if (error != null) {
|
||||
return done(error);
|
||||
}
|
||||
if (!_.isEmpty(stderr)) {
|
||||
return done(new Error(stderr));
|
||||
}
|
||||
console.info("Upgraded " + packageJSON.name + ".");
|
||||
return done();
|
||||
};
|
||||
command = "npm install --global " + packageJSON.name;
|
||||
return child_process.exec(command, function(error, stdout, stderr) {
|
||||
if (error != null) {
|
||||
return onUpdate(null, stdout, stderr);
|
||||
}
|
||||
if (_.any([error.code === 3, error.code === 'EPERM', error.code === 'ACCES'])) {
|
||||
return president.execute(command, onUpdate);
|
||||
}
|
||||
return done(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
}).call(this);
|
168
build/app.js
168
build/app.js
@ -1,168 +0,0 @@
|
||||
(function() {
|
||||
var _, actions, async, capitano, changeProjectDirectory, errors, plugins, resin, update;
|
||||
|
||||
_ = require('lodash');
|
||||
|
||||
async = require('async');
|
||||
|
||||
capitano = require('capitano');
|
||||
|
||||
resin = require('resin-sdk');
|
||||
|
||||
actions = require('./actions');
|
||||
|
||||
errors = require('./errors');
|
||||
|
||||
plugins = require('./plugins');
|
||||
|
||||
update = require('./update');
|
||||
|
||||
capitano.permission('user', function(done) {
|
||||
return resin.auth.isLoggedIn(function(isLoggedIn) {
|
||||
if (!isLoggedIn) {
|
||||
return done(new Error('You have to log in'));
|
||||
}
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
capitano.command({
|
||||
signature: '*',
|
||||
action: function() {
|
||||
return capitano.execute({
|
||||
command: 'help'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
capitano.globalOption({
|
||||
signature: 'quiet',
|
||||
description: 'quiet (no output)',
|
||||
boolean: true,
|
||||
alias: 'q'
|
||||
});
|
||||
|
||||
capitano.globalOption({
|
||||
signature: 'project',
|
||||
parameter: 'path',
|
||||
description: 'project path',
|
||||
alias: 'j'
|
||||
});
|
||||
|
||||
capitano.globalOption({
|
||||
signature: 'no-color',
|
||||
description: 'disable colour highlighting',
|
||||
boolean: true
|
||||
});
|
||||
|
||||
capitano.command(actions.info.version);
|
||||
|
||||
capitano.command(actions.help.help);
|
||||
|
||||
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.info);
|
||||
|
||||
capitano.command(actions.app.remove);
|
||||
|
||||
capitano.command(actions.app.restart);
|
||||
|
||||
capitano.command(actions.app.associate);
|
||||
|
||||
capitano.command(actions.app.init);
|
||||
|
||||
capitano.command(actions.device.list);
|
||||
|
||||
capitano.command(actions.device.supported);
|
||||
|
||||
capitano.command(actions.device.rename);
|
||||
|
||||
capitano.command(actions.device.init);
|
||||
|
||||
capitano.command(actions.device.info);
|
||||
|
||||
capitano.command(actions.device.remove);
|
||||
|
||||
capitano.command(actions.device.identify);
|
||||
|
||||
capitano.command(actions.drive.list);
|
||||
|
||||
capitano.command(actions.notes.set);
|
||||
|
||||
capitano.command(actions.preferences.preferences);
|
||||
|
||||
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.logs.logs);
|
||||
|
||||
capitano.command(actions.os.download);
|
||||
|
||||
capitano.command(actions.os.install);
|
||||
|
||||
capitano.command(actions.examples.list);
|
||||
|
||||
capitano.command(actions.examples.clone);
|
||||
|
||||
capitano.command(actions.examples.info);
|
||||
|
||||
capitano.command(actions.plugin.list);
|
||||
|
||||
capitano.command(actions.plugin.install);
|
||||
|
||||
capitano.command(actions.plugin.update);
|
||||
|
||||
capitano.command(actions.plugin.remove);
|
||||
|
||||
capitano.command(actions.update.update);
|
||||
|
||||
changeProjectDirectory = function(directory) {
|
||||
try {
|
||||
return process.chdir(directory);
|
||||
} catch (_error) {
|
||||
return errors.handle(new Error("Invalid project: " + directory));
|
||||
}
|
||||
};
|
||||
|
||||
async.waterfall([
|
||||
function(callback) {
|
||||
return update.check(callback);
|
||||
}, function(callback) {
|
||||
return plugins.register('resin-plugin-', callback);
|
||||
}, function(callback) {
|
||||
var cli;
|
||||
cli = capitano.parse(process.argv);
|
||||
if (cli.global.quiet) {
|
||||
console.info = _.noop;
|
||||
}
|
||||
if (cli.global.project != null) {
|
||||
changeProjectDirectory(cli.global.project);
|
||||
}
|
||||
return capitano.execute(cli, callback);
|
||||
}
|
||||
], errors.handle);
|
||||
|
||||
}).call(this);
|
@ -1,96 +0,0 @@
|
||||
[
|
||||
{
|
||||
"name": "basic-resin-node-project",
|
||||
"display_name": "Node.js Starter Project",
|
||||
"repository": "https://github.com/resin-io/basic-resin-node-project",
|
||||
"description": "This is a simple Hello, World project for node.js designed to act as a basis for future work. It demonstrates how to install native Linux packages and configure your application."
|
||||
},
|
||||
{
|
||||
"name": "cimon",
|
||||
"display_name": "Cimon",
|
||||
"repository": "https://bitbucket.org/efwe/cimon",
|
||||
"description": "A simple tool for reading temperatures from a USB-enabled thermometer. This project is used as the backend to efwe's awesome temperature visualisation at 123k.de.",
|
||||
"author": "efwe"
|
||||
},
|
||||
{
|
||||
"name": "firebaseDTL",
|
||||
"display_name": "Digital Temperature Logger",
|
||||
"repository": "https://github.com/shaunmulligan/firebaseDTL",
|
||||
"description": "A Firebase-backed Digital Temperature Logger allowing you to connect devices with multiple temperature sensors to a central cloud-based datastore.",
|
||||
"author": "Shaun Mulligan"
|
||||
},
|
||||
{
|
||||
"name": "digitiser",
|
||||
"display_name": "Digitiser",
|
||||
"repository": "https://github.com/shaunmulligan/digitiser",
|
||||
"description": "A tool for displaying integer values from a JSON endpoint on a MAX7219 7-segment display.",
|
||||
"author": "Shaun Mulligan"
|
||||
},
|
||||
{
|
||||
"name": "basic-gpio",
|
||||
"display_name": "Example Pi Pins Application",
|
||||
"repository": "https://github.com/shaunmulligan/basic-gpio",
|
||||
"description": "A simple application which demonstrates the use of the Pi Pins library to interface with GPIO.",
|
||||
"author": "Shaun Mulligan"
|
||||
},
|
||||
{
|
||||
"name": "coder",
|
||||
"display_name": "Google Coder",
|
||||
"repository": "https://github.com/resin-io/coder",
|
||||
"description": "Resin.io-enabled version of Google's excellent Coder project which makes it easy to develop web projects on your device."
|
||||
},
|
||||
{
|
||||
"name": "hoversnap",
|
||||
"display_name": "Hoversnap",
|
||||
"repository": "https://github.com/resin-io/hoversnap",
|
||||
"description": "A tool for controlling a camera using a foot switch in order to capture shots in which people appear to be flying."
|
||||
},
|
||||
{
|
||||
"name": "resin-piminer",
|
||||
"display_name": "Pi Miner",
|
||||
"repository": "https://github.com/csquared/resin-piminer",
|
||||
"description": "A bitcoin miner for the Raspberry Pi.",
|
||||
"author": "Chris Continanza"
|
||||
},
|
||||
{
|
||||
"name": "resin-cctv",
|
||||
"display_name": "Resin CCTV",
|
||||
"repository": "https://github.com/abresas/resin-cctv",
|
||||
"description": "A project which allows you to use your devices as a CCTV camera system which hooks into Dropbox.",
|
||||
"author": "Aleksis Brezas"
|
||||
},
|
||||
{
|
||||
"name": "resin_player",
|
||||
"display_name": "Resin Player",
|
||||
"repository": "https://bitbucket.org/lifeeth/resin_player/",
|
||||
"description": "A project which allows you to play squeezebox media through your devices.",
|
||||
"author": "Praneeth Bodduluri"
|
||||
},
|
||||
{
|
||||
"name": "salesforceTemp",
|
||||
"display_name": "Salesforce Temperature Probe",
|
||||
"repository": "https://github.com/shaunmulligan/salesforceTemp",
|
||||
"description": "Example application for interfacing with a temperature probe using Salesforce.com.",
|
||||
"author": "Shaun Mulligan"
|
||||
},
|
||||
{
|
||||
"name": "sms2speech",
|
||||
"display_name": "SMS to Speech",
|
||||
"repository": "https://github.com/alexandrosm/sms2speech",
|
||||
"description": "A simple tool which uses Twillio to read out incoming SMS messages.",
|
||||
"author": "Alexandros Marinos"
|
||||
},
|
||||
{
|
||||
"name": "resin-kiosk",
|
||||
"display_name": "Simple Digitiser Kiosk",
|
||||
"repository": "https://bitbucket.org/lifeeth/resin-kiosk",
|
||||
"description": "Displays values from a JSON endpoint on your browser in kiosk mode",
|
||||
"author": "Praneeth Bodduluri"
|
||||
},
|
||||
{
|
||||
"name": "text2speech",
|
||||
"display_name": "Text to Speech Converter",
|
||||
"repository": "https://github.com/resin-io/text2speech",
|
||||
"description": "A simple application that makes your device speak out loud."
|
||||
}
|
||||
]
|
@ -1,30 +0,0 @@
|
||||
(function() {
|
||||
var diskio, fs, progressStream;
|
||||
|
||||
fs = require('fs');
|
||||
|
||||
progressStream = require('progress-stream');
|
||||
|
||||
diskio = require('diskio');
|
||||
|
||||
exports.name = 'Raspberry Pi';
|
||||
|
||||
exports.write = function(options, callback) {
|
||||
var error, imageFileSize, imageFileStream, progress;
|
||||
imageFileSize = fs.statSync(options.image).size;
|
||||
if (imageFileSize === 0) {
|
||||
error = new Error("Invalid OS image: " + options.image + ". The image is 0 bytes.");
|
||||
return callback(error);
|
||||
}
|
||||
progress = progressStream({
|
||||
length: imageFileSize,
|
||||
time: 500
|
||||
});
|
||||
if (!options.quiet) {
|
||||
progress.on('progress', options.progress);
|
||||
}
|
||||
imageFileStream = fs.createReadStream(options.image).pipe(progress);
|
||||
return diskio.writeStream(options.device, imageFileStream, callback);
|
||||
};
|
||||
|
||||
}).call(this);
|
@ -1,25 +0,0 @@
|
||||
(function() {
|
||||
var _, isWindows, os, path;
|
||||
|
||||
_ = require('lodash');
|
||||
|
||||
os = require('os');
|
||||
|
||||
path = require('path');
|
||||
|
||||
isWindows = function() {
|
||||
return os.platform() === 'win32';
|
||||
};
|
||||
|
||||
exports.shouldElevate = function(error) {
|
||||
return _.all([isWindows(), error.code === 'EPERM' || error.code === 'EACCES']);
|
||||
};
|
||||
|
||||
exports.run = function(command) {
|
||||
if (!isWindows()) {
|
||||
return;
|
||||
}
|
||||
return require('windosu').exec(command);
|
||||
};
|
||||
|
||||
}).call(this);
|
@ -1,47 +0,0 @@
|
||||
(function() {
|
||||
var _, os;
|
||||
|
||||
_ = require('lodash');
|
||||
|
||||
os = require('os');
|
||||
|
||||
exports.handle = function(error, exit) {
|
||||
var errorCode, message;
|
||||
if (exit == null) {
|
||||
exit = true;
|
||||
}
|
||||
if ((error == null) || !(error instanceof Error)) {
|
||||
return;
|
||||
}
|
||||
if (process.env.DEBUG) {
|
||||
console.error(error.stack);
|
||||
} else {
|
||||
if (error.code === 'EISDIR') {
|
||||
console.error("File is a directory: " + error.path);
|
||||
} else if (error.code === 'ENOENT') {
|
||||
console.error("No such file or directory: " + error.path);
|
||||
} else if (error.code === 'EACCES' || error.code === 'EPERM') {
|
||||
message = 'You don\'t have enough privileges to run this operation.\n';
|
||||
if (os.platform() === 'win32') {
|
||||
message += 'Run a new Command Prompt as administrator and try running this command again.';
|
||||
} else {
|
||||
message += 'Try running this command again prefixing it with `sudo`.';
|
||||
}
|
||||
console.error(message);
|
||||
} else if (error.code === 'ENOGIT') {
|
||||
console.error('Git is not installed on this system.\nHead over to http://git-scm.com to install it and run this command again.');
|
||||
} else if (error.message != null) {
|
||||
console.error(error.message);
|
||||
}
|
||||
}
|
||||
if (_.isNumber(error.exitCode)) {
|
||||
errorCode = error.exitCode;
|
||||
} else {
|
||||
errorCode = 1;
|
||||
}
|
||||
if (exit) {
|
||||
return process.exit(errorCode);
|
||||
}
|
||||
};
|
||||
|
||||
}).call(this);
|
38
build/npm.js
38
build/npm.js
@ -1,38 +0,0 @@
|
||||
(function() {
|
||||
var _, async, npm;
|
||||
|
||||
npm = require('npm');
|
||||
|
||||
async = require('async');
|
||||
|
||||
_ = require('lodash-contrib');
|
||||
|
||||
exports.getLatestVersion = function(name, callback) {
|
||||
return async.waterfall([
|
||||
function(callback) {
|
||||
var options;
|
||||
options = {
|
||||
loglevel: 'silent',
|
||||
global: true
|
||||
};
|
||||
return npm.load(options, _.unary(callback));
|
||||
}, function(callback) {
|
||||
return npm.commands.view([name], true, function(error, data) {
|
||||
var versions;
|
||||
versions = _.keys(data);
|
||||
return callback(error, _.first(versions));
|
||||
});
|
||||
}
|
||||
], callback);
|
||||
};
|
||||
|
||||
exports.isUpdated = function(name, currentVersion, callback) {
|
||||
return exports.getLatestVersion(name, function(error, latestVersion) {
|
||||
if (error != null) {
|
||||
return callback(error);
|
||||
}
|
||||
return callback(null, currentVersion === latestVersion);
|
||||
});
|
||||
};
|
||||
|
||||
}).call(this);
|
@ -1,55 +0,0 @@
|
||||
(function() {
|
||||
var Nplugm, _, capitano, nplugm, registerPlugin;
|
||||
|
||||
Nplugm = require('nplugm');
|
||||
|
||||
_ = require('lodash');
|
||||
|
||||
capitano = require('capitano');
|
||||
|
||||
nplugm = null;
|
||||
|
||||
registerPlugin = function(plugin) {
|
||||
if (!_.isArray(plugin)) {
|
||||
return capitano.command(plugin);
|
||||
}
|
||||
return _.each(plugin, capitano.command);
|
||||
};
|
||||
|
||||
exports.register = function(prefix, callback) {
|
||||
nplugm = new Nplugm(prefix);
|
||||
return nplugm.list(function(error, plugins) {
|
||||
var i, len, plugin;
|
||||
if (error != null) {
|
||||
return callback(error);
|
||||
}
|
||||
for (i = 0, len = plugins.length; i < len; i++) {
|
||||
plugin = plugins[i];
|
||||
try {
|
||||
registerPlugin(nplugm.require(plugin));
|
||||
} catch (_error) {
|
||||
error = _error;
|
||||
console.error(error.message);
|
||||
}
|
||||
}
|
||||
return callback();
|
||||
});
|
||||
};
|
||||
|
||||
exports.list = function() {
|
||||
return nplugm.list.apply(nplugm, arguments);
|
||||
};
|
||||
|
||||
exports.install = function() {
|
||||
return nplugm.install.apply(nplugm, arguments);
|
||||
};
|
||||
|
||||
exports.update = function() {
|
||||
return nplugm.update.apply(nplugm, arguments);
|
||||
};
|
||||
|
||||
exports.remove = function() {
|
||||
return nplugm.remove.apply(nplugm, arguments);
|
||||
};
|
||||
|
||||
}).call(this);
|
@ -1,37 +0,0 @@
|
||||
(function() {
|
||||
var packageJSON, updateAction, updateNotifier;
|
||||
|
||||
updateNotifier = require('update-notifier');
|
||||
|
||||
packageJSON = require('../package.json');
|
||||
|
||||
updateAction = require('./actions/update');
|
||||
|
||||
exports.perform = function(callback) {
|
||||
return updateAction.update.action(null, null, callback);
|
||||
};
|
||||
|
||||
exports.notify = function(update) {
|
||||
if (!process.stdout.isTTY) {
|
||||
return;
|
||||
}
|
||||
return console.log("> Major update available: " + update.current + " -> " + update.latest + "\n> Run resin update to update.\n> Beware that a major release might introduce breaking changes.\n");
|
||||
};
|
||||
|
||||
exports.check = function(callback) {
|
||||
var notifier;
|
||||
notifier = updateNotifier({
|
||||
pkg: packageJSON
|
||||
});
|
||||
if (notifier.update == null) {
|
||||
return callback();
|
||||
}
|
||||
if (notifier.update.type === 'major') {
|
||||
exports.notify(notifier.update);
|
||||
return callback();
|
||||
}
|
||||
console.log("Performing " + notifier.update.type + " update, hold tight...");
|
||||
return exports.perform(callback);
|
||||
};
|
||||
|
||||
}).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": "Drive",
|
||||
"files": [
|
||||
"lib/actions/drive.coffee"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Environment Variables",
|
||||
"files": [
|
||||
"lib/actions/environment-variables.coffee"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Examples",
|
||||
"files": [
|
||||
"lib/actions/examples.coffee"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Help",
|
||||
"files": [
|
||||
"lib/actions/help.coffee"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Information",
|
||||
"files": [
|
||||
"lib/actions/info.coffee"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Keys",
|
||||
"files": [
|
||||
"lib/actions/keys.coffee"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Logs",
|
||||
"files": [
|
||||
"lib/actions/logs.coffee"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Notes",
|
||||
"files": [
|
||||
"lib/actions/notes.coffee"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "OS",
|
||||
"files": [
|
||||
"lib/actions/os.coffee"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Plugin",
|
||||
"files": [
|
||||
"lib/actions/plugin.coffee"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Preferences",
|
||||
"files": [
|
||||
"lib/actions/preferences.coffee"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Update",
|
||||
"files": [
|
||||
"lib/actions/update.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' ]
|
||||
},
|
||||
]
|
||||
};
|
@ -1,49 +0,0 @@
|
||||
_resin() {
|
||||
COMPREPLY=()
|
||||
|
||||
local current="${COMP_WORDS[COMP_CWORD]}"
|
||||
local previous="${COMP_WORDS[COMP_CWORD-1]}"
|
||||
local options="version help login logout signup drive whoami app apps init devices device note preferences keys key envs env logs os examples example"
|
||||
|
||||
case "${previous}" in
|
||||
app)
|
||||
local subcommands="create rm restart"
|
||||
COMPREPLY=( $(compgen -W "${subcommands}" -- ${current}) )
|
||||
return 0 ;;
|
||||
drive)
|
||||
local subcommands="list"
|
||||
COMPREPLY=( $(compgen -W "${subcommands}" -- ${current}) )
|
||||
return 0 ;;
|
||||
devices)
|
||||
local subcommands="supported"
|
||||
COMPREPLY=( $(compgen -W "${subcommands}" -- ${current}) )
|
||||
return 0 ;;
|
||||
device)
|
||||
local subcommands="rename rm identify init"
|
||||
COMPREPLY=( $(compgen -W "${subcommands}" -- ${current}) )
|
||||
return 0 ;;
|
||||
key)
|
||||
local subcommands="add rm"
|
||||
COMPREPLY=( $(compgen -W "${subcommands}" -- ${current}) )
|
||||
return 0 ;;
|
||||
env)
|
||||
local subcommands="add rename rm"
|
||||
COMPREPLY=( $(compgen -W "${subcommands}" -- ${current}) )
|
||||
return 0 ;;
|
||||
os)
|
||||
local subcommands="download install"
|
||||
COMPREPLY=( $(compgen -W "${subcommands}" -- ${current}) )
|
||||
return 0 ;;
|
||||
example)
|
||||
local subcommands="clone"
|
||||
COMPREPLY=( $(compgen -W "${subcommands}" -- ${current}) )
|
||||
return 0 ;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
COMPREPLY=( $(compgen -W "${options}" -- ${current}) )
|
||||
return 0
|
||||
}
|
||||
|
||||
complete -F _resin resin
|
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.
|
1543
doc/cli.markdown
1543
doc/cli.markdown
File diff suppressed because it is too large
Load Diff
@ -1,45 +0,0 @@
|
||||
_ = require('lodash')
|
||||
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(file)
|
||||
|
||||
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
|
||||
|
||||
# TODO: Make anchor prefix a configurable setting
|
||||
# in capitanodoc.json
|
||||
anchor: '#/pages/using/cli.md#' + 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,59 +1,40 @@
|
||||
mkdirp = require('mkdirp')
|
||||
path = require('path')
|
||||
gulp = require('gulp')
|
||||
mocha = require('gulp-mocha')
|
||||
coffee = require('gulp-coffee')
|
||||
markedMan = require('gulp-marked-man')
|
||||
coffeelint = require('gulp-coffeelint')
|
||||
inlinesource = require('gulp-inline-source')
|
||||
mocha = require('gulp-mocha')
|
||||
shell = require('gulp-shell')
|
||||
mochaNotifierReporter = require('mocha-notifier-reporter')
|
||||
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'
|
||||
json: [ 'lib/**/*.json' ]
|
||||
man: 'man/**/*.md'
|
||||
pages: 'lib/auth/pages/*.ejs'
|
||||
directories:
|
||||
man: 'man/'
|
||||
build: 'build/'
|
||||
|
||||
gulp.task 'man', ->
|
||||
gulp.src(OPTIONS.files.man)
|
||||
.pipe(markedMan())
|
||||
.pipe(gulp.dest(OPTIONS.directories.man))
|
||||
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(bare: true, header: true))
|
||||
.pipe(gulp.dest(OPTIONS.directories.build))
|
||||
|
||||
gulp.task 'test', ->
|
||||
gulp.src(OPTIONS.files.tests, read: false)
|
||||
.pipe(mocha({
|
||||
reporter: mochaNotifierReporter.decorate('landing')
|
||||
reporter: 'spec'
|
||||
}))
|
||||
|
||||
gulp.task 'coffee', [ 'test', 'lint', 'json' ], ->
|
||||
gulp.src(OPTIONS.files.app)
|
||||
.pipe(coffee())
|
||||
.pipe(gulp.dest(OPTIONS.directories.build))
|
||||
|
||||
gulp.task 'json', ->
|
||||
gulp.src(OPTIONS.files.json)
|
||||
.pipe(gulp.dest(OPTIONS.directories.build))
|
||||
|
||||
gulp.task 'lint', ->
|
||||
gulp.src(OPTIONS.files.coffee)
|
||||
.pipe(coffeelint({
|
||||
optFile: OPTIONS.config.coffeelint
|
||||
}))
|
||||
.pipe(coffeelint.reporter())
|
||||
|
||||
gulp.task 'build', [
|
||||
'coffee'
|
||||
'man'
|
||||
'coffee',
|
||||
'pages'
|
||||
]
|
||||
|
||||
gulp.task 'watch', [ 'test', 'lint' ], ->
|
||||
gulp.watch([ OPTIONS.files.coffee, OPTIONS.files.json ], [ 'coffee' ])
|
||||
gulp.watch([ OPTIONS.files.man ], [ 'man' ])
|
||||
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,10 +1,20 @@
|
||||
path = require('path')
|
||||
_ = require('lodash-contrib')
|
||||
async = require('async')
|
||||
resin = require('resin-sdk')
|
||||
visuals = require('resin-cli-visuals')
|
||||
###
|
||||
Copyright 2016-2017 Resin.io
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
###
|
||||
|
||||
commandOptions = require('./command-options')
|
||||
vcs = require('resin-vcs')
|
||||
|
||||
exports.create =
|
||||
signature: 'app create <name>'
|
||||
@ -12,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
|
||||
@ -28,25 +38,29 @@ exports.create =
|
||||
{
|
||||
signature: 'type'
|
||||
parameter: 'type'
|
||||
description: 'application type'
|
||||
description: 'application device type (Check available types with `resin devices supported`)'
|
||||
alias: 't'
|
||||
}
|
||||
]
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
async.waterfall([
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
patterns = require('../utils/patterns')
|
||||
|
||||
(callback) ->
|
||||
return callback(null, options.type) if options.type?
|
||||
# Validate the the application name is available
|
||||
# before asking the device type.
|
||||
# https://github.com/resin-io/resin-cli/issues/30
|
||||
resin.models.application.has(params.name).then (hasApplication) ->
|
||||
if hasApplication
|
||||
patterns.exitWithExpectedError('You already have an application with that name!')
|
||||
|
||||
resin.models.device.getSupportedDeviceTypes (error, deviceTypes) ->
|
||||
return callback(error) if error?
|
||||
visuals.widgets.select('Select a type', deviceTypes, callback)
|
||||
|
||||
(type, callback) ->
|
||||
resin.models.application.create(params.name, type, callback)
|
||||
|
||||
], done)
|
||||
.then ->
|
||||
return options.type or patterns.selectDeviceType()
|
||||
.then (deviceType) ->
|
||||
return resin.models.application.create(params.name, deviceType)
|
||||
.then (application) ->
|
||||
console.info("Application created: #{application.app_name} (#{application.device_type}, id #{application.id})")
|
||||
.nodeify(done)
|
||||
|
||||
exports.list =
|
||||
signature: 'apps'
|
||||
@ -62,17 +76,20 @@ exports.list =
|
||||
$ resin apps
|
||||
'''
|
||||
permission: 'user'
|
||||
primary: true
|
||||
action: (params, options, done) ->
|
||||
resin.models.application.getAll (error, applications) ->
|
||||
return done(error) if error?
|
||||
console.log visuals.widgets.table.horizontal applications, [
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
visuals = require('resin-cli-visuals')
|
||||
|
||||
resin.models.application.getAll().then (applications) ->
|
||||
console.log visuals.table.horizontal applications, [
|
||||
'id'
|
||||
'app_name'
|
||||
'device_type'
|
||||
'online_devices'
|
||||
'devices_length'
|
||||
]
|
||||
return done()
|
||||
.nodeify(done)
|
||||
|
||||
exports.info =
|
||||
signature: 'app <name>'
|
||||
@ -85,17 +102,20 @@ exports.info =
|
||||
$ resin app MyApp
|
||||
'''
|
||||
permission: 'user'
|
||||
primary: true
|
||||
action: (params, options, done) ->
|
||||
resin.models.application.get params.name, (error, application) ->
|
||||
return done(error) if error?
|
||||
console.log visuals.widgets.table.vertical application, [
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
visuals = require('resin-cli-visuals')
|
||||
|
||||
resin.models.application.get(params.name).then (application) ->
|
||||
console.log visuals.table.vertical application, [
|
||||
"$#{application.app_name}$"
|
||||
'id'
|
||||
'app_name'
|
||||
'device_type'
|
||||
'git_repository'
|
||||
'commit'
|
||||
]
|
||||
return done()
|
||||
.nodeify(done)
|
||||
|
||||
exports.restart =
|
||||
signature: 'app restart <name>'
|
||||
@ -109,7 +129,8 @@ exports.restart =
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
resin.models.application.restart(params.name, done)
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
resin.models.application.restart(params.name).nodeify(done)
|
||||
|
||||
exports.remove =
|
||||
signature: 'app rm <name>'
|
||||
@ -128,79 +149,9 @@ exports.remove =
|
||||
options: [ commandOptions.yes ]
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
visuals.patterns.remove 'application', options.yes, (callback) ->
|
||||
resin.models.application.remove(params.name, callback)
|
||||
, done
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
patterns = require('../utils/patterns')
|
||||
|
||||
exports.associate =
|
||||
signature: 'app associate <name>'
|
||||
description: 'associate a resin project'
|
||||
help: '''
|
||||
Use this command to associate a project directory with a resin application.
|
||||
|
||||
This command adds a 'resin' git remote to the directory and runs git init if necessary.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin app associate MyApp
|
||||
$ resin app associate MyApp --project my/app/directory
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
currentDirectory = process.cwd()
|
||||
|
||||
async.waterfall [
|
||||
|
||||
(callback) ->
|
||||
vcs.initialize(currentDirectory, callback)
|
||||
|
||||
(callback) ->
|
||||
resin.models.application.get(params.name, callback)
|
||||
|
||||
(application, callback) ->
|
||||
vcs.addRemote(currentDirectory, application.git_repository, callback)
|
||||
|
||||
], (error, remoteUrl) ->
|
||||
return done(error) if error?
|
||||
console.info("git repository added: #{remoteUrl}")
|
||||
return done(null, remoteUrl)
|
||||
|
||||
exports.init =
|
||||
signature: 'init'
|
||||
description: 'init an application'
|
||||
help: '''
|
||||
Use this command to initialise a directory as a resin application.
|
||||
|
||||
This command performs the following steps:
|
||||
- Create a resin.io application.
|
||||
- Initialize the current directory as a git repository.
|
||||
- Add the corresponding git remote to the application.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin init
|
||||
$ resin init --project my/app/directory
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
|
||||
currentDirectory = process.cwd()
|
||||
|
||||
async.waterfall([
|
||||
|
||||
(callback) ->
|
||||
currentDirectoryBasename = path.basename(currentDirectory)
|
||||
visuals.widgets.ask('What is the name of your application?', currentDirectoryBasename, callback)
|
||||
|
||||
(applicationName, callback) ->
|
||||
|
||||
# TODO: Make resin.models.application.create return
|
||||
# the whole application instead of just the id
|
||||
exports.create.action name: applicationName, options, (error) ->
|
||||
return callback(error) if error?
|
||||
return callback(null, applicationName)
|
||||
|
||||
(applicationName, callback) ->
|
||||
exports.associate.action(name: applicationName, options, callback)
|
||||
|
||||
], done)
|
||||
patterns.confirm(options.yes, 'Are you sure you want to delete the application?').then ->
|
||||
resin.models.application.remove(params.name)
|
||||
.nodeify(done)
|
||||
|
@ -1,73 +1,129 @@
|
||||
open = require('open')
|
||||
_ = require('lodash-contrib')
|
||||
url = require('url')
|
||||
async = require('async')
|
||||
resin = require('resin-sdk')
|
||||
settings = require('resin-settings-client')
|
||||
visuals = require('resin-cli-visuals')
|
||||
###
|
||||
Copyright 2016-2017 Resin.io
|
||||
|
||||
exports.whoami =
|
||||
signature: 'whoami'
|
||||
description: 'whoami'
|
||||
help: '''
|
||||
Use this command to get the logged in user name.
|
||||
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
|
||||
|
||||
Examples:
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
$ resin whoami
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
resin.auth.whoami (error, username) ->
|
||||
return done(error) if error?
|
||||
console.log(username)
|
||||
return done()
|
||||
|
||||
TOKEN_URL = url.resolve(settings.get('remoteUrl'), '/preferences')
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
###
|
||||
|
||||
exports.login =
|
||||
signature: 'login [token]'
|
||||
signature: 'login'
|
||||
description: 'login to resin.io'
|
||||
help: """
|
||||
help: '''
|
||||
Use this command to login to your resin.io account.
|
||||
|
||||
To login, you need your token, which is accesible from the preferences page:
|
||||
This command will prompt you to login using the following login types:
|
||||
|
||||
#{TOKEN_URL}
|
||||
- Web authorization: open your web browser and prompt you to authorize the CLI
|
||||
from the dashboard.
|
||||
|
||||
- Credentials: using email/password and 2FA.
|
||||
|
||||
- Token: using a session token or API key (experimental) from the preferences page.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin login
|
||||
$ resin login "eyJ0eXAiOiJKV1Qi..."
|
||||
"""
|
||||
$ resin login --web
|
||||
$ resin login --token "..."
|
||||
$ resin login --credentials
|
||||
$ resin login --credentials --email johndoe@gmail.com --password secret
|
||||
'''
|
||||
options: [
|
||||
{
|
||||
signature: 'token'
|
||||
description: 'session token or API key (experimental)'
|
||||
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: (params, options, done) ->
|
||||
_ = require('lodash')
|
||||
Promise = require('bluebird')
|
||||
resin = require('resin-sdk').fromSharedOptions()
|
||||
auth = require('../auth')
|
||||
form = require('resin-cli-form')
|
||||
patterns = require('../utils/patterns')
|
||||
messages = require('../utils/messages')
|
||||
|
||||
console.info """
|
||||
To login to the Resin CLI, you need your unique token, which is accesible from
|
||||
the preferences page at #{TOKEN_URL}
|
||||
login = (options) ->
|
||||
if options.token?
|
||||
return Promise.try ->
|
||||
return options.token if _.isString(options.token)
|
||||
return form.ask
|
||||
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
|
||||
console.info('Connecting to the web dashboard')
|
||||
return auth.login()
|
||||
|
||||
Attempting to open a browser at that location...
|
||||
"""
|
||||
return patterns.askLoginType().then (loginType) ->
|
||||
|
||||
async.waterfall([
|
||||
if loginType is 'register'
|
||||
{ runCommand } = require('../utils/helpers')
|
||||
return runCommand('signup')
|
||||
|
||||
(callback) ->
|
||||
open TOKEN_URL, (error) ->
|
||||
if error?
|
||||
console.error """
|
||||
Unable to open a web browser in the current environment.
|
||||
Please visit #{TOKEN_URL} manually.
|
||||
"""
|
||||
return callback()
|
||||
options[loginType] = true
|
||||
return login(options)
|
||||
|
||||
(callback) ->
|
||||
return callback(null, params.token) if params.token?
|
||||
visuals.widgets.ask('What\'s your token? (visible in the preferences page)', null, callback)
|
||||
resin.settings.get('resinUrl').then (resinUrl) ->
|
||||
console.log(messages.resinAsciiArt)
|
||||
console.log("\nLogging in to #{resinUrl}")
|
||||
return login(options)
|
||||
.then(resin.auth.whoami)
|
||||
.tap (username) ->
|
||||
console.info("Successfully logged in as: #{username}")
|
||||
console.info """
|
||||
|
||||
(token, callback) ->
|
||||
resin.auth.loginWithToken(token, done)
|
||||
Find out about the available commands by running:
|
||||
|
||||
], done)
|
||||
$ resin help
|
||||
|
||||
#{messages.reachingOut}
|
||||
"""
|
||||
.nodeify(done)
|
||||
|
||||
exports.logout =
|
||||
signature: 'logout'
|
||||
@ -79,9 +135,9 @@ exports.logout =
|
||||
|
||||
$ resin logout
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
resin.auth.logout(done)
|
||||
resin = require('resin-sdk').fromSharedOptions()
|
||||
resin.auth.logout().nodeify(done)
|
||||
|
||||
exports.signup =
|
||||
signature: 'signup'
|
||||
@ -94,70 +150,41 @@ exports.signup =
|
||||
Examples:
|
||||
|
||||
$ resin signup
|
||||
Email: me@mycompany.com
|
||||
Username: johndoe
|
||||
Email: johndoe@acme.com
|
||||
Password: ***********
|
||||
|
||||
$ resin signup --email me@mycompany.com --username johndoe --password ***********
|
||||
|
||||
$ resin whoami
|
||||
johndoe
|
||||
'''
|
||||
options: [
|
||||
{
|
||||
signature: 'email'
|
||||
parameter: 'email'
|
||||
description: 'user email'
|
||||
alias: 'e'
|
||||
}
|
||||
{
|
||||
signature: 'username'
|
||||
parameter: 'username'
|
||||
description: 'user name'
|
||||
alias: 'u'
|
||||
}
|
||||
{
|
||||
signature: 'password'
|
||||
parameter: 'user password'
|
||||
description: 'user password'
|
||||
alias: 'p'
|
||||
}
|
||||
]
|
||||
action: (params, options, done) ->
|
||||
resin = require('resin-sdk').fromSharedOptions()
|
||||
form = require('resin-cli-form')
|
||||
validation = require('../utils/validation')
|
||||
|
||||
hasOptionCredentials = not _.isEmpty(options)
|
||||
resin.settings.get('resinUrl').then (resinUrl) ->
|
||||
console.log("\nRegistering to #{resinUrl}")
|
||||
|
||||
if hasOptionCredentials
|
||||
form.run [
|
||||
message: 'Email:'
|
||||
name: 'email'
|
||||
type: 'input'
|
||||
validate: validation.validateEmail
|
||||
,
|
||||
message: 'Password:'
|
||||
name: 'password'
|
||||
type: 'password',
|
||||
validate: validation.validatePassword
|
||||
]
|
||||
|
||||
if not options.email?
|
||||
return done(new Error('Missing email'))
|
||||
|
||||
if not options.username?
|
||||
return done(new Error('Missing username'))
|
||||
|
||||
if not options.password?
|
||||
return done(new Error('Missing password'))
|
||||
|
||||
async.waterfall([
|
||||
|
||||
(callback) ->
|
||||
return callback(null, options) if hasOptionCredentials
|
||||
visuals.widgets.register(callback)
|
||||
|
||||
(credentials, callback) ->
|
||||
resin.auth.register credentials, (error, token) ->
|
||||
return callback(error, credentials)
|
||||
|
||||
(credentials, callback) ->
|
||||
resin.auth.login(credentials, callback)
|
||||
|
||||
], done)
|
||||
.then(resin.auth.register)
|
||||
.then(resin.auth.loginWithToken)
|
||||
.nodeify(done)
|
||||
|
||||
exports.whoami =
|
||||
signature: 'whoami'
|
||||
description: 'get current username'
|
||||
description: 'get current username and email address'
|
||||
help: '''
|
||||
Use this command to find out the current logged in username.
|
||||
Use this command to find out the current logged in username and email address.
|
||||
|
||||
Examples:
|
||||
|
||||
@ -165,9 +192,19 @@ exports.whoami =
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
resin.auth.whoami (error, username) ->
|
||||
Promise = require('bluebird')
|
||||
resin = require('resin-sdk').fromSharedOptions()
|
||||
visuals = require('resin-cli-visuals')
|
||||
|
||||
if not username?
|
||||
return done(new Error('Username not found'))
|
||||
|
||||
console.log(username)
|
||||
Promise.props
|
||||
username: resin.auth.whoami()
|
||||
email: resin.auth.getEmail()
|
||||
url: resin.settings.get('resinUrl')
|
||||
.then (results) ->
|
||||
console.log visuals.table.vertical results, [
|
||||
'$account information$'
|
||||
'username'
|
||||
'email'
|
||||
'url'
|
||||
]
|
||||
.nodeify(done)
|
||||
|
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,3 +1,19 @@
|
||||
###
|
||||
Copyright 2016-2017 Resin.io
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
###
|
||||
|
||||
_ = require('lodash')
|
||||
|
||||
exports.yes =
|
||||
@ -16,6 +32,40 @@ exports.application = _.defaults
|
||||
required: 'You have to specify an application'
|
||||
, exports.optionalApplication
|
||||
|
||||
exports.optionalDevice =
|
||||
signature: 'device'
|
||||
parameter: 'device'
|
||||
description: 'device uuid'
|
||||
alias: 'd'
|
||||
|
||||
exports.optionalDeviceApiKey =
|
||||
signature: 'deviceApiKey'
|
||||
description: 'custom device key - note that this is only supported on ResinOS 2.0.3+'
|
||||
parameter: 'device-api-key'
|
||||
alias: 'k'
|
||||
|
||||
exports.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'
|
||||
@ -33,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'
|
||||
|
328
lib/actions/config.coffee
Normal file
328
lib/actions/config.coffee
Normal file
@ -0,0 +1,328 @@
|
||||
###
|
||||
Copyright 2016-2017 Resin.io
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
###
|
||||
|
||||
commandOptions = require('./command-options')
|
||||
{ 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 the mounted filesystem (e.g. SD card) of a provisioned device"
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin config read --type raspberry-pi
|
||||
$ resin config read --type raspberry-pi --drive /dev/disk2
|
||||
'''
|
||||
options: [
|
||||
{
|
||||
signature: 'type'
|
||||
description: 'device type (Check available types with `resin devices supported`)'
|
||||
parameter: 'type'
|
||||
alias: 't'
|
||||
required: 'You have to specify a device type'
|
||||
}
|
||||
{
|
||||
signature: 'drive'
|
||||
description: 'drive'
|
||||
parameter: 'drive'
|
||||
alias: 'd'
|
||||
}
|
||||
]
|
||||
permission: 'user'
|
||||
root: true
|
||||
action: (params, options, done) ->
|
||||
Promise = require('bluebird')
|
||||
config = require('resin-config-json')
|
||||
visuals = require('resin-cli-visuals')
|
||||
umountAsync = Promise.promisify(require('umount').umount)
|
||||
prettyjson = require('prettyjson')
|
||||
|
||||
Promise.try ->
|
||||
return options.drive or visuals.drive('Select the device drive')
|
||||
.tap(umountAsync)
|
||||
.then (drive) ->
|
||||
return config.read(drive, options.type)
|
||||
.tap (configJSON) ->
|
||||
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 to the mounted filesystem (e.g. SD card) of a provisioned device
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin config write --type raspberry-pi username johndoe
|
||||
$ resin config write --type raspberry-pi --drive /dev/disk2 username johndoe
|
||||
$ resin config write --type raspberry-pi files.network/settings "..."
|
||||
'''
|
||||
options: [
|
||||
{
|
||||
signature: 'type'
|
||||
description: 'device type (Check available types with `resin devices supported`)'
|
||||
parameter: 'type'
|
||||
alias: 't'
|
||||
required: 'You have to specify a device type'
|
||||
}
|
||||
{
|
||||
signature: 'drive'
|
||||
description: 'drive'
|
||||
parameter: 'drive'
|
||||
alias: 'd'
|
||||
}
|
||||
]
|
||||
permission: 'user'
|
||||
root: true
|
||||
action: (params, options, done) ->
|
||||
Promise = require('bluebird')
|
||||
_ = require('lodash')
|
||||
config = require('resin-config-json')
|
||||
visuals = require('resin-cli-visuals')
|
||||
umountAsync = Promise.promisify(require('umount').umount)
|
||||
|
||||
Promise.try ->
|
||||
return options.drive or visuals.drive('Select the device drive')
|
||||
.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 umountAsync(drive)
|
||||
.then (configJSON) ->
|
||||
return config.write(drive, options.type, configJSON)
|
||||
.tap ->
|
||||
console.info('Done')
|
||||
.nodeify(done)
|
||||
|
||||
exports.inject =
|
||||
signature: 'config inject <file>'
|
||||
description: 'inject a device configuration file'
|
||||
help: '''
|
||||
Use this command to inject a config.json file to the mounted filesystem
|
||||
(e.g. SD card or mounted resinOS image) of a provisioned device"
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin config inject my/config.json --type raspberry-pi
|
||||
$ resin config inject my/config.json --type raspberry-pi --drive /dev/disk2
|
||||
'''
|
||||
options: [
|
||||
{
|
||||
signature: 'type'
|
||||
description: 'device type (Check available types with `resin devices supported`)'
|
||||
parameter: 'type'
|
||||
alias: 't'
|
||||
required: 'You have to specify a device type'
|
||||
}
|
||||
{
|
||||
signature: 'drive'
|
||||
description: 'drive'
|
||||
parameter: 'drive'
|
||||
alias: 'd'
|
||||
}
|
||||
]
|
||||
permission: 'user'
|
||||
root: true
|
||||
action: (params, options, done) ->
|
||||
Promise = require('bluebird')
|
||||
config = require('resin-config-json')
|
||||
visuals = require('resin-cli-visuals')
|
||||
umountAsync = Promise.promisify(require('umount').umount)
|
||||
readFileAsync = Promise.promisify(require('fs').readFile)
|
||||
|
||||
Promise.try ->
|
||||
return options.drive or visuals.drive('Select the device drive')
|
||||
.tap(umountAsync)
|
||||
.then (drive) ->
|
||||
readFileAsync(params.file, 'utf8').then(JSON.parse).then (configJSON) ->
|
||||
return config.write(drive, options.type, configJSON)
|
||||
.tap ->
|
||||
console.info('Done')
|
||||
.nodeify(done)
|
||||
|
||||
exports.reconfigure =
|
||||
signature: 'config reconfigure'
|
||||
description: 'reconfigure a provisioned device'
|
||||
help: '''
|
||||
Use this command to reconfigure a provisioned device
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin config reconfigure --type raspberry-pi
|
||||
$ resin config reconfigure --type raspberry-pi --advanced
|
||||
$ resin config reconfigure --type raspberry-pi --drive /dev/disk2
|
||||
'''
|
||||
options: [
|
||||
{
|
||||
signature: 'type'
|
||||
description: 'device type (Check available types with `resin devices supported`)'
|
||||
parameter: 'type'
|
||||
alias: 't'
|
||||
required: 'You have to specify a device type'
|
||||
}
|
||||
{
|
||||
signature: 'drive'
|
||||
description: 'drive'
|
||||
parameter: 'drive'
|
||||
alias: 'd'
|
||||
}
|
||||
{
|
||||
signature: 'advanced'
|
||||
description: 'show advanced commands'
|
||||
boolean: true
|
||||
alias: 'v'
|
||||
}
|
||||
]
|
||||
permission: 'user'
|
||||
root: true
|
||||
action: (params, options, done) ->
|
||||
Promise = require('bluebird')
|
||||
config = require('resin-config-json')
|
||||
visuals = require('resin-cli-visuals')
|
||||
{ runCommand } = require('../utils/helpers')
|
||||
umountAsync = Promise.promisify(require('umount').umount)
|
||||
|
||||
Promise.try ->
|
||||
return options.drive or visuals.drive('Select the device drive')
|
||||
.tap(umountAsync)
|
||||
.then (drive) ->
|
||||
config.read(drive, options.type).get('uuid')
|
||||
.tap ->
|
||||
umountAsync(drive)
|
||||
.then (uuid) ->
|
||||
configureCommand = "os configure #{drive} --device #{uuid}"
|
||||
if options.advanced
|
||||
configureCommand += ' --advanced'
|
||||
return runCommand(configureCommand)
|
||||
.then ->
|
||||
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.
|
||||
|
||||
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 --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')
|
||||
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?
|
||||
exitWithExpectedError '''
|
||||
You have to pass either a device or an application.
|
||||
|
||||
See the help page for examples:
|
||||
|
||||
$ resin help config generate
|
||||
'''
|
||||
|
||||
Promise.try ->
|
||||
if options.device?
|
||||
return resin.models.device.get(options.device)
|
||||
return resin.models.application.get(options.application)
|
||||
.then (resource) ->
|
||||
resin.models.device.getManifestBySlug(resource.device_type)
|
||||
.get('options')
|
||||
.then (formOptions) ->
|
||||
# Pass params as an override: if there is any param with exactly the same name as a
|
||||
# required option, that value is used (and the corresponding question is not asked)
|
||||
form.run(formOptions, override: options)
|
||||
.then (answers) ->
|
||||
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 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,134 +1,119 @@
|
||||
_ = require('lodash-contrib')
|
||||
path = require('path')
|
||||
async = require('async')
|
||||
resin = require('resin-sdk')
|
||||
visuals = require('resin-cli-visuals')
|
||||
vcs = require('resin-vcs')
|
||||
###
|
||||
Copyright 2016-2017 Resin.io
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
###
|
||||
|
||||
commandOptions = require('./command-options')
|
||||
osAction = require('./os')
|
||||
_ = require('lodash')
|
||||
{ normalizeUuidProp } = require('../utils/normalization')
|
||||
|
||||
expandForAppName = {
|
||||
$expand: belongs_to__application: $select: 'app_name'
|
||||
}
|
||||
|
||||
exports.list =
|
||||
signature: 'devices'
|
||||
description: 'list all devices'
|
||||
help: '''
|
||||
Use this command to list all devices that belong to a certain application.
|
||||
Use this command to list all devices that belong to you.
|
||||
|
||||
You can filter the devices by application by using the `--application` option.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin devices
|
||||
$ resin devices --application MyApp
|
||||
$ resin devices --app MyApp
|
||||
$ resin devices -a MyApp
|
||||
'''
|
||||
options: [ commandOptions.application ]
|
||||
options: [ commandOptions.optionalApplication ]
|
||||
permission: 'user'
|
||||
primary: true
|
||||
action: (params, options, done) ->
|
||||
resin.models.device.getAllByApplication options.application, (error, devices) ->
|
||||
return done(error) if error?
|
||||
console.log visuals.widgets.table.horizontal devices, [
|
||||
Promise = require('bluebird')
|
||||
resin = require('resin-sdk').fromSharedOptions()
|
||||
visuals = require('resin-cli-visuals')
|
||||
|
||||
Promise.try ->
|
||||
if options.application?
|
||||
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'
|
||||
'name'
|
||||
'uuid'
|
||||
'device_name'
|
||||
'device_type'
|
||||
'is_online'
|
||||
'application_name'
|
||||
'status'
|
||||
'last_seen'
|
||||
'is_online'
|
||||
'supervisor_version'
|
||||
'os_version'
|
||||
'dashboard_url'
|
||||
]
|
||||
|
||||
return done()
|
||||
.nodeify(done)
|
||||
|
||||
exports.info =
|
||||
signature: 'device <name>'
|
||||
signature: 'device <uuid>'
|
||||
description: 'list a single device'
|
||||
help: '''
|
||||
Use this command to show information about a single device.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin device MyDevice
|
||||
$ resin device 7cf02a6
|
||||
'''
|
||||
permission: 'user'
|
||||
primary: true
|
||||
action: (params, options, done) ->
|
||||
resin.models.device.get params.name, (error, device) ->
|
||||
return done(error) if error?
|
||||
console.log visuals.widgets.table.vertical device, [
|
||||
'id'
|
||||
'name'
|
||||
'device_type'
|
||||
'is_online'
|
||||
'ip_address'
|
||||
'application_name'
|
||||
'status'
|
||||
'last_seen'
|
||||
'uuid'
|
||||
'commit'
|
||||
'supervisor_version'
|
||||
'is_web_accessible'
|
||||
'note'
|
||||
]
|
||||
normalizeUuidProp(params)
|
||||
resin = require('resin-sdk').fromSharedOptions()
|
||||
visuals = require('resin-cli-visuals')
|
||||
|
||||
return done()
|
||||
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
|
||||
|
||||
exports.remove =
|
||||
signature: 'device rm <name>'
|
||||
description: 'remove a device'
|
||||
help: '''
|
||||
Use this command to remove a device from resin.io.
|
||||
|
||||
Notice this command asks for confirmation interactively.
|
||||
You can avoid this by passing the `--yes` boolean option.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin device rm MyDevice
|
||||
$ resin device rm MyDevice --yes
|
||||
'''
|
||||
options: [ commandOptions.yes ]
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
visuals.patterns.remove 'device', options.yes, (callback) ->
|
||||
resin.models.device.remove(params.name, callback)
|
||||
, done
|
||||
|
||||
exports.identify =
|
||||
signature: 'device identify <uuid>'
|
||||
description: 'identify a device with a UUID'
|
||||
help: '''
|
||||
Use this command to identify a device.
|
||||
|
||||
In the Raspberry Pi, the ACT led is blinked several times.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin device identify 23c73a12e3527df55c60b9ce647640c1b7da1b32d71e6a39849ac0f00db828
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
resin.models.device.identify(params.uuid, done)
|
||||
|
||||
exports.rename =
|
||||
signature: 'device rename <name> [newName]'
|
||||
description: 'rename a resin device'
|
||||
help: '''
|
||||
Use this command to rename a device.
|
||||
|
||||
If you omit the name, you'll get asked for it interactively.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin device rename MyDevice MyPi
|
||||
$ resin device rename MyDevice
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
async.waterfall [
|
||||
|
||||
(callback) ->
|
||||
if not _.isEmpty(params.newName)
|
||||
return callback(null, params.newName)
|
||||
visuals.widgets.ask('How do you want to name this device?', null, callback)
|
||||
|
||||
(newName, callback) ->
|
||||
resin.models.device.rename(params.name, newName, callback)
|
||||
|
||||
], done
|
||||
console.log visuals.table.vertical device, [
|
||||
"$#{device.device_name}$"
|
||||
'id'
|
||||
'device_type'
|
||||
'status'
|
||||
'is_online'
|
||||
'ip_address'
|
||||
'application_name'
|
||||
'last_seen'
|
||||
'uuid'
|
||||
'commit'
|
||||
'supervisor_version'
|
||||
'is_web_accessible'
|
||||
'note'
|
||||
'os_version'
|
||||
'dashboard_url'
|
||||
]
|
||||
.nodeify(done)
|
||||
|
||||
exports.supported =
|
||||
signature: 'devices supported'
|
||||
@ -140,82 +125,333 @@ exports.supported =
|
||||
|
||||
$ resin devices supported
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
resin.models.device.getSupportedDeviceTypes (error, devices) ->
|
||||
return done(error) if error?
|
||||
_.each(devices, _.unary(console.log))
|
||||
done()
|
||||
resin = require('resin-sdk').fromSharedOptions()
|
||||
visuals = require('resin-cli-visuals')
|
||||
|
||||
exports.init =
|
||||
signature: 'device init [device]'
|
||||
description: 'initialise a device with resin os'
|
||||
resin.models.config.getDeviceTypes().then (deviceTypes) ->
|
||||
console.log visuals.table.horizontal deviceTypes, [
|
||||
'slug'
|
||||
'name'
|
||||
]
|
||||
.nodeify(done)
|
||||
|
||||
exports.register =
|
||||
signature: 'device register <application>'
|
||||
description: 'register a device'
|
||||
help: '''
|
||||
Use this command to download the OS image of a certain application and write it to an SD Card.
|
||||
Use this command to register a device to an application.
|
||||
|
||||
Note that this command requires admin privileges.
|
||||
Examples:
|
||||
|
||||
If `device` is omitted, you will be prompted to select a device interactively.
|
||||
$ resin device register MyApp
|
||||
$ resin device register MyApp --uuid <uuid>
|
||||
'''
|
||||
permission: 'user'
|
||||
options: [
|
||||
{
|
||||
signature: 'uuid'
|
||||
description: 'custom uuid'
|
||||
parameter: 'uuid'
|
||||
alias: 'u'
|
||||
}
|
||||
]
|
||||
action: (params, options, done) ->
|
||||
Promise = require('bluebird')
|
||||
resin = require('resin-sdk').fromSharedOptions()
|
||||
|
||||
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.id, 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.
|
||||
|
||||
Notice this command asks for confirmation interactively.
|
||||
You can avoid this by passing the `--yes` boolean option.
|
||||
|
||||
You can quiet the progress bar by passing the `--quiet` boolean option.
|
||||
Examples:
|
||||
|
||||
You may have to unmount the device before attempting this operation.
|
||||
$ resin device rm 7cf02a6
|
||||
$ resin device rm 7cf02a6 --yes
|
||||
'''
|
||||
options: [ commandOptions.yes ]
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
normalizeUuidProp(params)
|
||||
resin = require('resin-sdk').fromSharedOptions()
|
||||
patterns = require('../utils/patterns')
|
||||
|
||||
You need to configure the network type and other settings:
|
||||
patterns.confirm(options.yes, 'Are you sure you want to delete the device?').then ->
|
||||
resin.models.device.remove(params.uuid)
|
||||
.nodeify(done)
|
||||
|
||||
Ethernet:
|
||||
You can setup the device OS to use ethernet by setting the `--network` option to "ethernet".
|
||||
exports.identify =
|
||||
signature: 'device identify <uuid>'
|
||||
description: 'identify a device with a UUID'
|
||||
help: '''
|
||||
Use this command to identify a device.
|
||||
|
||||
Wifi:
|
||||
You can setup the device OS to use wifi by setting the `--network` option to "wifi".
|
||||
If you set "network" to "wifi", you will need to specify the `--ssid` and `--key` option as well.
|
||||
In the Raspberry Pi, the ACT led is blinked several times.
|
||||
|
||||
You can omit network related options to be asked about them interactively.
|
||||
Examples:
|
||||
|
||||
$ resin device identify 23c73a1
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
normalizeUuidProp(params)
|
||||
resin = require('resin-sdk').fromSharedOptions()
|
||||
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
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin device reboot 23c73a1
|
||||
'''
|
||||
options: [ commandOptions.forceUpdateLock ]
|
||||
permission: 'user'
|
||||
action: (params, options, 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]'
|
||||
description: 'rename a resin device'
|
||||
help: '''
|
||||
Use this command to rename a device.
|
||||
|
||||
If you omit the name, you'll get asked for it interactively.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin device rename 7cf02a6
|
||||
$ resin device rename 7cf02a6 MyPi
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
normalizeUuidProp(params)
|
||||
Promise = require('bluebird')
|
||||
resin = require('resin-sdk').fromSharedOptions()
|
||||
form = require('resin-cli-form')
|
||||
|
||||
Promise.try ->
|
||||
return params.newName if not _.isEmpty(params.newName)
|
||||
|
||||
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.
|
||||
|
||||
If you omit the application, you'll get asked for it interactively.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin device move 7cf02a6
|
||||
$ resin device move 7cf02a6 --application MyNewApp
|
||||
'''
|
||||
permission: 'user'
|
||||
options: [ commandOptions.optionalApplication ]
|
||||
action: (params, options, done) ->
|
||||
normalizeUuidProp(params)
|
||||
resin = require('resin-sdk').fromSharedOptions()
|
||||
patterns = require('../utils/patterns')
|
||||
|
||||
resin.models.device.get(params.uuid, expandForAppName).then (device) ->
|
||||
return options.application or patterns.selectApplication (application) ->
|
||||
return _.every [
|
||||
application.device_type is device.device_type
|
||||
device.belongs_to__application[0].app_name isnt application.app_name
|
||||
]
|
||||
.tap (application) ->
|
||||
return resin.models.device.move(params.uuid, application)
|
||||
.then (application) ->
|
||||
console.info("#{params.uuid} was moved to #{application}")
|
||||
.nodeify(done)
|
||||
|
||||
exports.init =
|
||||
signature: 'device init'
|
||||
description: 'initialise a device with resinOS'
|
||||
help: '''
|
||||
Use this command to download the OS image of a certain application and write it to an SD Card.
|
||||
|
||||
Notice this command may ask for confirmation interactively.
|
||||
You can avoid this by passing the `--yes` boolean option.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin device init
|
||||
$ resin device init --application 91
|
||||
$ resin device init --application 91 --network ethernet
|
||||
$ resin device init /dev/disk2 --application 91 --network wifi --ssid MyNetwork --key secret
|
||||
$ resin device init --application MyApp
|
||||
'''
|
||||
options: [
|
||||
commandOptions.optionalApplication
|
||||
commandOptions.network
|
||||
commandOptions.wifiSsid
|
||||
commandOptions.wifiKey
|
||||
commandOptions.yes
|
||||
commandOptions.advancedConfig
|
||||
_.assign({}, commandOptions.osVersion, { signature: 'os-version', parameter: 'os-version' })
|
||||
commandOptions.drive
|
||||
{
|
||||
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')
|
||||
rimraf = Promise.promisify(require('rimraf'))
|
||||
tmp = require('tmp')
|
||||
tmpNameAsync = Promise.promisify(tmp.tmpName)
|
||||
tmp.setGracefulCleanup()
|
||||
|
||||
params.id = options.application
|
||||
resin = require('resin-sdk').fromSharedOptions()
|
||||
patterns = require('../utils/patterns')
|
||||
{ runCommand } = require('../utils/helpers')
|
||||
|
||||
async.waterfall([
|
||||
Promise.try ->
|
||||
return options.application if options.application?
|
||||
return patterns.selectApplication()
|
||||
.then(resin.models.application.get)
|
||||
.then (application) ->
|
||||
|
||||
(callback) ->
|
||||
return callback(null, options.application) if options.application?
|
||||
vcs.getApplicationId(process.cwd(), callback)
|
||||
download = ->
|
||||
tmpNameAsync().then (tempPath) ->
|
||||
osVersion = options['os-version'] or 'default'
|
||||
runCommand("os download #{application.device_type} --output '#{tempPath}' --version #{osVersion}")
|
||||
.disposer (tempPath) ->
|
||||
return rimraf(tempPath)
|
||||
|
||||
(applicationId, callback) ->
|
||||
params.id = applicationId
|
||||
return callback(null, params.device) if params.device?
|
||||
visuals.patterns.selectDrive(callback)
|
||||
Promise.using download(), (tempPath) ->
|
||||
runCommand("device register #{application.app_name}")
|
||||
.then(resin.models.device.get)
|
||||
.tap (device) ->
|
||||
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) ->
|
||||
resin.models.device.remove(device.uuid).finally ->
|
||||
throw error
|
||||
.then (device) ->
|
||||
console.log('Done')
|
||||
return device.uuid
|
||||
|
||||
(device, callback) ->
|
||||
params.device = device
|
||||
message = "This will completely erase #{params.device}. Are you sure you want to continue?"
|
||||
visuals.patterns.confirm(options.yes, message, callback)
|
||||
|
||||
(confirmed, callback) ->
|
||||
return done() if not confirmed
|
||||
options.yes = confirmed
|
||||
osAction.download.action(params, options, callback)
|
||||
|
||||
(outputFile, callback) ->
|
||||
params.image = outputFile
|
||||
osAction.install.action(params, options, callback)
|
||||
|
||||
], done)
|
||||
.nodeify(done)
|
||||
|
@ -1,32 +0,0 @@
|
||||
_ = require('lodash')
|
||||
async = require('async')
|
||||
visuals = require('resin-cli-visuals')
|
||||
drivelist = require('drivelist')
|
||||
|
||||
exports.list =
|
||||
signature: 'drives'
|
||||
description: 'list available drives'
|
||||
help: '''
|
||||
Use this command to list all drives that are connected to your machine.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin drives
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
drivelist.list (error, drives) ->
|
||||
return done(error) if error?
|
||||
|
||||
async.reject drives, drivelist.isSystem, (removableDrives) ->
|
||||
|
||||
if _.isEmpty(removableDrives)
|
||||
return done(new Error('No removable devices available'))
|
||||
|
||||
console.log visuals.widgets.table.horizontal removableDrives, [
|
||||
'device'
|
||||
'description'
|
||||
'size'
|
||||
]
|
||||
|
||||
return done()
|
@ -1,26 +1,46 @@
|
||||
_ = require('lodash-contrib')
|
||||
resin = require('resin-sdk')
|
||||
visuals = require('resin-cli-visuals')
|
||||
###
|
||||
Copyright 2016-2017 Resin.io
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
###
|
||||
|
||||
commandOptions = require('./command-options')
|
||||
{ normalizeUuidProp } = require('../utils/normalization')
|
||||
|
||||
exports.list =
|
||||
signature: 'envs'
|
||||
description: 'list all environment variables'
|
||||
help: '''
|
||||
Use this command to list all environment variables for a particular application.
|
||||
Notice we will support per-device environment variables soon.
|
||||
Use this command to list all environment variables for
|
||||
a particular application or device.
|
||||
|
||||
This command lists all custom environment variables set on the devices running
|
||||
the application. If you want to see all environment variables, including private
|
||||
This command lists all custom environment variables.
|
||||
If you want to see all environment variables, including private
|
||||
ones used by resin, use the verbose option.
|
||||
|
||||
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 91
|
||||
$ resin envs --application 91 --verbose
|
||||
$ resin envs --application MyApp
|
||||
$ resin envs --application MyApp --verbose
|
||||
$ resin envs --device 7cf02a6
|
||||
'''
|
||||
options: [
|
||||
commandOptions.application
|
||||
commandOptions.optionalApplication
|
||||
commandOptions.optionalDevice
|
||||
|
||||
{
|
||||
signature: 'verbose'
|
||||
@ -31,19 +51,35 @@ exports.list =
|
||||
]
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
resin.models.environmentVariables.getAllByApplication options.application, (error, environmentVariables) ->
|
||||
return done(error) if error?
|
||||
normalizeUuidProp(options, 'device')
|
||||
Promise = require('bluebird')
|
||||
_ = require('lodash')
|
||||
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
|
||||
exitWithExpectedError('You must specify an application or device')
|
||||
|
||||
.tap (environmentVariables) ->
|
||||
if _.isEmpty(environmentVariables)
|
||||
exitWithExpectedError('No environment variables found')
|
||||
if not options.verbose
|
||||
environmentVariables = _.reject(environmentVariables, resin.models.environmentVariables.isSystemVariable)
|
||||
isSystemVariable = resin.models.environmentVariables.isSystemVariable
|
||||
environmentVariables = _.reject(environmentVariables, isSystemVariable)
|
||||
|
||||
console.log visuals.widgets.table.horizontal environmentVariables, [
|
||||
console.log visuals.table.horizontal environmentVariables, [
|
||||
'id'
|
||||
'name'
|
||||
'value'
|
||||
]
|
||||
|
||||
return done()
|
||||
.nodeify(done)
|
||||
|
||||
exports.remove =
|
||||
signature: 'env rm <id>'
|
||||
@ -56,17 +92,29 @@ exports.remove =
|
||||
Notice this command asks for confirmation interactively.
|
||||
You can avoid this by passing the `--yes` boolean option.
|
||||
|
||||
If you want to eliminate a device environment variable, pass the `--device` boolean option.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin env rm 215
|
||||
$ resin env rm 215 --yes
|
||||
$ resin env rm 215 --device
|
||||
'''
|
||||
options: [ commandOptions.yes ]
|
||||
options: [
|
||||
commandOptions.yes
|
||||
commandOptions.booleanDevice
|
||||
]
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
visuals.patterns.remove 'environment variable', options.yes, (callback) ->
|
||||
resin.models.environmentVariables.remove(params.id, callback)
|
||||
, done
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
patterns = require('../utils/patterns')
|
||||
|
||||
patterns.confirm(options.yes, 'Are you sure you want to delete the environment variable?').then ->
|
||||
if options.device
|
||||
resin.models.environmentVariables.device.remove(params.id)
|
||||
else
|
||||
resin.models.environmentVariables.remove(params.id)
|
||||
.nodeify(done)
|
||||
|
||||
exports.add =
|
||||
signature: 'env add <key> [value]'
|
||||
@ -74,31 +122,53 @@ exports.add =
|
||||
help: '''
|
||||
Use this command to add an enviroment variable to an application.
|
||||
|
||||
You need to pass the `--application` option.
|
||||
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.
|
||||
|
||||
Use the `--device` option if you want to assign the environment variable
|
||||
to a specific device.
|
||||
|
||||
If the value is grabbed from the environment, a warning message will be printed.
|
||||
Use `--quiet` to remove it.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin env add EDITOR vim -a 91
|
||||
$ resin env add TERM -a 91
|
||||
$ resin env add EDITOR vim --application MyApp
|
||||
$ resin env add TERM --application MyApp
|
||||
$ resin env add EDITOR vim --device 7cf02a6
|
||||
'''
|
||||
options: [ commandOptions.application ]
|
||||
options: [
|
||||
commandOptions.optionalApplication
|
||||
commandOptions.optionalDevice
|
||||
]
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
if not params.value?
|
||||
params.value = process.env[params.key]
|
||||
normalizeUuidProp(options, 'device')
|
||||
Promise = require('bluebird')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
|
||||
{ exitWithExpectedError } = require('../utils/patterns')
|
||||
|
||||
Promise.try ->
|
||||
if not params.value?
|
||||
return done(new Error("Environment value not found for key: #{params.key}"))
|
||||
else
|
||||
console.info("Warning: using #{params.key}=#{params.value} from host environment")
|
||||
params.value = process.env[params.key]
|
||||
|
||||
resin.models.environmentVariables.create(options.application, params.key, params.value, done)
|
||||
if not params.value?
|
||||
throw new Error("Environment value not found for key: #{params.key}")
|
||||
else
|
||||
console.info("Warning: using #{params.key}=#{params.value} from host environment")
|
||||
|
||||
if options.application?
|
||||
resin.models.environmentVariables.create(options.application, params.key, params.value)
|
||||
else if options.device?
|
||||
resin.models.environmentVariables.device.create(options.device, params.key, params.value)
|
||||
else
|
||||
exitWithExpectedError('You must specify an application or device')
|
||||
.nodeify(done)
|
||||
|
||||
exports.rename =
|
||||
signature: 'env rename <id> <value>'
|
||||
@ -106,10 +176,22 @@ exports.rename =
|
||||
help: '''
|
||||
Use this command to rename an enviroment variable from an application.
|
||||
|
||||
Pass the `--device` boolean option if you want to rename a device environment variable.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin env rename 376 emacs
|
||||
$ resin env rename 376 emacs --device
|
||||
'''
|
||||
permission: 'user'
|
||||
options: [ commandOptions.booleanDevice ]
|
||||
action: (params, options, done) ->
|
||||
resin.models.environmentVariables.update(params.id, params.value, done)
|
||||
Promise = require('bluebird')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
|
||||
Promise.try ->
|
||||
if options.device
|
||||
resin.models.environmentVariables.device.update(params.id, params.value)
|
||||
else
|
||||
resin.models.environmentVariables.update(params.id, params.value)
|
||||
.nodeify(done)
|
||||
|
@ -1,90 +0,0 @@
|
||||
async = require('async')
|
||||
fs = require('fs')
|
||||
path = require('path')
|
||||
_ = require('lodash')
|
||||
resin = require('resin-sdk')
|
||||
visuals = require('resin-cli-visuals')
|
||||
vcs = require('resin-vcs')
|
||||
examplesData = require('../data/examples.json')
|
||||
|
||||
exports.list =
|
||||
signature: 'examples'
|
||||
description: 'list all example applications'
|
||||
help: '''
|
||||
Use this command to list available example applications from resin.io
|
||||
|
||||
Example:
|
||||
|
||||
$ resin examples
|
||||
'''
|
||||
permission: 'user'
|
||||
action: ->
|
||||
examplesData = _.map examplesData, (example, index) ->
|
||||
example.id = index + 1
|
||||
return example
|
||||
|
||||
examplesData = _.map examplesData, (example) ->
|
||||
example.author ?= 'Unknown'
|
||||
return example
|
||||
|
||||
console.log visuals.widgets.table.horizontal examplesData, [
|
||||
'id'
|
||||
'display_name'
|
||||
'repository'
|
||||
'author'
|
||||
]
|
||||
|
||||
exports.info =
|
||||
signature: 'example <id>'
|
||||
description: 'list a single example application'
|
||||
help: '''
|
||||
Use this command to show information of a single example application
|
||||
|
||||
Example:
|
||||
|
||||
$ resin example 3
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
id = params.id - 1
|
||||
example = examplesData[id]
|
||||
|
||||
if not example?
|
||||
return done(new Error("Unknown example: #{id}"))
|
||||
|
||||
example.id = id
|
||||
example.author ?= 'Unknown'
|
||||
|
||||
console.log visuals.widgets.table.vertical example, [
|
||||
'id'
|
||||
'display_name'
|
||||
'description'
|
||||
'author'
|
||||
'repository'
|
||||
]
|
||||
|
||||
return done()
|
||||
|
||||
exports.clone =
|
||||
signature: 'example clone <id>'
|
||||
description: 'clone an example application'
|
||||
help: '''
|
||||
Use this command to clone an example application to the current directory
|
||||
|
||||
This command outputs information about the cloning process.
|
||||
Use `--quiet` to remove that output.
|
||||
|
||||
Example:
|
||||
|
||||
$ resin example clone 3
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
example = examplesData[params.id - 1]
|
||||
|
||||
if not example?
|
||||
return done(new Error("Unknown example: #{id}"))
|
||||
|
||||
currentDirectory = process.cwd()
|
||||
console.info("Cloning #{example.display_name} to #{currentDirectory}")
|
||||
vcs.clone(example.repository, currentDirectory, done)
|
@ -1,115 +1,96 @@
|
||||
###
|
||||
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')
|
||||
_.str = require('underscore.string')
|
||||
resin = require('resin-sdk')
|
||||
capitano = require('capitano')
|
||||
columnify = require('columnify')
|
||||
messages = require('../utils/messages')
|
||||
{ exitWithExpectedError } = require('../utils/patterns')
|
||||
|
||||
# TODO: Refactor this terrible mess
|
||||
parse = (object) ->
|
||||
return _.fromPairs _.map object, (item) ->
|
||||
|
||||
PADDING_INITIAL = ' '
|
||||
PADDING_MIDDLE = '\t'
|
||||
# Hacky way to determine if an object is
|
||||
# a function or a command
|
||||
if item.alias?
|
||||
signature = item.toString()
|
||||
else
|
||||
signature = item.signature.toString()
|
||||
|
||||
getFieldMaxLength = (array, field) ->
|
||||
return _.max _.map array, (item) ->
|
||||
return item[field].toString().length
|
||||
return [
|
||||
signature
|
||||
item.description
|
||||
]
|
||||
|
||||
buildHelpString = (firstColumn, secondColumn) ->
|
||||
result = "#{PADDING_INITIAL}#{firstColumn}"
|
||||
result += "#{PADDING_MIDDLE}#{secondColumn}"
|
||||
return result
|
||||
indent = (text) ->
|
||||
text = _.map text.split('\n'), (line) ->
|
||||
return ' ' + line
|
||||
return text.join('\n')
|
||||
|
||||
addOptionPrefix = (option) ->
|
||||
return if option.length <= 0
|
||||
if option.length is 1
|
||||
return "-#{option}"
|
||||
else
|
||||
return "--#{option}"
|
||||
print = (data) ->
|
||||
console.log indent columnify data,
|
||||
showHeaders: false
|
||||
minWidth: 35
|
||||
|
||||
addAlias = (alias) ->
|
||||
return ", #{addOptionPrefix(alias)}"
|
||||
|
||||
buildOptionSignatureHelp = (option) ->
|
||||
result = addOptionPrefix(option.signature.toString())
|
||||
|
||||
if _.isString(option.alias)
|
||||
result += addAlias(option.alias)
|
||||
else if _.isArray(option.alias)
|
||||
for alias in option.alias
|
||||
result += addAlias(alias)
|
||||
|
||||
if option.parameter?
|
||||
result += " <#{option.parameter}>"
|
||||
|
||||
return result
|
||||
|
||||
getCommandHelp = (command) ->
|
||||
maxSignatureLength = getFieldMaxLength(capitano.state.commands, 'signature')
|
||||
commandSignature = _.str.rpad(command.signature.toString(), maxSignatureLength, ' ')
|
||||
return buildHelpString(commandSignature, command.description)
|
||||
|
||||
getOptionsParsedSignatures = (optionsHelp) ->
|
||||
maxLength = _.max _.map optionsHelp, (signature) ->
|
||||
return signature.length
|
||||
|
||||
return _.map optionsHelp, (signature) ->
|
||||
return _.str.rpad(signature, maxLength, ' ')
|
||||
|
||||
getOptionHelp = (option, maxLength) ->
|
||||
result = PADDING_INITIAL
|
||||
result += _.str.rpad(option.signature, maxLength, ' ')
|
||||
result += PADDING_MIDDLE
|
||||
result += option.description
|
||||
return result
|
||||
|
||||
general = ->
|
||||
general = (params, options, done) ->
|
||||
console.log('Usage: resin [COMMAND] [OPTIONS]\n')
|
||||
console.log('Commands:\n')
|
||||
console.log(messages.reachingOut)
|
||||
console.log('\nPrimary commands:\n')
|
||||
|
||||
for command in capitano.state.commands
|
||||
continue if command.isWildcard()
|
||||
console.log(getCommandHelp(command))
|
||||
# We do not want the wildcard command
|
||||
# to be printed in the help screen.
|
||||
commands = _.reject capitano.state.commands, (command) ->
|
||||
return command.hidden or command.isWildcard()
|
||||
|
||||
console.log('\nGlobal Options:\n')
|
||||
groupedCommands = _.groupBy commands, (command) ->
|
||||
if command.primary
|
||||
return 'primary'
|
||||
return 'secondary'
|
||||
|
||||
options = _.map capitano.state.globalOptions, (option) ->
|
||||
option.signature = buildOptionSignatureHelp(option)
|
||||
return option
|
||||
print(parse(groupedCommands.primary))
|
||||
|
||||
optionSignatureMaxLength = _.max _.map options, (option) ->
|
||||
return option.signature.length
|
||||
if options.verbose
|
||||
console.log('\nAdditional commands:\n')
|
||||
print(parse(groupedCommands.secondary))
|
||||
else
|
||||
console.log('\nRun `resin help --verbose` to list additional commands')
|
||||
|
||||
for option in options
|
||||
console.log(getOptionHelp(option, optionSignatureMaxLength))
|
||||
if not _.isEmpty(capitano.state.globalOptions)
|
||||
console.log('\nGlobal Options:\n')
|
||||
print(parse(capitano.state.globalOptions))
|
||||
|
||||
console.log()
|
||||
return done()
|
||||
|
||||
command = (params, options, done) ->
|
||||
capitano.state.getMatchCommand params.command, (error, command) ->
|
||||
return done(error) if error?
|
||||
|
||||
if not command? or command.isWildcard()
|
||||
return capitano.defaults.actions.commandNotFound(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')
|
||||
|
||||
options = _.map command.options, (option) ->
|
||||
option.signature = buildOptionSignatureHelp(option)
|
||||
return option
|
||||
|
||||
optionSignatureMaxLength = _.max _.map options, (option) ->
|
||||
return option.signature.toString().length
|
||||
|
||||
for option in options
|
||||
console.log(getOptionHelp(option, optionSignatureMaxLength))
|
||||
|
||||
console.log()
|
||||
print(parse(command.options))
|
||||
|
||||
return done()
|
||||
|
||||
@ -124,6 +105,13 @@ exports.help =
|
||||
$ resin help apps
|
||||
$ resin help os download
|
||||
'''
|
||||
primary: true
|
||||
options: [
|
||||
signature: 'verbose'
|
||||
description: 'show additional commands'
|
||||
boolean: true
|
||||
alias: 'v'
|
||||
]
|
||||
action: (params, options, done) ->
|
||||
if params.command?
|
||||
command(params, options, done)
|
||||
|
@ -1,16 +1,42 @@
|
||||
###
|
||||
Copyright 2016-2017 Resin.io
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
###
|
||||
|
||||
module.exports =
|
||||
wizard: require('./wizard')
|
||||
apiKey: require('./api-key')
|
||||
app: require('./app')
|
||||
info: require('./info')
|
||||
auth: require('./auth')
|
||||
drive: require('./drive')
|
||||
info: require('./info')
|
||||
device: require('./device')
|
||||
env: require('./environment-variables')
|
||||
keys: require('./keys')
|
||||
logs: require('./logs')
|
||||
local: require('./local')
|
||||
notes: require('./notes')
|
||||
preferences: require('./preferences')
|
||||
os: require('./os')
|
||||
help: require('./help')
|
||||
examples: require('./examples')
|
||||
plugin: require('./plugin')
|
||||
update: require('./update')
|
||||
os: require('./os')
|
||||
settings: require('./settings')
|
||||
config: require('./config')
|
||||
sync: require('./sync')
|
||||
ssh: require('./ssh')
|
||||
internal: require('./internal')
|
||||
build: require('./build')
|
||||
deploy: require('./deploy')
|
||||
util: require('./util')
|
||||
preload: require('./preload')
|
||||
push: require('./push')
|
||||
join: require('./join')
|
||||
leave: require('./leave')
|
||||
|
@ -1,10 +0,0 @@
|
||||
packageJSON = require('../../package.json')
|
||||
|
||||
exports.version =
|
||||
signature: 'version'
|
||||
description: 'output the version number'
|
||||
help: '''
|
||||
Display the Resin CLI version.
|
||||
'''
|
||||
action: ->
|
||||
console.log(packageJSON.version)
|
30
lib/actions/info.ts
Normal file
30
lib/actions/info.ts
Normal file
@ -0,0 +1,30 @@
|
||||
/*
|
||||
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 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,10 +1,19 @@
|
||||
_ = require('lodash')
|
||||
_.str = require('underscore.string')
|
||||
async = require('async')
|
||||
fs = require('fs')
|
||||
resin = require('resin-sdk')
|
||||
capitano = require('capitano')
|
||||
visuals = require('resin-cli-visuals')
|
||||
###
|
||||
Copyright 2016-2017 Resin.io
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
###
|
||||
|
||||
commandOptions = require('./command-options')
|
||||
|
||||
exports.list =
|
||||
@ -19,12 +28,15 @@ exports.list =
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
resin.models.key.getAll (error, keys) ->
|
||||
return done(error) if error?
|
||||
console.log visuals.widgets.table.horizontal keys, [ 'id', 'title' ]
|
||||
return done()
|
||||
resin = require('resin-sdk').fromSharedOptions()
|
||||
visuals = require('resin-cli-visuals')
|
||||
|
||||
SSH_KEY_WIDTH = 43
|
||||
resin.models.key.getAll().then (keys) ->
|
||||
console.log visuals.table.horizontal keys, [
|
||||
'id'
|
||||
'title'
|
||||
]
|
||||
.nodeify(done)
|
||||
|
||||
exports.info =
|
||||
signature: 'key <id>'
|
||||
@ -38,13 +50,20 @@ exports.info =
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
resin.models.key.get params.id, (error, key) ->
|
||||
return done(error) if error?
|
||||
resin = require('resin-sdk').fromSharedOptions()
|
||||
visuals = require('resin-cli-visuals')
|
||||
|
||||
key.public_key = '\n' + visuals.helpers.chop(key.public_key, SSH_KEY_WIDTH)
|
||||
resin.models.key.get(params.id).then (key) ->
|
||||
console.log visuals.table.vertical key, [
|
||||
'id'
|
||||
'title'
|
||||
]
|
||||
|
||||
console.log(visuals.widgets.table.vertical(key, [ 'id', 'title', 'public_key' ]))
|
||||
return done()
|
||||
# Since the public key string is long, it might
|
||||
# wrap to lines below, causing the table layout to break.
|
||||
# See https://github.com/resin-io/resin-cli/issues/151
|
||||
console.log('\n' + key.public_key)
|
||||
.nodeify(done)
|
||||
|
||||
exports.remove =
|
||||
signature: 'key rm <id>'
|
||||
@ -63,9 +82,12 @@ exports.remove =
|
||||
options: [ commandOptions.yes ]
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
visuals.patterns.remove 'key', options.yes, (callback) ->
|
||||
resin.models.key.remove(params.id, callback)
|
||||
, done
|
||||
resin = require('resin-sdk').fromSharedOptions()
|
||||
patterns = require('../utils/patterns')
|
||||
|
||||
patterns.confirm(options.yes, 'Are you sure you want to delete the key?').then ->
|
||||
resin.models.key.remove(params.id)
|
||||
.nodeify(done)
|
||||
|
||||
exports.add =
|
||||
signature: 'key add <name> [path]'
|
||||
@ -83,16 +105,19 @@ exports.add =
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
async.waterfall [
|
||||
_ = require('lodash')
|
||||
Promise = require('bluebird')
|
||||
readFileAsync = Promise.promisify(require('fs').readFile)
|
||||
capitano = require('capitano')
|
||||
resin = require('resin-sdk').fromSharedOptions()
|
||||
|
||||
(callback) ->
|
||||
if params.path?
|
||||
fs.readFile(params.path, encoding: 'utf8', callback)
|
||||
else
|
||||
capitano.utils.getStdin (data) ->
|
||||
return callback(null, data)
|
||||
Promise.try ->
|
||||
return readFileAsync(params.path, encoding: 'utf8') if params.path?
|
||||
|
||||
(key, callback) ->
|
||||
resin.models.key.create(params.name, key, callback)
|
||||
# TODO: should this be promisified for consistency?
|
||||
Promise.fromNode (callback) ->
|
||||
capitano.utils.getStdin (data) ->
|
||||
return callback(null, data)
|
||||
|
||||
], done
|
||||
.then(_.partial(resin.models.key.create, params.name))
|
||||
.nodeify(done)
|
||||
|
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)
|
23
lib/actions/local/index.coffee
Normal file
23
lib/actions/local/index.coffee
Normal file
@ -0,0 +1,23 @@
|
||||
###
|
||||
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.
|
||||
###
|
||||
|
||||
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,9 +1,22 @@
|
||||
_ = require('lodash')
|
||||
resin = require('resin-sdk')
|
||||
###
|
||||
Copyright 2016-2017 Resin.io
|
||||
|
||||
LOGS_HISTORY_COUNT = 200
|
||||
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
|
||||
|
||||
exports.logs =
|
||||
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.
|
||||
###
|
||||
|
||||
{ normalizeUuidProp } = require('../utils/normalization')
|
||||
|
||||
module.exports =
|
||||
signature: 'logs <uuid>'
|
||||
description: 'show device logs'
|
||||
help: '''
|
||||
@ -11,29 +24,14 @@ exports.logs =
|
||||
|
||||
By default, the command prints all log messages and exit.
|
||||
|
||||
To limit the output to the n last lines, use the `--num` option along with a number.
|
||||
This is similar to doing `resin logs <uuid> | tail -n X`.
|
||||
|
||||
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,
|
||||
and the tool won't notice if you're using an invalid UUID.
|
||||
|
||||
This is due to some technical limitations that we plan to address soon.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin logs 23c73a12e3527df55c60b9ce647640c1b7da1b32d71e6a39849ac0f00db828
|
||||
$ resin logs 23c73a12e3527df55c60b9ce647640c1b7da1b32d71e6a39849ac0f00db828 --num 20
|
||||
$ resin logs 23c73a12e3527df55c60b9ce647640c1b7da1b32d71e6a39849ac0f00db828 --tail
|
||||
$ resin logs 23c73a1
|
||||
$ resin logs 23c73a1
|
||||
'''
|
||||
options: [
|
||||
{
|
||||
signature: 'num'
|
||||
parameter: 'num'
|
||||
description: 'number of lines to display'
|
||||
alias: 'n'
|
||||
}
|
||||
{
|
||||
signature: 'tail'
|
||||
description: 'continuously stream output'
|
||||
@ -42,15 +40,22 @@ exports.logs =
|
||||
}
|
||||
]
|
||||
permission: 'user'
|
||||
primary: true
|
||||
action: (params, options, done) ->
|
||||
resin.logs.subscribe params.uuid, {
|
||||
history: options.num or LOGS_HISTORY_COUNT
|
||||
tail: options.tail
|
||||
}, (error, message) ->
|
||||
return done(error) if error?
|
||||
if _.isArray(message)
|
||||
_.each message, (line) ->
|
||||
console.log(line)
|
||||
else
|
||||
console.log(message)
|
||||
return done()
|
||||
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}")
|
||||
|
||||
if options.tail
|
||||
resin.logs.subscribe(params.uuid, { count: 100 }).then (logs) ->
|
||||
logs.on('line', printLine)
|
||||
logs.on('error', done)
|
||||
.catch(done)
|
||||
else
|
||||
resin.logs.history(params.uuid)
|
||||
.each(printLine)
|
||||
.catch(done)
|
||||
|
@ -1,5 +1,20 @@
|
||||
async = require('async')
|
||||
resin = require('resin-sdk')
|
||||
###
|
||||
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.
|
||||
###
|
||||
|
||||
{ normalizeUuidProp } = require('../utils/normalization')
|
||||
|
||||
exports.set =
|
||||
signature: 'note <|note>'
|
||||
@ -9,20 +24,32 @@ exports.set =
|
||||
|
||||
If note command isn't passed, the tool attempts to read from `stdin`.
|
||||
|
||||
To view the notes, use $ resin device <name>.
|
||||
To view the notes, use $ resin device <uuid>.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin note "My useful note" --device MyDevice
|
||||
$ cat note.txt | resin note --device MyDevice
|
||||
$ resin note "My useful note" --device 7cf02a6
|
||||
$ cat note.txt | resin note --device 7cf02a6
|
||||
'''
|
||||
options: [
|
||||
signature: 'device'
|
||||
parameter: 'device'
|
||||
description: 'device name'
|
||||
description: 'device uuid'
|
||||
alias: [ 'd', 'dev' ]
|
||||
required: 'You have to specify a device'
|
||||
]
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
resin.models.device.note(options.device, params.note, done)
|
||||
normalizeUuidProp(options, 'device')
|
||||
Promise = require('bluebird')
|
||||
_ = require('lodash')
|
||||
resin = require('resin-sdk').fromSharedOptions()
|
||||
|
||||
{ exitWithExpectedError } = require('../utils/patterns')
|
||||
|
||||
Promise.try ->
|
||||
if _.isEmpty(params.note)
|
||||
exitWithExpectedError('Missing note content')
|
||||
|
||||
resin.models.device.note(options.device, params.note)
|
||||
.nodeify(done)
|
||||
|
@ -1,182 +1,382 @@
|
||||
_ = require('lodash-contrib')
|
||||
os = require('os')
|
||||
async = require('async')
|
||||
path = require('path')
|
||||
mkdirp = require('mkdirp')
|
||||
resin = require('resin-sdk')
|
||||
visuals = require('resin-cli-visuals')
|
||||
###
|
||||
Copyright 2016-2017 Resin.io
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
###
|
||||
|
||||
commandOptions = require('./command-options')
|
||||
npm = require('../npm')
|
||||
packageJSON = require('../../package.json')
|
||||
updateActions = require('./update')
|
||||
elevate = require('../elevate')
|
||||
_ = 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 <id>'
|
||||
description: 'download device OS'
|
||||
signature: 'os download <type>'
|
||||
description: 'download an unconfigured os image'
|
||||
help: '''
|
||||
Use this command to download the device OS configured to a specific network.
|
||||
Use this command to download an unconfigured os image for a certain device type.
|
||||
Check available types with `resin devices supported`
|
||||
|
||||
Ethernet:
|
||||
You can setup the device OS to use ethernet by setting the `--network` option to "ethernet".
|
||||
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).
|
||||
|
||||
Wifi:
|
||||
You can setup the device OS to use wifi by setting the `--network` option to "wifi".
|
||||
If you set "network" to "wifi", you will need to specify the `--ssid` and `--key` option as well.
|
||||
|
||||
Alternatively, you can omit all kind of network configuration options to configure interactively.
|
||||
|
||||
You have to specify an output location with the `--output` option.
|
||||
You can pass `--version menu` to pick the OS version from the interactive menu
|
||||
of all available versions.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin os download 91 --output ~/MyResinOS.zip
|
||||
$ resin os download 91 --network ethernet --output ~/MyResinOS.zip
|
||||
$ resin os download 91 --network wifi --ssid MyNetwork --key secreykey123 --output ~/MyResinOS.zip
|
||||
$ resin os download 91 --network ethernet --output ~/MyResinOS.zip
|
||||
$ 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: [
|
||||
commandOptions.network
|
||||
commandOptions.wifiSsid
|
||||
commandOptions.wifiKey
|
||||
|
||||
{
|
||||
signature: 'output'
|
||||
description: 'output path'
|
||||
parameter: 'output'
|
||||
description: 'output file'
|
||||
alias: 'o'
|
||||
required: 'You need to specify an output file'
|
||||
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')
|
||||
manager = require('resin-image-manager')
|
||||
visuals = require('resin-cli-visuals')
|
||||
|
||||
console.info("Getting device operating system for #{params.type}")
|
||||
|
||||
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?
|
||||
bar.update(state)
|
||||
else
|
||||
spinner.start()
|
||||
|
||||
stream.on 'end', ->
|
||||
spinner.stop()
|
||||
|
||||
# We completely rely on the `mime` custom property
|
||||
# to make this decision.
|
||||
# The actual stream should be checked instead.
|
||||
if stream.mime is 'application/zip'
|
||||
output = unzip.Extract(path: options.output)
|
||||
else
|
||||
output = fs.createWriteStream(options.output)
|
||||
|
||||
return rindle.wait(stream.pipe(output)).return(options.output)
|
||||
.tap (output) ->
|
||||
console.info('The image was downloaded successfully')
|
||||
.nodeify(done)
|
||||
|
||||
buildConfig = (image, deviceType, advanced = false) ->
|
||||
form = require('resin-cli-form')
|
||||
helpers = require('../utils/helpers')
|
||||
|
||||
helpers.getManifest(image, deviceType)
|
||||
.get('options')
|
||||
.then (questions) ->
|
||||
if not advanced
|
||||
advancedGroup = _.find questions,
|
||||
name: 'advanced'
|
||||
isGroup: true
|
||||
|
||||
if advancedGroup?
|
||||
override = helpers.getGroupDefaults(advancedGroup)
|
||||
|
||||
return form.run(questions, { override })
|
||||
|
||||
exports.buildConfig =
|
||||
signature: 'os build-config <image> <device-type>'
|
||||
description: 'build the OS config and save it to the JSON file'
|
||||
help: '''
|
||||
Use this command to prebuild the OS config once and skip the interactive part of `resin os configure`.
|
||||
|
||||
Example:
|
||||
|
||||
$ resin os build-config ../path/rpi3.img raspberrypi3 --output rpi3-config.json
|
||||
$ resin os configure ../path/rpi3.img 7cf02a6 --config "$(cat rpi3-config.json)"
|
||||
'''
|
||||
permission: 'user'
|
||||
options: [
|
||||
commandOptions.advancedConfig
|
||||
{
|
||||
signature: 'output'
|
||||
description: 'the path to the output JSON file'
|
||||
alias: 'o'
|
||||
required: 'the output path is required'
|
||||
parameter: 'output'
|
||||
}
|
||||
]
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
osParams =
|
||||
network: options.network
|
||||
wifiSsid: options.ssid
|
||||
wifiKey: options.key
|
||||
appId: params.id
|
||||
fs = require('fs')
|
||||
Promise = require('bluebird')
|
||||
writeFileAsync = Promise.promisify(fs.writeFile)
|
||||
|
||||
async.waterfall [
|
||||
buildConfig(params.image, params['device-type'], options.advanced)
|
||||
.then (answers) ->
|
||||
writeFileAsync(options.output, JSON.stringify(answers, null, 4))
|
||||
.nodeify(done)
|
||||
|
||||
(callback) ->
|
||||
return callback() if osParams.network?
|
||||
visuals.patterns.selectNetworkParameters (error, parameters) ->
|
||||
return callback(error) if error?
|
||||
_.extend(osParams, parameters)
|
||||
return callback()
|
||||
|
||||
(callback) ->
|
||||
|
||||
# We need to ensure this directory exists
|
||||
mkdirp(path.dirname(options.output), _.unary(callback))
|
||||
|
||||
(callback) ->
|
||||
console.info("Destination file: #{options.output}\n")
|
||||
|
||||
bar = new visuals.widgets.Progress('Downloading Device OS')
|
||||
spinner = new visuals.widgets.Spinner('Downloading Device OS (size unknown)')
|
||||
|
||||
resin.models.os.download osParams, options.output, (error) ->
|
||||
spinner.stop()
|
||||
return callback(error) if error?
|
||||
, (state) ->
|
||||
if state?
|
||||
bar.update(state)
|
||||
else
|
||||
spinner.start()
|
||||
|
||||
], (error) ->
|
||||
return done(error) if error?
|
||||
console.info("\nFinished downloading #{options.output}")
|
||||
return done(null, options.output)
|
||||
|
||||
exports.install =
|
||||
signature: 'os install <image> [device]'
|
||||
description: 'write an operating system image to a device'
|
||||
exports.configure =
|
||||
signature: 'os configure <image> [uuid] [deviceApiKey]'
|
||||
description: 'configure an os image'
|
||||
help: '''
|
||||
Use this command to write an operating system image to a device.
|
||||
Use this command to configure a previously downloaded operating system image for
|
||||
the specific device or for an application generally.
|
||||
|
||||
Note that this command requires admin privileges.
|
||||
Calling this command without --version is not recommended, and may fail in
|
||||
future releases if the OS version cannot be inferred.
|
||||
|
||||
If `device` is omitted, you will be prompted to select a device interactively.
|
||||
Note that device api keys are only supported on ResinOS 2.0.3+.
|
||||
|
||||
Notice this command asks for confirmation interactively.
|
||||
You can avoid this by passing the `--yes` boolean option.
|
||||
|
||||
You can quiet the progress bar by passing the `--quiet` boolean option.
|
||||
|
||||
You may have to unmount the device before attempting this operation.
|
||||
|
||||
See the `drives` command to get a list of all connected devices to your machine and their respective ids.
|
||||
|
||||
In Mac OS X:
|
||||
|
||||
$ sudo diskutil unmountDisk /dev/xxx
|
||||
|
||||
In GNU/Linux:
|
||||
|
||||
$ sudo umount /dev/xxx
|
||||
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 install rpi.iso /dev/disk2
|
||||
$ 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
|
||||
'''
|
||||
options: [ commandOptions.yes ]
|
||||
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')
|
||||
|
||||
bundle = require('../devices/raspberry-pi')
|
||||
if _.filter([
|
||||
options.device
|
||||
options.application
|
||||
params.uuid
|
||||
]).length != 1
|
||||
patterns.exitWithExpectedError '''
|
||||
To configure an image, you must provide exactly one of:
|
||||
|
||||
async.waterfall [
|
||||
* A device, with --device <uuid>
|
||||
* An application, with --app <appname>
|
||||
* [Deprecated] A device, passing its uuid directly on the command line
|
||||
|
||||
(callback) ->
|
||||
npm.isUpdated(packageJSON.name, packageJSON.version, callback)
|
||||
See the help page for examples:
|
||||
|
||||
(isUpdated, callback) ->
|
||||
return callback() if isUpdated
|
||||
$ 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'
|
||||
)
|
||||
|
||||
console.info '''
|
||||
Resin CLI is outdated.
|
||||
uuid = options.device || params.uuid
|
||||
deviceApiKey = options.deviceApiKey || params.deviceApiKey
|
||||
|
||||
In order to avoid device compatibility issues, this command
|
||||
requires that you have the Resin CLI updated.
|
||||
console.info('Configuring operating system image')
|
||||
|
||||
Updating now...
|
||||
'''
|
||||
configurationResourceType = if uuid then 'device' else 'application'
|
||||
|
||||
updateActions.update.action(params, options, _.unary(callback))
|
||||
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) ->
|
||||
answers.version = options.version
|
||||
|
||||
(callback) ->
|
||||
return callback(null, params.device) if params.device?
|
||||
(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)
|
||||
|
||||
# TODO: See if we can reuse the drives action somehow here
|
||||
visuals.patterns.selectDrive (error, device) ->
|
||||
return callback(error) if error?
|
||||
INIT_WARNING_MESSAGE = '''
|
||||
Note: Initializing the device may ask for administrative permissions
|
||||
because we need to access the raw devices directly.
|
||||
'''
|
||||
|
||||
if not device?
|
||||
return callback(new Error('No removable devices available'))
|
||||
exports.initialize =
|
||||
signature: 'os initialize <image>'
|
||||
description: 'initialize an os image'
|
||||
help: """
|
||||
Use this command to initialize a device with previously configured operating system image.
|
||||
|
||||
return callback(null, device)
|
||||
#{INIT_WARNING_MESSAGE}
|
||||
|
||||
(device, callback) ->
|
||||
params.device = device
|
||||
message = "This will completely erase #{params.device}. Are you sure you want to continue?"
|
||||
visuals.patterns.confirm(options.yes, message, callback)
|
||||
Examples:
|
||||
|
||||
(confirmed, callback) ->
|
||||
return done() if not confirmed
|
||||
$ resin os initialize ../path/rpi.img --type 'raspberry-pi'
|
||||
"""
|
||||
permission: 'user'
|
||||
options: [
|
||||
commandOptions.yes
|
||||
{
|
||||
signature: 'type'
|
||||
description: 'device type (Check available types with `resin devices supported`)'
|
||||
parameter: 'type'
|
||||
alias: 't'
|
||||
required: 'You have to specify a device type'
|
||||
}
|
||||
commandOptions.drive
|
||||
]
|
||||
action: (params, options, done) ->
|
||||
Promise = require('bluebird')
|
||||
umountAsync = Promise.promisify(require('umount').umount)
|
||||
form = require('resin-cli-form')
|
||||
patterns = require('../utils/patterns')
|
||||
helpers = require('../utils/helpers')
|
||||
|
||||
bar = new visuals.widgets.Progress('Writing Device OS')
|
||||
params.progress = _.bind(bar.update, bar)
|
||||
bundle.write(params, callback)
|
||||
console.info("""
|
||||
Initializing device
|
||||
|
||||
], (error) ->
|
||||
return done() if not error?
|
||||
#{INIT_WARNING_MESSAGE}
|
||||
""")
|
||||
helpers.getManifest(params.image, options.type)
|
||||
.then (manifest) ->
|
||||
return manifest.initialization?.options
|
||||
.then (questions) ->
|
||||
return form.run questions,
|
||||
override:
|
||||
drive: options.drive
|
||||
.tap (answers) ->
|
||||
return if not answers.drive?
|
||||
patterns.confirm(
|
||||
options.yes
|
||||
"This will erase #{answers.drive}. Are you sure?"
|
||||
"Going to erase #{answers.drive}."
|
||||
)
|
||||
.return(answers.drive)
|
||||
.then(umountAsync)
|
||||
.tap (answers) ->
|
||||
return helpers.sudo([
|
||||
'internal'
|
||||
'osinit'
|
||||
params.image
|
||||
options.type
|
||||
JSON.stringify(answers)
|
||||
])
|
||||
.then (answers) ->
|
||||
return if not answers.drive?
|
||||
|
||||
if elevate.shouldElevate(error) and not options.fromScript
|
||||
# TODO: resin local makes use of ejectAsync, see below
|
||||
# DO we need this / should we do that here?
|
||||
|
||||
# Need to escape every path to avoid errors
|
||||
resinWritePath = "\"#{path.join(__dirname, '..', '..', 'bin', 'resin-write')}\""
|
||||
elevate.run("\"#{process.argv[0]}\" #{resinWritePath} \"#{params.image}\" \"#{params.device}\"")
|
||||
else
|
||||
return done(error)
|
||||
# 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)
|
||||
|
@ -1,94 +0,0 @@
|
||||
_ = require('lodash')
|
||||
visuals = require('resin-cli-visuals')
|
||||
commandOptions = require('./command-options')
|
||||
plugins = require('../plugins')
|
||||
|
||||
exports.list =
|
||||
signature: 'plugins'
|
||||
description: 'list all plugins'
|
||||
help: '''
|
||||
Use this command to list all the installed resin plugins.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin plugins
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
plugins.list (error, resinPlugins) ->
|
||||
return done(error) if error?
|
||||
|
||||
if _.isEmpty(resinPlugins)
|
||||
console.log('You don\'t have any plugins yet')
|
||||
return done()
|
||||
|
||||
console.log visuals.widgets.table.horizontal resinPlugins, [
|
||||
'name'
|
||||
'version'
|
||||
'description'
|
||||
'license'
|
||||
]
|
||||
|
||||
return done()
|
||||
|
||||
exports.install =
|
||||
signature: 'plugin install <name>'
|
||||
description: 'install a plugin'
|
||||
help: '''
|
||||
Use this command to install a resin plugin
|
||||
|
||||
Use `--quiet` to prevent information logging.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin plugin install hello
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
plugins.install params.name, (error) ->
|
||||
return done(error) if error?
|
||||
console.info("Plugin installed: #{params.name}")
|
||||
return done()
|
||||
|
||||
exports.update =
|
||||
signature: 'plugin update <name>'
|
||||
description: 'update a plugin'
|
||||
help: '''
|
||||
Use this command to update a resin plugin
|
||||
|
||||
Use `--quiet` to prevent information logging.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin plugin update hello
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
plugins.update params.name, (error, version) ->
|
||||
return done(error) if error?
|
||||
console.info("Plugin updated: #{params.name}@#{version}")
|
||||
return done()
|
||||
|
||||
exports.remove =
|
||||
signature: 'plugin rm <name>'
|
||||
description: 'remove a plugin'
|
||||
help: '''
|
||||
Use this command to remove a resin.io plugin.
|
||||
|
||||
Notice this command asks for confirmation interactively.
|
||||
You can avoid this by passing the `--yes` boolean option.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin plugin rm hello
|
||||
$ resin plugin rm hello --yes
|
||||
'''
|
||||
options: [ commandOptions.yes ]
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
visuals.patterns.remove 'plugin', options.yes, (callback) ->
|
||||
plugins.remove(params.name, callback)
|
||||
, (error) ->
|
||||
return done(error) if error?
|
||||
console.info("Plugin removed: #{params.name}")
|
||||
return done()
|
@ -1,21 +0,0 @@
|
||||
open = require('open')
|
||||
url = require('url')
|
||||
settings = require('resin-settings-client')
|
||||
|
||||
exports.preferences =
|
||||
signature: 'preferences'
|
||||
description: 'open preferences form'
|
||||
help: '''
|
||||
Use this command to open the preferences form.
|
||||
|
||||
In the future, we will allow changing all preferences directly from the terminal.
|
||||
For now, we open your default web browser and point it to the web based preferences form.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin preferences
|
||||
'''
|
||||
permission: 'user'
|
||||
action: ->
|
||||
absUrl = url.resolve(settings.get('remoteUrl'), '/preferences')
|
||||
open(absUrl)
|
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);
|
||||
},
|
||||
};
|
141
lib/actions/ssh.coffee
Normal file
141
lib/actions/ssh.coffee
Normal file
@ -0,0 +1,141 @@
|
||||
###
|
||||
Copyright 2016-2017 Resin.io
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
###
|
||||
|
||||
commandOptions = require('./command-options')
|
||||
{ normalizeUuidProp } = require('../utils/normalization')
|
||||
|
||||
module.exports =
|
||||
signature: 'ssh [uuid]'
|
||||
description: '(beta) get a shell into the running app container of a device'
|
||||
help: '''
|
||||
Warning: 'resin ssh' requires an openssh-compatible client to be correctly
|
||||
installed in your shell environment. For more information (including Windows
|
||||
support) please check the README here: https://github.com/resin-io/resin-cli
|
||||
|
||||
Use this command to get a shell into the running application container of
|
||||
your device.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin ssh MyApp
|
||||
$ resin ssh 7cf02a6
|
||||
$ resin ssh 7cf02a6 --port 8080
|
||||
$ resin ssh 7cf02a6 -v
|
||||
$ resin ssh 7cf02a6 -s
|
||||
'''
|
||||
permission: 'user'
|
||||
primary: true
|
||||
options: [
|
||||
signature: 'port'
|
||||
parameter: 'port'
|
||||
description: 'ssh gateway port'
|
||||
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-preconfigured')
|
||||
_ = require('lodash')
|
||||
bash = require('bash')
|
||||
hasbin = require('hasbin')
|
||||
{ getSubShellCommand } = require('../utils/helpers')
|
||||
patterns = require('../utils/patterns')
|
||||
|
||||
options.port ?= 22
|
||||
|
||||
verbose = if options.verbose then '-vvv' else ''
|
||||
|
||||
proxyConfig = global.PROXY_CONFIG
|
||||
useProxy = !!proxyConfig and not options.noproxy
|
||||
|
||||
getSshProxyCommand = (hasTunnelBin) ->
|
||||
return '' if not useProxy
|
||||
|
||||
if not hasTunnelBin
|
||||
console.warn('''
|
||||
Proxy is enabled but the `proxytunnel` binary cannot be found.
|
||||
Please install it if you want to route the `resin ssh` requests through the proxy.
|
||||
Alternatively you can pass `--noproxy` param to the `resin ssh` command to ignore the proxy config
|
||||
for the `ssh` requests.
|
||||
|
||||
Attemmpting the unproxied request for now.
|
||||
''')
|
||||
return ''
|
||||
|
||||
tunnelOptions =
|
||||
proxy: "#{proxyConfig.host}:#{proxyConfig.port}"
|
||||
dest: '%h:%p'
|
||||
{ proxyAuth } = proxyConfig
|
||||
if proxyAuth
|
||||
i = proxyAuth.indexOf(':')
|
||||
_.assign tunnelOptions,
|
||||
user: proxyAuth.substring(0, i)
|
||||
pass: proxyAuth.substring(i + 1)
|
||||
proxyCommand = "proxytunnel #{bash.args(tunnelOptions, '--', '=')}"
|
||||
return "-o #{bash.args({ ProxyCommand: proxyCommand }, '', '=')}"
|
||||
|
||||
Promise.try ->
|
||||
return false if not params.uuid
|
||||
return resin.models.device.has(params.uuid)
|
||||
.then (uuidExists) ->
|
||||
return params.uuid if uuidExists
|
||||
return patterns.inferOrSelectDevice()
|
||||
.then (uuid) ->
|
||||
console.info("Connecting to: #{uuid}")
|
||||
resin.models.device.get(uuid)
|
||||
.then (device) ->
|
||||
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)
|
18
lib/actions/sync.ts
Normal file
18
lib/actions/sync.ts
Normal file
@ -0,0 +1,18 @@
|
||||
/*
|
||||
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 ResinSync from 'resin-sync';
|
||||
export = ResinSync.capitano('resin-cli');
|
@ -1,58 +0,0 @@
|
||||
_ = require('lodash')
|
||||
child_process = require('child_process')
|
||||
president = require('president')
|
||||
npm = require('../npm')
|
||||
packageJSON = require('../../package.json')
|
||||
|
||||
exports.update =
|
||||
signature: 'update'
|
||||
description: 'update the resin cli'
|
||||
help: '''
|
||||
Use this command to update the Resin CLI
|
||||
|
||||
This command outputs information about the update process.
|
||||
Use `--quiet` to remove that output.
|
||||
|
||||
The Resin CLI checks for updates once per day.
|
||||
|
||||
Major updates require a manual update with this update command,
|
||||
while minor updates are applied automatically.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin update
|
||||
'''
|
||||
action: (params, options, done) ->
|
||||
npm.isUpdated packageJSON.name, packageJSON.version, (error, isUpdated) ->
|
||||
return done(error) if error?
|
||||
|
||||
if isUpdated
|
||||
return done(new Error('You\'re already running the latest version.'))
|
||||
|
||||
onUpdate = (error, stdout, stderr) ->
|
||||
return done(error) if error?
|
||||
return done(new Error(stderr)) if not _.isEmpty(stderr)
|
||||
console.info("Upgraded #{packageJSON.name}.")
|
||||
return done()
|
||||
|
||||
command = "npm install --global #{packageJSON.name}"
|
||||
|
||||
# Attempting to self update using the NPM API was not considered safe.
|
||||
# A safer thing to do is to call npm as a child process
|
||||
# https://github.com/npm/npm/issues/7723
|
||||
child_process.exec command, (error, stdout, stderr) ->
|
||||
return onUpdate(null, stdout, stderr) if error?
|
||||
|
||||
if _.any [
|
||||
|
||||
# 3 is equivalent to EACCES for NPM
|
||||
error.code is 3
|
||||
|
||||
error.code is 'EPERM'
|
||||
error.code is 'ACCES'
|
||||
]
|
||||
return president.execute(command, onUpdate)
|
||||
|
||||
return done(error)
|
||||
|
||||
|
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'
|
||||
]
|
74
lib/actions/wizard.coffee
Normal file
74
lib/actions/wizard.coffee
Normal file
@ -0,0 +1,74 @@
|
||||
###
|
||||
Copyright 2016-2017 Resin.io
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
###
|
||||
|
||||
exports.wizard =
|
||||
signature: 'quickstart [name]'
|
||||
description: 'getting started with resin.io'
|
||||
help: '''
|
||||
Use this command to run a friendly wizard to get started with resin.io.
|
||||
|
||||
The wizard will guide you through:
|
||||
|
||||
- Create an application.
|
||||
- Initialise an SDCard with the resin.io operating system.
|
||||
- Associate an existing project directory with your resin.io application.
|
||||
- Push your project to your devices.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin quickstart
|
||||
$ resin quickstart MyApp
|
||||
'''
|
||||
primary: true
|
||||
action: (params, options, done) ->
|
||||
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("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
|
||||
runCommand("app create #{applicationName}")
|
||||
.then (applicationName) ->
|
||||
params.name = applicationName
|
||||
.then ->
|
||||
return runCommand("device init --application #{params.name}")
|
||||
.tap(patterns.awaitDevice)
|
||||
.then (uuid) ->
|
||||
return runCommand("device #{uuid}")
|
||||
.then ->
|
||||
return resin.models.application.get(params.name)
|
||||
.then (application) ->
|
||||
console.log """
|
||||
Your device is ready to start pushing some code!
|
||||
|
||||
Check our official documentation for more information:
|
||||
|
||||
http://docs.resin.io/#/pages/introduction/introduction.md
|
||||
|
||||
Clone an example or go to an existing application directory and run:
|
||||
|
||||
$ git remote add resin #{application.git_repository}
|
||||
$ git push resin master
|
||||
"""
|
||||
.nodeify(done)
|
245
lib/app.coffee
245
lib/app.coffee
@ -1,43 +1,111 @@
|
||||
_ = require('lodash')
|
||||
async = require('async')
|
||||
###
|
||||
Copyright 2016-2017 Resin.io
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
###
|
||||
|
||||
Raven = require('raven')
|
||||
Raven.disableConsoleAlerts()
|
||||
Raven.config require('./config').sentryDsn,
|
||||
captureUnhandledRejections: true,
|
||||
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 = require('capitano')
|
||||
resin = require('resin-sdk')
|
||||
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')
|
||||
plugins = require('./plugins')
|
||||
update = require('./update')
|
||||
events = require('./events')
|
||||
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 (isLoggedIn) ->
|
||||
resin.auth.isLoggedIn().then (isLoggedIn) ->
|
||||
if not isLoggedIn
|
||||
return done(new Error('You have to log in'))
|
||||
return done()
|
||||
exitWithExpectedError('''
|
||||
You have to log in to continue
|
||||
|
||||
Run the following command to go through the login wizard:
|
||||
|
||||
$ resin login
|
||||
''')
|
||||
.nodeify(done)
|
||||
|
||||
capitano.command
|
||||
signature: '*'
|
||||
action: ->
|
||||
capitano.execute(command: 'help')
|
||||
|
||||
# ---------- Options ----------
|
||||
capitano.globalOption
|
||||
signature: 'quiet'
|
||||
description: 'quiet (no output)'
|
||||
boolean: true
|
||||
alias: 'q'
|
||||
|
||||
capitano.globalOption
|
||||
signature: 'project'
|
||||
parameter: 'path'
|
||||
description: 'project path'
|
||||
alias: 'j'
|
||||
|
||||
# We don't do anything in response to this options
|
||||
# explicitly. We use InquirerJS to provide CLI widgets,
|
||||
# and that module understands --no-color automatically.
|
||||
capitano.globalOption
|
||||
signature: 'no-color'
|
||||
description: 'disable colour highlighting'
|
||||
signature: 'help'
|
||||
boolean: true
|
||||
alias: 'h'
|
||||
|
||||
# ---------- Info Module ----------
|
||||
capitano.command(actions.info.version)
|
||||
@ -45,39 +113,45 @@ capitano.command(actions.info.version)
|
||||
# ---------- Help Module ----------
|
||||
capitano.command(actions.help.help)
|
||||
|
||||
# ---------- Wizard Module ----------
|
||||
capitano.command(actions.wizard.wizard)
|
||||
|
||||
# ---------- Api key module ----------
|
||||
capitano.command(actions.apiKey.generate)
|
||||
|
||||
# ---------- App Module ----------
|
||||
capitano.command(actions.app.create)
|
||||
capitano.command(actions.app.list)
|
||||
capitano.command(actions.app.remove)
|
||||
capitano.command(actions.app.restart)
|
||||
capitano.command(actions.app.info)
|
||||
|
||||
# ---------- Auth Module ----------
|
||||
capitano.command(actions.auth.login)
|
||||
capitano.command(actions.auth.logout)
|
||||
capitano.command(actions.auth.signup)
|
||||
capitano.command(actions.auth.whoami)
|
||||
|
||||
# ---------- App Module ----------
|
||||
capitano.command(actions.app.create)
|
||||
capitano.command(actions.app.list)
|
||||
capitano.command(actions.app.info)
|
||||
capitano.command(actions.app.remove)
|
||||
capitano.command(actions.app.restart)
|
||||
capitano.command(actions.app.associate)
|
||||
capitano.command(actions.app.init)
|
||||
|
||||
# ---------- 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.info)
|
||||
capitano.command(actions.device.remove)
|
||||
capitano.command(actions.device.identify)
|
||||
|
||||
# ---------- Drive Module ----------
|
||||
capitano.command(actions.drive.list)
|
||||
capitano.command(actions.device.reboot)
|
||||
capitano.command(actions.device.shutdown)
|
||||
capitano.command(actions.device.enableDeviceUrl)
|
||||
capitano.command(actions.device.disableDeviceUrl)
|
||||
capitano.command(actions.device.getDeviceUrl)
|
||||
capitano.command(actions.device.hasDeviceUrl)
|
||||
capitano.command(actions.device.register)
|
||||
capitano.command(actions.device.move)
|
||||
capitano.command(actions.device.info)
|
||||
|
||||
# ---------- Notes Module ----------
|
||||
capitano.command(actions.notes.set)
|
||||
|
||||
# ---------- Preferences Module ----------
|
||||
capitano.command(actions.preferences.preferences)
|
||||
|
||||
# ---------- Keys Module ----------
|
||||
capitano.command(actions.keys.list)
|
||||
capitano.command(actions.keys.add)
|
||||
@ -90,50 +164,71 @@ capitano.command(actions.env.add)
|
||||
capitano.command(actions.env.rename)
|
||||
capitano.command(actions.env.remove)
|
||||
|
||||
# ---------- Logs Module ----------
|
||||
capitano.command(actions.logs.logs)
|
||||
|
||||
# ---------- OS Module ----------
|
||||
capitano.command(actions.os.versions)
|
||||
capitano.command(actions.os.download)
|
||||
capitano.command(actions.os.install)
|
||||
capitano.command(actions.os.buildConfig)
|
||||
capitano.command(actions.os.configure)
|
||||
capitano.command(actions.os.initialize)
|
||||
|
||||
# ---------- Examples Module ----------
|
||||
capitano.command(actions.examples.list)
|
||||
capitano.command(actions.examples.clone)
|
||||
capitano.command(actions.examples.info)
|
||||
# ---------- Config Module ----------
|
||||
capitano.command(actions.config.read)
|
||||
capitano.command(actions.config.write)
|
||||
capitano.command(actions.config.inject)
|
||||
capitano.command(actions.config.reconfigure)
|
||||
capitano.command(actions.config.generate)
|
||||
|
||||
# ---------- Plugins Module ----------
|
||||
capitano.command(actions.plugin.list)
|
||||
capitano.command(actions.plugin.install)
|
||||
capitano.command(actions.plugin.update)
|
||||
capitano.command(actions.plugin.remove)
|
||||
# ---------- Settings Module ----------
|
||||
capitano.command(actions.settings.list)
|
||||
|
||||
# ---------- Update Module ----------
|
||||
capitano.command(actions.update.update)
|
||||
# ---------- Logs Module ----------
|
||||
capitano.command(actions.logs)
|
||||
|
||||
changeProjectDirectory = (directory) ->
|
||||
try
|
||||
process.chdir(directory)
|
||||
catch
|
||||
errors.handle(new Error("Invalid project: #{directory}"))
|
||||
# ---------- Sync Module ----------
|
||||
capitano.command(actions.sync)
|
||||
|
||||
async.waterfall([
|
||||
# ---------- Preload Module ----------
|
||||
capitano.command(actions.preload)
|
||||
|
||||
(callback) ->
|
||||
update.check(callback)
|
||||
# ---------- SSH Module ----------
|
||||
capitano.command(actions.ssh)
|
||||
|
||||
(callback) ->
|
||||
plugins.register('resin-plugin-', callback)
|
||||
# ---------- 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)
|
||||
|
||||
(callback) ->
|
||||
cli = capitano.parse(process.argv)
|
||||
# ---------- Public utils ----------
|
||||
capitano.command(actions.util.availableDrives)
|
||||
|
||||
if cli.global.quiet
|
||||
console.info = _.noop
|
||||
# ---------- Internal utils ----------
|
||||
capitano.command(actions.internal.osInit)
|
||||
capitano.command(actions.internal.scanDevices)
|
||||
capitano.command(actions.internal.sudo)
|
||||
|
||||
if cli.global.project?
|
||||
changeProjectDirectory(cli.global.project)
|
||||
#------------ Local build and deploy -------
|
||||
capitano.command(actions.build)
|
||||
capitano.command(actions.deploy)
|
||||
|
||||
capitano.execute(cli, callback)
|
||||
#------------ Push/remote builds -------
|
||||
capitano.command(actions.push.push)
|
||||
|
||||
], errors.handle)
|
||||
#------------ Join/Leave -------
|
||||
capitano.command(actions.join.join)
|
||||
capitano.command(actions.leave.leave)
|
||||
|
||||
update.notify()
|
||||
|
||||
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 |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user