Compare commits
878 Commits
v0.4.4
...
v0.9.2-rc1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2516850976 | ||
|
|
ac5cb304d6 | ||
|
|
d35e0e05d8 | ||
|
|
fdc16672d5 | ||
|
|
1356e609b3 | ||
|
|
4410480fd6 | ||
|
|
bc3f2a195d | ||
|
|
261be61cc0 | ||
|
|
62d8aaa58e | ||
|
|
e871741ec3 | ||
|
|
088249d751 | ||
|
|
babf6ddb48 | ||
|
|
8b0be9cd2b | ||
|
|
748be0ad66 | ||
|
|
41e02e6f64 | ||
|
|
d4ae014f76 | ||
|
|
ecdd081c27 | ||
|
|
8f69c1faff | ||
|
|
02a47b2edc | ||
|
|
2f28a00e02 | ||
|
|
da5cab621a | ||
|
|
74136bf8e6 | ||
|
|
53c88bc505 | ||
|
|
5dfcd31b54 | ||
|
|
59a752c306 | ||
|
|
77759afcaa | ||
|
|
ca0d475c83 | ||
|
|
5ea76f704a | ||
|
|
b2cbd62a8d | ||
|
|
8f4b2bc458 | ||
|
|
571597dde3 | ||
|
|
63f8277ecb | ||
|
|
15699a253c | ||
|
|
060390c160 | ||
|
|
fb7a5745c2 | ||
|
|
553e21f919 | ||
|
|
af5935d3f2 | ||
|
|
2a8d8fa891 | ||
|
|
19fcea6264 | ||
|
|
ab3c508a39 | ||
|
|
6a18025fe5 | ||
|
|
2fb3c6fe3e | ||
|
|
693ca3179b | ||
|
|
c3a501d33d | ||
|
|
daed587841 | ||
|
|
ff4b315d0c | ||
|
|
e5f90c5ac5 | ||
|
|
ff2d445039 | ||
|
|
26c2f3cc69 | ||
|
|
5b7bb8c81f | ||
|
|
7e71e4c876 | ||
|
|
76054f1152 | ||
|
|
19157702b3 | ||
|
|
18e0ea81d9 | ||
|
|
a66463285b | ||
|
|
eb0c4b2982 | ||
|
|
1aa8521cf8 | ||
|
|
bcc1aed724 | ||
|
|
2de5daffe9 | ||
|
|
235857d021 | ||
|
|
1e5baa57da | ||
|
|
c62c05773c | ||
|
|
af6888020d | ||
|
|
4c2d21275a | ||
|
|
73980f9c73 | ||
|
|
368a13ed13 | ||
|
|
f6ce1f0685 | ||
|
|
4981e76755 | ||
|
|
8babf66dc4 | ||
|
|
8ee82f263d | ||
|
|
2d92956c82 | ||
|
|
4cd73c111e | ||
|
|
355859e707 | ||
|
|
2cd6ec4db6 | ||
|
|
dc540a0cf0 | ||
|
|
93cd0877dd | ||
|
|
8d941162d8 | ||
|
|
23e51e3b8d | ||
|
|
27f11ec6c2 | ||
|
|
63c3d57993 | ||
|
|
8080d01ac9 | ||
|
|
d1d0c30924 | ||
|
|
a8ade0f873 | ||
|
|
6611eb1134 | ||
|
|
e80342d369 | ||
|
|
23edbd05e8 | ||
|
|
299d59b5fc | ||
|
|
90963f8f45 | ||
|
|
9afc3da083 | ||
|
|
be9874d2f4 | ||
|
|
8a4fa93202 | ||
|
|
78c08c4dd9 | ||
|
|
d65f9518df | ||
|
|
64949bb888 | ||
|
|
17b3589782 | ||
|
|
00af8545d6 | ||
|
|
4126cdbba7 | ||
|
|
e4c2271c6b | ||
|
|
b5fdd63a85 | ||
|
|
0779e13d46 | ||
|
|
5dbb0f2a28 | ||
|
|
8dc2050fd8 | ||
|
|
cdd682429e | ||
|
|
51de09e16e | ||
|
|
42248daf60 | ||
|
|
6d606cc52b | ||
|
|
b2e0510697 | ||
|
|
d26d20d730 | ||
|
|
ff98f27407 | ||
|
|
df32dfdc70 | ||
|
|
10a4c59385 | ||
|
|
78051c2814 | ||
|
|
391082fa50 | ||
|
|
23a4d8ec76 | ||
|
|
8fa2d80325 | ||
|
|
be2c4044ce | ||
|
|
4f177ee605 | ||
|
|
e2ed97648a | ||
|
|
9b793b5d7c | ||
|
|
d9ad645f6d | ||
|
|
f096f552d1 | ||
|
|
f94704a803 | ||
|
|
8a7c8d7758 | ||
|
|
8e7181e690 | ||
|
|
30534a617b | ||
|
|
69be7de1c4 | ||
|
|
2fb7651b60 | ||
|
|
da74a931e6 | ||
|
|
acc72e634e | ||
|
|
f93f360ab2 | ||
|
|
bb7b22a739 | ||
|
|
fd75fd3dc7 | ||
|
|
283c4b7193 | ||
|
|
1e6d38cbf0 | ||
|
|
f041ceefc5 | ||
|
|
62cef9bbd2 | ||
|
|
34d75115db | ||
|
|
ff35f9a5aa | ||
|
|
3133664a11 | ||
|
|
03532960f3 | ||
|
|
77cd10e840 | ||
|
|
493ba98292 | ||
|
|
2f8aa22925 | ||
|
|
14257bdde8 | ||
|
|
db5da1ebfe | ||
|
|
6baa1ee7bd | ||
|
|
752bec258d | ||
|
|
885e809019 | ||
|
|
ebb03a8c80 | ||
|
|
270456adfd | ||
|
|
36251dd177 | ||
|
|
3c5b38e225 | ||
|
|
bdd1d745fd | ||
|
|
7135ae2cdc | ||
|
|
9a75d2d5b4 | ||
|
|
3929481b5b | ||
|
|
4a4f76c669 | ||
|
|
892a17bdbf | ||
|
|
34603057a5 | ||
|
|
2ab211d831 | ||
|
|
ee27d7e582 | ||
|
|
6bf91a2c55 | ||
|
|
39801ee005 | ||
|
|
cd5f5d6090 | ||
|
|
015deae38c | ||
|
|
47491c7e41 | ||
|
|
99267f0cce | ||
|
|
1813c0a0d2 | ||
|
|
7d3630d981 | ||
|
|
4e29df1b8a | ||
|
|
6444fdc414 | ||
|
|
5471359991 | ||
|
|
5cd881b791 | ||
|
|
1f6cad6171 | ||
|
|
ff448b0b96 | ||
|
|
8bbb578b74 | ||
|
|
b530a3cd95 | ||
|
|
98bccd9eca | ||
|
|
06bb106199 | ||
|
|
fc96f75c35 | ||
|
|
83e6696fd3 | ||
|
|
dd56abf725 | ||
|
|
35afccefdf | ||
|
|
0d889ef9a8 | ||
|
|
e51ea84439 | ||
|
|
26ec406a04 | ||
|
|
b3a9893fcf | ||
|
|
e2992d1626 | ||
|
|
b75e418f71 | ||
|
|
7add8369f9 | ||
|
|
7ee3c0e12f | ||
|
|
569811d1f0 | ||
|
|
8d3dc7bede | ||
|
|
61191056a7 | ||
|
|
65c794ea67 | ||
|
|
1b8d869199 | ||
|
|
27b07dab5a | ||
|
|
4e433c272f | ||
|
|
529c1ec83b | ||
|
|
a4e77692c4 | ||
|
|
1c5d41a56e | ||
|
|
48acef5336 | ||
|
|
cf1ddf2ff8 | ||
|
|
b79173c272 | ||
|
|
3a7096f19b | ||
|
|
353fa604ab | ||
|
|
9670aa8505 | ||
|
|
0ec9c2c0e0 | ||
|
|
f906142534 | ||
|
|
a9f19b7d1d | ||
|
|
1be5c6c527 | ||
|
|
ac761a3973 | ||
|
|
ff32d2d34c | ||
|
|
fc1e4e060d | ||
|
|
7765a3308d | ||
|
|
8e5eba0ac5 | ||
|
|
ad5fad46ee | ||
|
|
2980684ae0 | ||
|
|
1761d770c9 | ||
|
|
e854d0f374 | ||
|
|
7e32fcc7d3 | ||
|
|
9a5df48213 | ||
|
|
70d5495c7e | ||
|
|
e59ede0a08 | ||
|
|
613e740d0a | ||
|
|
92277088f2 | ||
|
|
35a018df42 | ||
|
|
cbac292e4d | ||
|
|
d2bee16058 | ||
|
|
f4975d7361 | ||
|
|
b929c1e70b | ||
|
|
29fc46370c | ||
|
|
4f1418d3ba | ||
|
|
edb8022336 | ||
|
|
e5a7889ce9 | ||
|
|
6bbed5fd4c | ||
|
|
2a575837b2 | ||
|
|
bdb0d32235 | ||
|
|
7519325162 | ||
|
|
f5230f1299 | ||
|
|
11e78892c1 | ||
|
|
6503928fbf | ||
|
|
d90217ab06 | ||
|
|
1a0e818328 | ||
|
|
85bdaa6145 | ||
|
|
47eaf2bda4 | ||
|
|
f5193d065a | ||
|
|
cc78ff7bd1 | ||
|
|
087bc6fd2d | ||
|
|
80820e610a | ||
|
|
4155dee5fa | ||
|
|
19e8c841f8 | ||
|
|
62e1fdfbb6 | ||
|
|
1ca021fc6f | ||
|
|
6a1b3697fe | ||
|
|
1dd1f19ca5 | ||
|
|
e7d9d71442 | ||
|
|
190749f6c1 | ||
|
|
95a8aa69db | ||
|
|
98803c2345 | ||
|
|
6e6f83e417 | ||
|
|
f5c8cc1b53 | ||
|
|
8443aa43da | ||
|
|
a6153c37e6 | ||
|
|
c6232e67aa | ||
|
|
68f3b9cfda | ||
|
|
81b35c69e1 | ||
|
|
247d3f5822 | ||
|
|
15e08a2f8e | ||
|
|
ccce5e52c1 | ||
|
|
b1058f3be9 | ||
|
|
1a7227aff6 | ||
|
|
c9dd9772b4 | ||
|
|
0cdfbc76b5 | ||
|
|
05b8587849 | ||
|
|
340c0d27b6 | ||
|
|
69d1077169 | ||
|
|
7dc719148e | ||
|
|
528c39f238 | ||
|
|
ee51e854ba | ||
|
|
d444edd82f | ||
|
|
38faa7073b | ||
|
|
2d955f50de | ||
|
|
86d810038a | ||
|
|
62fc20ae25 | ||
|
|
b2059859df | ||
|
|
ff1ab8cfe9 | ||
|
|
0a83a4d6d0 | ||
|
|
328bd5bd2e | ||
|
|
cc3c78663c | ||
|
|
f1f41dffa2 | ||
|
|
6bd52cea7f | ||
|
|
64ec90fe3d | ||
|
|
7fa9666be9 | ||
|
|
b431254cc4 | ||
|
|
b54b2425bb | ||
|
|
de1bbbea38 | ||
|
|
3cc41b0a98 | ||
|
|
3346c6bc38 | ||
|
|
ba5934c559 | ||
|
|
40100ffa7c | ||
|
|
d53452a7eb | ||
|
|
2bcc69b59a | ||
|
|
be5d207d5d | ||
|
|
a425c4cc1b | ||
|
|
4df962d4b6 | ||
|
|
2e03f1b706 | ||
|
|
14e548bf40 | ||
|
|
dcb0456e18 | ||
|
|
584c4f4686 | ||
|
|
20856a4171 | ||
|
|
6790e050de | ||
|
|
0ac5a2d40b | ||
|
|
4cd20d2163 | ||
|
|
30f44f7b98 | ||
|
|
a2e6a78c83 | ||
|
|
56d465e7bf | ||
|
|
93745adc2d | ||
|
|
feaba53aa8 | ||
|
|
a7c34b9855 | ||
|
|
fa1dc760f2 | ||
|
|
f3b9c72262 | ||
|
|
f6ba07bf8b | ||
|
|
47a447cb67 | ||
|
|
833420def2 | ||
|
|
a4cf5b47b2 | ||
|
|
0d7810120f | ||
|
|
f4948a6ae4 | ||
|
|
540545ff74 | ||
|
|
67ab66f95d | ||
|
|
84ed484196 | ||
|
|
e7071ea390 | ||
|
|
13c6c15587 | ||
|
|
ed645bd758 | ||
|
|
1d617f1b92 | ||
|
|
55d7d9e555 | ||
|
|
4546f19bab | ||
|
|
a063252e06 | ||
|
|
aa781b4a51 | ||
|
|
c6eba498c5 | ||
|
|
debc6a2f8c | ||
|
|
b6106af3ec | ||
|
|
4abc6dd4d1 | ||
|
|
4d9b2595b8 | ||
|
|
56aac57035 | ||
|
|
f7de33c1a5 | ||
|
|
9fb6654d0e | ||
|
|
9a34545aa9 | ||
|
|
432fa5636e | ||
|
|
05ca9416e7 | ||
|
|
0a7d679866 | ||
|
|
90640882b4 | ||
|
|
67c7ba0fed | ||
|
|
98f7f9e207 | ||
|
|
4d89e3f5b7 | ||
|
|
4479ff5cdb | ||
|
|
0a8c4fa471 | ||
|
|
bba93792d5 | ||
|
|
2b0638c95f | ||
|
|
ac3cd213dc | ||
|
|
b3d62c26f7 | ||
|
|
8cc4850fe0 | ||
|
|
0187b4ff92 | ||
|
|
ff51ea0ac8 | ||
|
|
9f4ffef125 | ||
|
|
64d442c2a3 | ||
|
|
b35727ee95 | ||
|
|
e584bfb47d | ||
|
|
8031ef551a | ||
|
|
3964f5be3b | ||
|
|
d59735fc79 | ||
|
|
0c6ce15f45 | ||
|
|
9f909ae54d | ||
|
|
e286b4f20b | ||
|
|
4ca24cffc6 | ||
|
|
28075b2a78 | ||
|
|
90c9257154 | ||
|
|
1d289143fe | ||
|
|
5fe26f3ab5 | ||
|
|
2867d31a88 | ||
|
|
13b34a6668 | ||
|
|
1f1c56d0ec | ||
|
|
ceaec960c4 | ||
|
|
17e9ac1292 | ||
|
|
c1abc67fa8 | ||
|
|
25e5ca5e4c | ||
|
|
ee16cd4311 | ||
|
|
343824c742 | ||
|
|
2bfef7d7a8 | ||
|
|
72f7f9a572 | ||
|
|
284fb06636 | ||
|
|
307a966e73 | ||
|
|
6431034285 | ||
|
|
db6475c811 | ||
|
|
7d34039738 | ||
|
|
bb4ad618e1 | ||
|
|
33b6145162 | ||
|
|
eb3f7fc9aa | ||
|
|
0fa7112e75 | ||
|
|
6b466251ef | ||
|
|
b344657d0f | ||
|
|
84eb54e8db | ||
|
|
4d4c633f7e | ||
|
|
a6468ddcff | ||
|
|
980ee23130 | ||
|
|
ffb0735aba | ||
|
|
35ce5cd8b5 | ||
|
|
d65f376ed9 | ||
|
|
100d85149f | ||
|
|
21fb3ebfa9 | ||
|
|
3ccba631c3 | ||
|
|
6cb3fe08e8 | ||
|
|
57018249a6 | ||
|
|
7dce4d54c1 | ||
|
|
bbf822e430 | ||
|
|
a2c0ffffdf | ||
|
|
7d40c641bc | ||
|
|
b8b4ddc2ae | ||
|
|
2ae22a66fe | ||
|
|
3685461692 | ||
|
|
b767e5e1a7 | ||
|
|
f6576815c9 | ||
|
|
ba2c65ec19 | ||
|
|
4057275605 | ||
|
|
c9d2957d7a | ||
|
|
8ad8fbb877 | ||
|
|
36ad824dd8 | ||
|
|
ed4ad24f62 | ||
|
|
8665960ff2 | ||
|
|
1a0a6f1c0e | ||
|
|
ecc832d060 | ||
|
|
d7368a1a50 | ||
|
|
19d4be62c6 | ||
|
|
5ecbf19aa8 | ||
|
|
404e21f937 | ||
|
|
11d3bd69af | ||
|
|
691f7cb42c | ||
|
|
1f4d23bf50 | ||
|
|
0ae29130cd | ||
|
|
5278dd5015 | ||
|
|
892b8d4bff | ||
|
|
6ce8226cf4 | ||
|
|
a7d405991c | ||
|
|
9755a37fe6 | ||
|
|
e3df9911bc | ||
|
|
0c32665255 | ||
|
|
6f9bab57e3 | ||
|
|
e608377088 | ||
|
|
200052dd56 | ||
|
|
0107f361bb | ||
|
|
ea86e4e087 | ||
|
|
a90d93691f | ||
|
|
ddbedb9b31 | ||
|
|
94fd38c358 | ||
|
|
fb6442f9bb | ||
|
|
e8ffaedb08 | ||
|
|
d4fbc039a7 | ||
|
|
9654e4eaad | ||
|
|
2217e88611 | ||
|
|
4dd735bad4 | ||
|
|
4eaf18946a | ||
|
|
b6c103373e | ||
|
|
edd9d33eaa | ||
|
|
3e3e036bda | ||
|
|
f5681217b5 | ||
|
|
01970f5651 | ||
|
|
76b360706f | ||
|
|
61f591f56e | ||
|
|
dfc9455e72 | ||
|
|
c681c71fa6 | ||
|
|
805e865bfb | ||
|
|
e89f4f27e0 | ||
|
|
8b9edc2836 | ||
|
|
897cbb9e9f | ||
|
|
e97ad03b99 | ||
|
|
6ace0c1b05 | ||
|
|
a05e41ca6b | ||
|
|
bb824a9f1b | ||
|
|
2f2be31d8d | ||
|
|
0a3ee71352 | ||
|
|
314e1a69ae | ||
|
|
375e38c850 | ||
|
|
cc538ad523 | ||
|
|
cdd3dcc99e | ||
|
|
4b121ec3b1 | ||
|
|
f56501251e | ||
|
|
a811132245 | ||
|
|
2122f99fce | ||
|
|
05a43ceac0 | ||
|
|
bdcda45a11 | ||
|
|
99420925f5 | ||
|
|
2fc1d3fa5f | ||
|
|
7adb6ee3a8 | ||
|
|
395ef0b8c4 | ||
|
|
cf998978a8 | ||
|
|
5987d713a4 | ||
|
|
e5293ff926 | ||
|
|
3bbf46b66d | ||
|
|
1da9a19951 | ||
|
|
b65e429bb6 | ||
|
|
5cc30c6935 | ||
|
|
7ac9d02614 | ||
|
|
e58a6a5433 | ||
|
|
1731fc5642 | ||
|
|
32b2ccda1e | ||
|
|
065fe4a16e | ||
|
|
bdf266e655 | ||
|
|
a23776f03f | ||
|
|
133c2610d1 | ||
|
|
c1582f5a49 | ||
|
|
2781eab500 | ||
|
|
9e14a9da5a | ||
|
|
605a8bf618 | ||
|
|
d28dfe1e19 | ||
|
|
d782d0b17f | ||
|
|
a22075aef2 | ||
|
|
e0110932f4 | ||
|
|
2751101ebe | ||
|
|
1de9d2d6c7 | ||
|
|
8777e477b8 | ||
|
|
b536e6ea35 | ||
|
|
17f0ef63b9 | ||
|
|
2b622f88dc | ||
|
|
71ce9e4aa3 | ||
|
|
e315240651 | ||
|
|
21cf86665b | ||
|
|
c080cadea0 | ||
|
|
238c393640 | ||
|
|
d01440fe55 | ||
|
|
9122378855 | ||
|
|
b551fad495 | ||
|
|
68e6e0ab33 | ||
|
|
bca966a3a9 | ||
|
|
ce523d98f4 | ||
|
|
5ab3f500d6 | ||
|
|
70f2c8dd3a | ||
|
|
3b68017af5 | ||
|
|
2081b1be95 | ||
|
|
73d0790e30 | ||
|
|
3262aa7497 | ||
|
|
17e01a3771 | ||
|
|
4d8da95bc7 | ||
|
|
70df9efc4c | ||
|
|
d4a026dc5d | ||
|
|
5d02e35df7 | ||
|
|
22a3722aa0 | ||
|
|
258a5a173c | ||
|
|
2e6f31599a | ||
|
|
345aa57c80 | ||
|
|
0631e90ee0 | ||
|
|
864990e640 | ||
|
|
39e4bf7e5d | ||
|
|
aa10d84b75 | ||
|
|
bd904fcbda | ||
|
|
b11ed7c1ef | ||
|
|
f5be4686d5 | ||
|
|
ec85b27621 | ||
|
|
c2a58aeb70 | ||
|
|
c54da1924b | ||
|
|
05132c0dd6 | ||
|
|
bffe4e5d3e | ||
|
|
f64314b82f | ||
|
|
92e20827d8 | ||
|
|
22ab564dac | ||
|
|
d725d99fa5 | ||
|
|
f468cdb307 | ||
|
|
48c40a57c5 | ||
|
|
3de6cabcff | ||
|
|
a375cf6635 | ||
|
|
c0bcb61158 | ||
|
|
360b576411 | ||
|
|
dcc4962fdf | ||
|
|
2cfe4d289d | ||
|
|
723278a440 | ||
|
|
4bbfdadfde | ||
|
|
4677af8419 | ||
|
|
5ffc77e797 | ||
|
|
e4a94a81ef | ||
|
|
bbab6aa760 | ||
|
|
42d966bd52 | ||
|
|
90cefae4d8 | ||
|
|
5eda685ff5 | ||
|
|
8d79d67ca4 | ||
|
|
ecb3880846 | ||
|
|
6ed44af89a | ||
|
|
7a1609a398 | ||
|
|
894aa8054b | ||
|
|
4edba20d24 | ||
|
|
da69a464fb | ||
|
|
add2080fb2 | ||
|
|
31193538e6 | ||
|
|
26a9331e41 | ||
|
|
8c5c3fe09d | ||
|
|
42748a2c4b | ||
|
|
11cf2a195b | ||
|
|
4b3198dff4 | ||
|
|
abbff8d89a | ||
|
|
be58e0a8c0 | ||
|
|
69db72ffc5 | ||
|
|
b04536e55f | ||
|
|
be49936400 | ||
|
|
a81be13f82 | ||
|
|
c386353481 | ||
|
|
2631eb118c | ||
|
|
9d603a6388 | ||
|
|
65468736df | ||
|
|
33c5060e45 | ||
|
|
d90d9b7f19 | ||
|
|
455e36bb0e | ||
|
|
6abdeb34d6 | ||
|
|
80c72d0761 | ||
|
|
3fb47bcc47 | ||
|
|
e827095b94 | ||
|
|
d4801494a1 | ||
|
|
64711f9e66 | ||
|
|
7890844e47 | ||
|
|
0a053c62ab | ||
|
|
14040ba772 | ||
|
|
6658917591 | ||
|
|
c67475cbaa | ||
|
|
889cb9eea8 | ||
|
|
7601fae952 | ||
|
|
d69915040d | ||
|
|
e5f1f299f0 | ||
|
|
b1368b27fc | ||
|
|
f703b8d396 | ||
|
|
25628b7df4 | ||
|
|
782f9059ba | ||
|
|
6b4222888c | ||
|
|
1877cfa16b | ||
|
|
3700ba4726 | ||
|
|
ad6c25e0cc | ||
|
|
83332ceade | ||
|
|
c1cd100dae | ||
|
|
0c7fb4f1f9 | ||
|
|
df6d77fff7 | ||
|
|
eec5363a20 | ||
|
|
22887dfb0b | ||
|
|
b882a7c049 | ||
|
|
da80b76883 | ||
|
|
579991f458 | ||
|
|
bbf5735e9e | ||
|
|
0f4640ec57 | ||
|
|
2846f26e5b | ||
|
|
16ea661b6b | ||
|
|
321318819c | ||
|
|
fd8e1dedb5 | ||
|
|
f98e3efad2 | ||
|
|
8c8b5ed418 | ||
|
|
55c3dd57d1 | ||
|
|
9b7b6ebe4a | ||
|
|
25768888a5 | ||
|
|
b6edde6052 | ||
|
|
78fc0d15cc | ||
|
|
b79533c7f9 | ||
|
|
7dfece8f3d | ||
|
|
7ab3755d79 | ||
|
|
70bf02df1f | ||
|
|
e03dbe77e8 | ||
|
|
475cc88cea | ||
|
|
2264aa8230 | ||
|
|
68aaa20d3c | ||
|
|
0b4e99e1e6 | ||
|
|
e6a550d786 | ||
|
|
93b4949cce | ||
|
|
b9eba9d290 | ||
|
|
e4e07ec278 | ||
|
|
e24cad0a5e | ||
|
|
ac1269a222 | ||
|
|
92b24d9790 | ||
|
|
6b47d33df2 | ||
|
|
dd4f348592 | ||
|
|
ddff0d3e6f | ||
|
|
afb8806cb6 | ||
|
|
e360f6cd6a | ||
|
|
b0c6ad709c | ||
|
|
9abb58641d | ||
|
|
0323844ca6 | ||
|
|
327aa0964c | ||
|
|
b09c09edf4 | ||
|
|
8f8dec51ea | ||
|
|
d8fc2a7401 | ||
|
|
d0607ee68f | ||
|
|
38bc8ac90d | ||
|
|
f2187f1bf8 | ||
|
|
116c147620 | ||
|
|
128c228ff5 | ||
|
|
b22c075b95 | ||
|
|
921c00c8ea | ||
|
|
eb0c262ef7 | ||
|
|
fc2acacab5 | ||
|
|
cdff94c07f | ||
|
|
73ea4c9bcd | ||
|
|
50f334281f | ||
|
|
9922786748 | ||
|
|
a164f385ba | ||
|
|
3cdbf2ef0c | ||
|
|
0392f124cf | ||
|
|
65bff509e0 | ||
|
|
07cc5a430a | ||
|
|
e058c82515 | ||
|
|
45712bef22 | ||
|
|
b514eb9e12 | ||
|
|
17bb7cf6ee | ||
|
|
ecc8482666 | ||
|
|
2a60981b09 | ||
|
|
34198b8674 | ||
|
|
395b855871 | ||
|
|
9e82d7d768 | ||
|
|
b6ddb4f577 | ||
|
|
0d05948fc8 | ||
|
|
21fce533b8 | ||
|
|
9dee6f4d59 | ||
|
|
f850e151de | ||
|
|
a9e34206bc | ||
|
|
d797d587ab | ||
|
|
924e4b8aae | ||
|
|
c138ecb54d | ||
|
|
01c3adc31b | ||
|
|
e1a63c80f3 | ||
|
|
445d35e33e | ||
|
|
e98fc80d12 | ||
|
|
82ca0def35 | ||
|
|
f24facc06c | ||
|
|
d205af4abf | ||
|
|
a002939a3f | ||
|
|
a640bdb758 | ||
|
|
07d475cecb | ||
|
|
e4019385aa | ||
|
|
6d3ac8d734 | ||
|
|
313cdb7e24 | ||
|
|
30d988d5b0 | ||
|
|
0d3b1edd4d | ||
|
|
025e555fde | ||
|
|
4f9cc68175 | ||
|
|
10a179cbbd | ||
|
|
a5273d922e | ||
|
|
9afb667cf4 | ||
|
|
af52052758 | ||
|
|
42ef7c062a | ||
|
|
3837e789aa | ||
|
|
9b4bcfea50 | ||
|
|
c4c8ec402c | ||
|
|
3153c28e86 | ||
|
|
7f7d8765ca | ||
|
|
abfb45401a | ||
|
|
9f7e6a19ab | ||
|
|
02cfe37d30 | ||
|
|
b436cdff4d | ||
|
|
a938f4375f | ||
|
|
77612b87c5 | ||
|
|
9dc25fc411 | ||
|
|
b279fa85f6 | ||
|
|
3b8b47f103 | ||
|
|
f410c678ba | ||
|
|
438a437459 | ||
|
|
9f0074c398 | ||
|
|
3728a328ea | ||
|
|
90224d5fcd | ||
|
|
c8fd1bc665 | ||
|
|
751b71564e | ||
|
|
57a2f99484 | ||
|
|
e1d29fbab7 | ||
|
|
ac0f317bf8 | ||
|
|
29c6aac571 | ||
|
|
09b4b202b2 | ||
|
|
7ec73416c8 | ||
|
|
9658d3bffd | ||
|
|
4d5e77f0d6 | ||
|
|
80f10b6580 | ||
|
|
4cb1d26cf9 | ||
|
|
355095d41f | ||
|
|
cd76d85aea | ||
|
|
719d255636 | ||
|
|
f22c692529 | ||
|
|
b83f0ec092 | ||
|
|
41f333d0ff | ||
|
|
82666d779e | ||
|
|
c9c095f202 | ||
|
|
03db5d1058 | ||
|
|
8ab32c820f | ||
|
|
43f90b8e61 | ||
|
|
c5fb496a5d | ||
|
|
bb89c44b99 | ||
|
|
7c0b5ea1c4 | ||
|
|
8bb4517b9c | ||
|
|
5c7482e5fd | ||
|
|
7c6fa3f0b6 | ||
|
|
adb5635186 | ||
|
|
4ab3162ed8 | ||
|
|
71c8f40ffe | ||
|
|
5659173628 | ||
|
|
6e2f8e5940 | ||
|
|
4f48569cb2 | ||
|
|
55d629b980 | ||
|
|
08f40ad3e7 | ||
|
|
e51c5409aa | ||
|
|
97344cc535 | ||
|
|
6fd0beda35 | ||
|
|
7ea6dfbe07 | ||
|
|
5bf4d2c117 | ||
|
|
742c9a9401 | ||
|
|
e5ae33b4ed | ||
|
|
a14846152b | ||
|
|
a59ec33787 | ||
|
|
410dfbe0fd | ||
|
|
95d8ebe146 | ||
|
|
00824cebca | ||
|
|
c24e1b136b | ||
|
|
36c09de412 | ||
|
|
68bc0f4103 | ||
|
|
e253d47379 | ||
|
|
bbf33a771e | ||
|
|
f8ac2e6ffb | ||
|
|
6ec9ce1bc6 | ||
|
|
afcb0d38fe | ||
|
|
69fe4bb619 | ||
|
|
af6079f780 | ||
|
|
50153d77c5 | ||
|
|
50de80d09a | ||
|
|
c18cd26e78 | ||
|
|
b2ab4d0c38 | ||
|
|
42e48476e7 | ||
|
|
448f12acb1 | ||
|
|
9e73976a9e | ||
|
|
0d5223db23 | ||
|
|
d34ead2fbf | ||
|
|
c09b807991 | ||
|
|
a50d2e4c90 | ||
|
|
f5ae8d9dab | ||
|
|
053f1db6be | ||
|
|
d664c96001 | ||
|
|
de7930d820 | ||
|
|
8b75535eb5 | ||
|
|
8eefaeb5ed | ||
|
|
696478ad9b | ||
|
|
dd7b2554e7 | ||
|
|
1d92167143 | ||
|
|
c3fd028395 | ||
|
|
2968bed32e | ||
|
|
bd2971aada | ||
|
|
0e82ddadfc | ||
|
|
a9ca80d47d | ||
|
|
ae543ab167 | ||
|
|
ab6cc149d3 | ||
|
|
3732ab5661 | ||
|
|
3c8559a576 | ||
|
|
ec410addb9 | ||
|
|
acc2ee297a | ||
|
|
409ed945d1 | ||
|
|
0af0a517ca | ||
|
|
7241390042 | ||
|
|
a888a57cd9 | ||
|
|
cd84bd8cf6 | ||
|
|
c94f2451d4 | ||
|
|
d1957b834b | ||
|
|
b68447daaf | ||
|
|
fa98165862 | ||
|
|
1f49d44fdb | ||
|
|
3fa11f963d | ||
|
|
862344bfc3 | ||
|
|
f15967a7a3 | ||
|
|
6ab32ae19c | ||
|
|
4439cdde41 | ||
|
|
ee73337f49 | ||
|
|
35ee0b5982 | ||
|
|
eeea325e54 | ||
|
|
f878b31407 | ||
|
|
d657155723 | ||
|
|
8862878337 | ||
|
|
ad1098a730 | ||
|
|
8b4e6cc502 | ||
|
|
a0ae6222c9 | ||
|
|
5d15d3aa2c | ||
|
|
f7cd87e2fb | ||
|
|
53acef17cd | ||
|
|
1df1582459 | ||
|
|
3c78335edf |
@@ -1,2 +0,0 @@
|
||||
DOCKER_HOST="tcp://192.168.2.2:2375"
|
||||
DOCKER_TLS_VERIFY=
|
||||
@@ -1,16 +1,17 @@
|
||||
.DS_Store
|
||||
.git
|
||||
.idea
|
||||
.vendor
|
||||
.dockerignore
|
||||
.trash-cache
|
||||
bin
|
||||
gopath
|
||||
tmp
|
||||
state
|
||||
build
|
||||
images/*/build
|
||||
scripts/images/*/dist/
|
||||
dist
|
||||
Godeps/_workspace/pkg
|
||||
tests/integration/.venv*
|
||||
tests/integration/.tox
|
||||
*/*/*/*.pyc
|
||||
*/*/*/__pycache__
|
||||
.trash-cache
|
||||
#.dapper
|
||||
vendor/*/*/*/.git
|
||||
tmp
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
.DS_Store
|
||||
.git
|
||||
.idea
|
||||
.vendor
|
||||
.dockerignore
|
||||
bin
|
||||
gopath
|
||||
tmp
|
||||
state
|
||||
build
|
||||
Godeps/_workspace/pkg
|
||||
tests/integration/.venv*
|
||||
tests/integration/.tox
|
||||
*/*/*/*.pyc
|
||||
*/*/*/__pycache__
|
||||
14
.drone.yml
14
.drone.yml
@@ -1,6 +1,8 @@
|
||||
build:
|
||||
image: rancher/dapper:1.10.3
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
commands:
|
||||
- ./scripts/ci
|
||||
---
|
||||
pipeline:
|
||||
build:
|
||||
image: rancher/dapper:1.10.3
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
commands:
|
||||
- dapper ci
|
||||
|
||||
5
.github/ISSUE_TEMPLATE.md
vendored
Normal file
5
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
**RancherOS Version: (ros os version)**
|
||||
|
||||
**Where are you running RancherOS? (docker-machine, AWS, GCE, baremetal, etc.)**
|
||||
|
||||
|
||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -5,11 +5,15 @@
|
||||
/build
|
||||
/dist
|
||||
/gopath
|
||||
/images/*/build
|
||||
.dockerfile
|
||||
*.swp
|
||||
/tests/integration/MANIFEST
|
||||
/tests/integration/.venv*
|
||||
/tests/integration/.tox
|
||||
/tests/integration/.idea
|
||||
*.pyc
|
||||
__pychache__
|
||||
.docker-env.*
|
||||
__pycache__
|
||||
/.dapper
|
||||
/.trash-cache
|
||||
.idea
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
FROM debian:jessie
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
RUN apt-get update && apt-get install -y grub2 parted kexec-tools
|
||||
|
||||
COPY ./scripts/installer /scripts
|
||||
COPY ./build.conf /scripts/
|
||||
|
||||
COPY ./dist/artifacts/vmlinuz /dist/vmlinuz
|
||||
COPY ./dist/artifacts/initrd /dist/initrd
|
||||
|
||||
ENTRYPOINT ["/scripts/lay-down-os"]
|
||||
@@ -1,18 +0,0 @@
|
||||
FROM aarch64/debian:jessie
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
ENV ARCH arm
|
||||
|
||||
RUN apt-get update && apt-get install -y parted git gcc make autoconf
|
||||
|
||||
RUN mkdir -p /usr/local/src && \
|
||||
cd /usr/local/src && \
|
||||
git clone https://git.linaro.org/people/takahiro.akashi/kexec-tools.git && \
|
||||
cd kexec-tools && git checkout kdump/for-14 && ./bootstrap && ./configure && make && make install
|
||||
|
||||
COPY ./scripts/installer /scripts
|
||||
COPY ./build.conf /scripts/
|
||||
|
||||
COPY ./dist/artifacts/vmlinuz /dist/
|
||||
COPY ./dist/artifacts/initrd /dist/
|
||||
|
||||
ENTRYPOINT ["/scripts/lay-down-os"]
|
||||
@@ -1,81 +1,162 @@
|
||||
FROM rancher/os-dapper-base
|
||||
FROM ubuntu:16.04
|
||||
# FROM arm64=aarch64/ubuntu:16.04 arm=armhf/ubuntu:16.04
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get -y install locales sudo vim less curl wget git rsync build-essential isolinux xorriso gccgo \
|
||||
libblkid-dev libmount-dev libselinux1-dev cpio genisoimage qemu-kvm qemu python-pip ca-certificates pkg-config tox module-init-tools
|
||||
apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
ca-certificates \
|
||||
cpio \
|
||||
curl \
|
||||
dosfstools \
|
||||
gccgo \
|
||||
genisoimage \
|
||||
gettext \
|
||||
git \
|
||||
isolinux \
|
||||
less \
|
||||
libblkid-dev \
|
||||
libmount-dev \
|
||||
libselinux1-dev \
|
||||
locales \
|
||||
module-init-tools \
|
||||
openssh-client \
|
||||
pkg-config \
|
||||
qemu \
|
||||
qemu-kvm \
|
||||
rsync \
|
||||
sudo \
|
||||
syslinux-common \
|
||||
vim \
|
||||
wget \
|
||||
xorriso
|
||||
|
||||
ARG HOST_ARCH
|
||||
ENV HOST_ARCH ${HOST_ARCH}
|
||||
RUN ln -sf go-6 /usr/bin/go && mkdir -p /usr/local && cd /usr/local && \
|
||||
wget -O - https://storage.googleapis.com/golang/go1.6.src.tar.gz | tar -xz && \
|
||||
cd go/src && GOROOT_BOOTSTRAP=/usr GOARCH=${HOST_ARCH} GOHOSTARCH=${HOST_ARCH} ./make.bash
|
||||
|
||||
ENV PATH /usr/local/go/bin:$PATH
|
||||
RUN mkdir -p /go/src /go/bin && chmod -R 777 /go
|
||||
ENV GOPATH /go
|
||||
ENV PATH /go/bin:$PATH
|
||||
|
||||
ARG HOST_DOCKER_BINARY_URL
|
||||
ENV HOST_DOCKER_BINARY_URL ${HOST_DOCKER_BINARY_URL}
|
||||
RUN wget -O - ${HOST_DOCKER_BINARY_URL} > /usr/local/bin/docker
|
||||
RUN chmod +x /usr/local/bin/docker
|
||||
########## Dapper Configuration #####################
|
||||
|
||||
ENV DAPPER_ENV VERSION DEV_BUILD RUNTEST
|
||||
ENV DAPPER_DOCKER_SOCKET true
|
||||
ENV DAPPER_SOURCE /go/src/github.com/rancher/os
|
||||
ENV DAPPER_OUTPUT ./bin ./dist ./build/os-config.yml
|
||||
ENV DAPPER_OUTPUT ./bin ./dist ./build/initrd ./build/kernel
|
||||
ENV DAPPER_RUN_ARGS --privileged
|
||||
ENV TRASH_CACHE ${DAPPER_SOURCE}/.trash-cache
|
||||
ENV SHELL /bin/bash
|
||||
WORKDIR ${DAPPER_SOURCE}
|
||||
|
||||
COPY .dockerignore.dapper .dockerignore
|
||||
########## General Configuration #####################
|
||||
ARG DAPPER_HOST_ARCH=amd64
|
||||
ARG HOST_ARCH=${DAPPER_HOST_ARCH}
|
||||
ARG ARCH=${HOST_ARCH}
|
||||
|
||||
CMD make
|
||||
ARG OS_REPO=rancher
|
||||
ARG HOSTNAME_DEFAULT=rancher
|
||||
ARG DISTRIB_ID=RancherOS
|
||||
|
||||
ARG TOOLCHAIN
|
||||
ENV TOOLCHAIN ${TOOLCHAIN}
|
||||
ARG DOCKER_VERSION=1.11.2
|
||||
ARG DOCKER_PATCH_VERSION=v${DOCKER_VERSION}-ros1
|
||||
ARG DOCKER_BUILD_VERSION=1.10.3
|
||||
ARG DOCKER_BUILD_PATCH_VERSION=v${DOCKER_BUILD_VERSION}-ros1
|
||||
ARG SELINUX_POLICY_URL=https://github.com/rancher/refpolicy/releases/download/v0.0.3/policy.29
|
||||
|
||||
RUN if [ "${TOOLCHAIN}" != "" ] && ! which ${TOOLCHAIN}-gcc; then \
|
||||
apt-get install -y gcc-${TOOLCHAIN} g++-${TOOLCHAIN} \
|
||||
ARG KERNEL_VERSION_amd64=4.9.20-rancher
|
||||
ARG KERNEL_URL_amd64=https://github.com/rancher/os-kernel/releases/download/v${KERNEL_VERSION_amd64}/linux-${KERNEL_VERSION_amd64}-x86.tar.gz
|
||||
ARG KERNEL_URL_arm64=https://github.com/imikushin/os-kernel/releases/download/Estuary-4.4.0-arm64.8/linux-4.4.0-rancher-arm64.tar.gz
|
||||
|
||||
ARG DOCKER_URL_amd64=https://get.docker.com/builds/Linux/x86_64/docker-${DOCKER_VERSION}.tgz
|
||||
ARG DOCKER_URL_arm=https://github.com/rancher/docker/releases/download/${DOCKER_PATCH_VERSION}/docker-${DOCKER_VERSION}_arm.tgz
|
||||
ARG DOCKER_URL_arm64=https://github.com/rancher/docker/releases/download/${DOCKER_PATCH_VERSION}/docker-${DOCKER_VERSION}_arm64.tgz
|
||||
|
||||
ARG BUILD_DOCKER_URL_amd64=https://get.docker.com/builds/Linux/x86_64/docker-${DOCKER_BUILD_VERSION}
|
||||
ARG BUILD_DOCKER_URL_arm=https://github.com/rancher/docker/releases/download/${DOCKER_BUILD_PATCH_VERSION}/docker-${DOCKER_BUILD_VERSION}_arm
|
||||
ARG BUILD_DOCKER_URL_arm64=https://github.com/rancher/docker/releases/download/${DOCKER_BUILD_PATCH_VERSION}/docker-${DOCKER_BUILD_VERSION}_arm64
|
||||
|
||||
ARG OS_RELEASES_YML=https://releases.rancher.com/os
|
||||
|
||||
ARG OS_SERVICES_REPO=https://raw.githubusercontent.com/${OS_REPO}/os-services
|
||||
ARG IMAGE_NAME=${OS_REPO}/os
|
||||
ARG DFS_IMAGE=${OS_REPO}/docker:v${DOCKER_VERSION}-2
|
||||
|
||||
ARG OS_BASE_URL_amd64=https://github.com/rancher/os-base/releases/download/v2016.08.1-2/os-base_amd64.tar.xz
|
||||
ARG OS_BASE_URL_arm64=https://github.com/rancher/os-base/releases/download/v2016.08.1-2/os-base_arm64.tar.xz
|
||||
ARG OS_BASE_URL_arm=https://github.com/rancher/os-base/releases/download/v2016.08.1-3/os-base_arm.tar.xz
|
||||
######################################################
|
||||
|
||||
# Set up environment and export all ARGS as ENV
|
||||
ENV ARCH=${ARCH} \
|
||||
HOST_ARCH=${HOST_ARCH}
|
||||
|
||||
ENV BUILD_DOCKER_URL=BUILD_DOCKER_URL_${ARCH} \
|
||||
BUILD_DOCKER_URL_amd64=${BUILD_DOCKER_URL_amd64} \
|
||||
BUILD_DOCKER_URL_arm=${BUILD_DOCKER_URL_arm} \
|
||||
BUILD_DOCKER_URL_arm64=${BUILD_DOCKER_URL_arm64} \
|
||||
DAPPER_HOST_ARCH=${DAPPER_HOST_ARCH} \
|
||||
DFS_IMAGE=${DFS_IMAGE} \
|
||||
DISTRIB_ID=${DISTRIB_ID} \
|
||||
DOCKER_PATCH_VERSION=${DOCKER_PATCH_VERSION} \
|
||||
DOCKER_URL=DOCKER_URL_${ARCH} \
|
||||
DOCKER_URL_amd64=${DOCKER_URL_amd64} \
|
||||
DOCKER_URL_arm=${DOCKER_URL_arm} \
|
||||
DOCKER_URL_arm64=${DOCKER_URL_arm64} \
|
||||
DOCKER_VERSION=${DOCKER_VERSION} \
|
||||
DOWNLOADS=/usr/src/downloads \
|
||||
GOPATH=/go \
|
||||
GO_VERSION=1.7.1 \
|
||||
GOARCH=$ARCH \
|
||||
HOSTNAME_DEFAULT=${HOSTNAME_DEFAULT} \
|
||||
IMAGE_NAME=${IMAGE_NAME} \
|
||||
KERNEL_VERSION=${KERNEL_VERSION_amd64} \
|
||||
KERNEL_URL=KERNEL_URL_${ARCH} \
|
||||
KERNEL_URL_amd64=${KERNEL_URL_amd64} \
|
||||
KERNEL_URL_arm64=${KERNEL_URL_arm64} \
|
||||
OS_BASE_SHA1=OS_BASE_SHA1_${ARCH} \
|
||||
OS_BASE_URL=OS_BASE_URL_${ARCH} \
|
||||
OS_BASE_URL_amd64=${OS_BASE_URL_amd64} \
|
||||
OS_BASE_URL_arm=${OS_BASE_URL_arm} \
|
||||
OS_BASE_URL_arm64=${OS_BASE_URL_arm64} \
|
||||
OS_RELEASES_YML=${OS_RELEASES_YML} \
|
||||
OS_REPO=${OS_REPO} \
|
||||
OS_SERVICES_REPO=${OS_SERVICES_REPO} \
|
||||
REPO_VERSION=master \
|
||||
SELINUX_POLICY_URL=${SELINUX_POLICY_URL}
|
||||
ENV PATH=${GOPATH}/bin:/usr/local/go/bin:$PATH
|
||||
|
||||
RUN mkdir -p ${DOWNLOADS}
|
||||
|
||||
# Download kernel
|
||||
RUN rm /bin/sh && ln -s /bin/bash /bin/sh
|
||||
RUN echo "... Downloading ${!KERNEL_URL}"; \
|
||||
if [ -n "${!KERNEL_URL}" ]; then \
|
||||
curl -fL ${!KERNEL_URL} > ${DOWNLOADS}/kernel.tar.gz \
|
||||
;fi
|
||||
|
||||
RUN if [ "${TOOLCHAIN}" != "" ]; then \
|
||||
apt-get update && \
|
||||
cd /usr/local/src && \
|
||||
for i in libselinux libsepol pcre3 util-linux; do \
|
||||
apt-get build-dep -y $i && \
|
||||
apt-get source -y $i \
|
||||
;done \
|
||||
;fi
|
||||
# Download SELinux Policy
|
||||
RUN curl -pfL ${SELINUX_POLICY_URL} > ${DOWNLOADS}/$(basename ${SELINUX_POLICY_URL})
|
||||
|
||||
RUN if [ "${TOOLCHAIN}" != "" ]; then \
|
||||
cd /usr/local/src/pcre3-* && \
|
||||
autoreconf && \
|
||||
CC=${TOOLCHAIN}-gcc CXX=${TOOLCHAIN}-g++ ./configure --host=${TOOLCHAIN} --prefix=/usr/${TOOLCHAIN} && \
|
||||
make -j$(nproc) && \
|
||||
make install \
|
||||
;fi
|
||||
# Install Go
|
||||
COPY assets/go-dnsclient.patch ${DAPPER_SOURCE}
|
||||
RUN ln -sf go-6 /usr/bin/go && \
|
||||
curl -sfL https://storage.googleapis.com/golang/go${GO_VERSION}.src.tar.gz | tar -xzf - -C /usr/local && \
|
||||
patch /usr/local/go/src/net/dnsclient_unix.go ${DAPPER_SOURCE}/go-dnsclient.patch && \
|
||||
cd /usr/local/go/src && \
|
||||
GOROOT_BOOTSTRAP=/usr GOARCH=${HOST_ARCH} GOHOSTARCH=${HOST_ARCH} ./make.bash && \
|
||||
rm /usr/bin/go
|
||||
|
||||
RUN if [ "${TOOLCHAIN}" != "" ]; then \
|
||||
cd /usr/local/src/libselinux-* && \
|
||||
CC=${TOOLCHAIN}-gcc CXX=${TOOLCHAIN}-g++ make CFLAGS=-Wall && \
|
||||
make PREFIX=/usr/${TOOLCHAIN} DESTDIR=/usr/${TOOLCHAIN} install && \
|
||||
cd /usr/local/src/libsepol-* && \
|
||||
CC=${TOOLCHAIN}-gcc CXX=${TOOLCHAIN}-g++ make CFLAGS=-Wall && \
|
||||
make PREFIX=/usr/${TOOLCHAIN} DESTDIR=/usr/${TOOLCHAIN} install \
|
||||
;fi
|
||||
# Install Host Docker
|
||||
RUN curl -fL ${!BUILD_DOCKER_URL} > /usr/bin/docker && \
|
||||
chmod +x /usr/bin/docker
|
||||
|
||||
RUN if [ "${TOOLCHAIN}" != "" ]; then \
|
||||
cd /usr/local/src/util-linux-* && \
|
||||
autoreconf && \
|
||||
CC=${TOOLCHAIN}-gcc CXX=${TOOLCHAIN}-g++ ./configure --host=${TOOLCHAIN} --prefix=/usr/${TOOLCHAIN} \
|
||||
--disable-all-programs \
|
||||
--enable-libmount \
|
||||
--enable-libblkid \
|
||||
--enable-libuuid \
|
||||
--enable-mount && \
|
||||
make -j$(nproc) && \
|
||||
make install \
|
||||
;fi
|
||||
# Install Trash
|
||||
RUN go get github.com/rancher/trash
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get -y install dosfstools
|
||||
# Install golint
|
||||
RUN go get github.com/golang/lint/golint
|
||||
|
||||
RUN go get gopkg.in/check.v1
|
||||
|
||||
# Install dapper
|
||||
RUN curl -sL https://releases.rancher.com/dapper/latest/dapper-`uname -s`-`uname -m | sed 's/arm.*/arm/'` > /usr/bin/dapper && \
|
||||
chmod +x /usr/bin/dapper
|
||||
|
||||
RUN cd ${DOWNLOADS} && \
|
||||
curl -pfL ${!OS_BASE_URL} | tar xvJf -
|
||||
|
||||
ENTRYPOINT ["./scripts/entry"]
|
||||
CMD ["ci"]
|
||||
|
||||
133
Makefile
Normal file → Executable file
133
Makefile
Normal file → Executable file
@@ -1,104 +1,67 @@
|
||||
FORCE_PULL := 0
|
||||
DEV_BUILD := 0
|
||||
HOST_ARCH := amd64
|
||||
ARCH := amd64
|
||||
SUFFIX := $(if $(filter-out amd64,$(ARCH)),_$(ARCH))
|
||||
TARGETS := $(shell ls scripts | grep -vE 'clean|run|help|docs|release')
|
||||
|
||||
include build.conf
|
||||
include build.conf.$(ARCH)
|
||||
.dapper:
|
||||
@echo Downloading dapper
|
||||
@curl -sL https://releases.rancher.com/dapper/latest/dapper-`uname -s`-`uname -m|sed 's/v7l//'` > .dapper.tmp
|
||||
@@chmod +x .dapper.tmp
|
||||
@./.dapper.tmp -v
|
||||
@mv .dapper.tmp .dapper
|
||||
|
||||
$(TARGETS): .dapper
|
||||
./.dapper $@
|
||||
|
||||
bin/ros:
|
||||
mkdir -p $(dir $@)
|
||||
ARCH=$(ARCH) VERSION=$(VERSION) ./scripts/mk-ros.sh $@
|
||||
trash: .dapper
|
||||
./.dapper -m bind trash
|
||||
|
||||
build/host_ros: bin/ros
|
||||
mkdir -p $(dir $@)
|
||||
ifeq "$(ARCH)" "$(HOST_ARCH)"
|
||||
ln -sf ../bin/ros $@
|
||||
else
|
||||
ARCH=$(HOST_ARCH) TOOLCHAIN= VERSION=$(VERSION) ./scripts/mk-ros.sh $@
|
||||
endif
|
||||
trash-keep: .dapper
|
||||
./.dapper -m bind trash -k
|
||||
|
||||
deps: trash
|
||||
|
||||
assets/docker:
|
||||
mkdir -p $(dir $@)
|
||||
wget -O - "$(DOCKER_BINARY_URL)" > $@
|
||||
chmod +x $@
|
||||
build/initrd/.id: .dapper
|
||||
./.dapper prepare
|
||||
|
||||
assets/selinux/policy.29:
|
||||
mkdir -p $(dir $@)
|
||||
wget -O - "$(SELINUX_POLICY_URL)" > $@
|
||||
run: build/initrd/.id .dapper
|
||||
./.dapper -m bind build-target
|
||||
./scripts/run
|
||||
|
||||
assets/modules.tar.gz:
|
||||
mkdir -p $(dir $@)
|
||||
ifeq "$(ARCH)" "amd64"
|
||||
curl -L "$(VBOX_MODULES_URL)" > $@
|
||||
else
|
||||
touch $@
|
||||
endif
|
||||
docs:
|
||||
./scripts/docs
|
||||
|
||||
ifdef COMPILED_KERNEL_URL
|
||||
shell-bind: .dapper
|
||||
./.dapper -m bind -s
|
||||
|
||||
installer: minimal
|
||||
docker build -t $(IMAGE_NAME):$(VERSION)$(SUFFIX) -f Dockerfile.$(ARCH) .
|
||||
clean:
|
||||
@./scripts/clean
|
||||
|
||||
dist/artifacts/vmlinuz: build/kernel/
|
||||
mkdir -p $(dir $@)
|
||||
mv $(or $(wildcard build/kernel/boot/vmlinuz*), $(wildcard build/kernel/boot/vmlinux*)) $@
|
||||
release: release-build openstack
|
||||
|
||||
release-build:
|
||||
./.dapper release
|
||||
|
||||
build/kernel/:
|
||||
mkdir -p $@
|
||||
wget -O - "$(COMPILED_KERNEL_URL)" | tar -xzf - -C $@
|
||||
itest:
|
||||
./.dapper integration-test 2>&1 | tee dist/itest.log
|
||||
|
||||
openstack:
|
||||
cp dist/artifacts/rancheros.iso scripts/images/openstack/
|
||||
cd scripts/images/openstack && ../../../.dapper
|
||||
cp ./scripts/images/openstack/dist/*.img dist/
|
||||
|
||||
dist/artifacts/initrd: bin/ros assets/docker assets/selinux/policy.29 build/kernel/ build/images.tar assets/modules.tar.gz
|
||||
mkdir -p $(dir $@)
|
||||
SUFFIX=$(SUFFIX) DFS_IMAGE=$(DFS_IMAGE) DEV_BUILD=$(DEV_BUILD) \
|
||||
KERNEL_RELEASE=$(KERNEL_RELEASE) ARCH=$(ARCH) ./scripts/mk-initrd.sh $@
|
||||
openstack-run:
|
||||
qemu-system-x86_64 -curses \
|
||||
-net nic -net user \
|
||||
-m 2048M \
|
||||
--hdc scripts/images/openstack/dist/rancheros-openstack.img
|
||||
|
||||
rpi: release
|
||||
# scripts/images/raspberry-pi-hypriot/dist/rancheros-raspberry-pi.zip
|
||||
cp dist/artifacts/rootfs_arm.tar.gz scripts/images/raspberry-pi-hypriot/
|
||||
cd scripts/images/raspberry-pi-hypriot/ \
|
||||
&& ../../../.dapper
|
||||
|
||||
dist/artifacts/rancheros.iso: minimal
|
||||
./scripts/mk-rancheros-iso.sh
|
||||
help:
|
||||
@./scripts/help
|
||||
|
||||
all: minimal installer iso
|
||||
.DEFAULT_GOAL := default
|
||||
|
||||
initrd: dist/artifacts/initrd
|
||||
|
||||
minimal: initrd dist/artifacts/vmlinuz
|
||||
|
||||
iso: dist/artifacts/rancheros.iso dist/artifacts/iso-checksums.txt
|
||||
|
||||
test: minimal
|
||||
./scripts/unit-test
|
||||
cd tests/integration && HOST_ARCH=$(HOST_ARCH) ARCH=$(ARCH) tox
|
||||
|
||||
.PHONY: all minimal initrd iso installer test
|
||||
|
||||
endif
|
||||
|
||||
|
||||
build/os-config.yml: build/host_ros
|
||||
ARCH=$(ARCH) VERSION=$(VERSION) ./scripts/gen-os-config.sh $@
|
||||
|
||||
|
||||
build/images.tar: build/host_ros build/os-config.yml
|
||||
ARCH=$(ARCH) FORCE_PULL=$(FORCE_PULL) ./scripts/mk-images-tar.sh
|
||||
|
||||
|
||||
dist/artifacts/rootfs.tar.gz: bin/ros assets/docker build/images.tar assets/selinux/policy.29 assets/modules.tar.gz
|
||||
mkdir -p $(dir $@)
|
||||
SUFFIX=$(SUFFIX) DFS_IMAGE=$(DFS_IMAGE) DEV_BUILD=$(DEV_BUILD) IS_ROOTFS=1 ./scripts/mk-initrd.sh $@
|
||||
|
||||
|
||||
dist/artifacts/iso-checksums.txt: dist/artifacts/rancheros.iso
|
||||
./scripts/mk-iso-checksums-txt.sh
|
||||
|
||||
|
||||
version:
|
||||
@echo $(VERSION)
|
||||
|
||||
rootfs: dist/artifacts/rootfs.tar.gz
|
||||
|
||||
.PHONY: rootfs version bin/ros
|
||||
.PHONY: $(TARGETS)
|
||||
|
||||
97
README.md
97
README.md
@@ -10,75 +10,82 @@ a container that runs the user Docker. The user Docker is then the instance tha
|
||||
used to create containers. We created this separation because it seemed logical and also
|
||||
it would really be bad if somebody did `docker rm -f $(docker ps -qa)` and deleted the entire OS.
|
||||
|
||||

|
||||
|
||||
## Latest Release
|
||||
|
||||
**v0.4.3 - Docker 1.10.1- Linux 4.2.8**
|
||||
**v0.9.1 - Docker 1.12.6 - Linux 4.9.18**
|
||||
|
||||
### ISO
|
||||
|
||||
https://releases.rancher.com/os/latest/rancheros.iso
|
||||
https://releases.rancher.com/os/v0.4.3/rancheros.iso
|
||||
|
||||
**Note**: you must login using `rancher` for username and password.
|
||||
https://releases.rancher.com/os/latest/rancheros.iso
|
||||
https://releases.rancher.com/os/v0.9.1/rancheros.iso
|
||||
|
||||
### Additional Downloads
|
||||
|
||||
#### Latest Links
|
||||
|
||||
* https://releases.rancher.com/os/latest/initrd
|
||||
* https://releases.rancher.com/os/latest/initrd-v0.9.1
|
||||
* https://releases.rancher.com/os/latest/iso-checksums.txt
|
||||
* https://releases.rancher.com/os/latest/rancheros-v0.4.3.tar.gz
|
||||
* https://releases.rancher.com/os/latest/rancheros-openstack.img
|
||||
* https://releases.rancher.com/os/latest/rancheros.iso
|
||||
* https://releases.rancher.com/os/latest/rancheros-v0.9.1.tar.gz
|
||||
* https://releases.rancher.com/os/latest/rootfs.tar.gz
|
||||
* https://releases.rancher.com/os/latest/vmlinuz
|
||||
* https://releases.rancher.com/os/latest/vmlinuz-4.9.18-rancher
|
||||
|
||||
* https://releases.rancher.com/os/v0.4.3/initrd
|
||||
* https://releases.rancher.com/os/v0.4.3/iso-checksums.txt
|
||||
* https://releases.rancher.com/os/v0.4.3/rancheros-v0.4.3.tar.gz
|
||||
* https://releases.rancher.com/os/v0.4.3/rancheros.iso
|
||||
* https://releases.rancher.com/os/v0.4.3/vmlinuz
|
||||
#### v0.9.1 Links
|
||||
|
||||
**Note**: you can use `http` instead of `https` in the above URLs, e.g. for iPXE.
|
||||
* https://releases.rancher.com/os/v0.9.1/initrd
|
||||
* https://releases.rancher.com/os/v0.9.1/initrd-v0.9.1
|
||||
* https://releases.rancher.com/os/v0.9.1/iso-checksums.txt
|
||||
* https://releases.rancher.com/os/v0.9.1/rancheros-openstack.img
|
||||
* https://releases.rancher.com/os/v0.9.1/rancheros.iso
|
||||
* https://releases.rancher.com/os/v0.9.1/rancheros-v0.9.1.tar.gz
|
||||
* https://releases.rancher.com/os/v0.9.1/rootfs.tar.gz
|
||||
* https://releases.rancher.com/os/v0.9.1/vmlinuz
|
||||
* https://releases.rancher.com/os/v0.9.1/vmlinuz-4.9.18-rancher
|
||||
|
||||
#### v0.9.0 Links
|
||||
|
||||
* https://releases.rancher.com/os/v0.9.0/rootfs_arm.tar.gz
|
||||
* https://releases.rancher.com/os/v0.9.0/rootfs_arm64.tar.gz
|
||||
* https://releases.rancher.com/os/v0.9.0/rancheros-raspberry-pi.zip
|
||||
|
||||
**Note**: you can use `http` instead of `https` in the above URLs, e.g. for iPXE.
|
||||
|
||||
### Amazon
|
||||
|
||||
We have 2 different [virtualization types of AMIs](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/virtualization_types.html). SSH keys are added to the **`rancher`** user, so you must log in using the **rancher** user.
|
||||
SSH keys are added to the **`rancher`** user, so you must log in using the **rancher** user.
|
||||
|
||||
**HVM**
|
||||
|
||||
Region | Type | AMI |
|
||||
-------|------|------
|
||||
ap-northeast-1 | HVM | [ami-58dbdd36](https://console.aws.amazon.com/ec2/home?region=ap-northeast-1#launchInstanceWizard:ami=ami-58dbdd36)
|
||||
ap-southeast-1 | HVM | [ami-217eb042](https://console.aws.amazon.com/ec2/home?region=ap-southeast-1#launchInstanceWizard:ami=ami-217eb042)
|
||||
ap-southeast-2 | HVM | [ami-8da483ee](https://console.aws.amazon.com/ec2/home?region=ap-southeast-2#launchInstanceWizard:ami=ami-8da483ee)
|
||||
eu-central-1 | HVM | [ami-62b0ab0e](https://console.aws.amazon.com/ec2/home?region=eu-central-1#launchInstanceWizard:ami=ami-62b0ab0e)
|
||||
eu-west-1 | HVM | [ami-7de95b0e](https://console.aws.amazon.com/ec2/home?region=eu-west-1#launchInstanceWizard:ami=ami-7de95b0e)
|
||||
sa-east-1 | HVM | [ami-70bc3f1c](https://console.aws.amazon.com/ec2/home?region=sa-east-1#launchInstanceWizard:ami=ami-70bc3f1c)
|
||||
us-east-1 | HVM | [ami-6f023505](https://console.aws.amazon.com/ec2/home?region=us-east-1#launchInstanceWizard:ami=ami-6f023505)
|
||||
us-west-1 | HVM | [ami-17780977](https://console.aws.amazon.com/ec2/home?region=us-west-1#launchInstanceWizard:ami=ami-17780977)
|
||||
us-west-2 | HVM | [ami-e45fbf84](https://console.aws.amazon.com/ec2/home?region=us-west-2#launchInstanceWizard:ami=ami-e45fbf84)
|
||||
ap-south-1 | HVM | [ami-fd1e6d92](https://ap-south-1.console.aws.amazon.com/ec2/home?region=ap-south-1#launchInstanceWizard:ami=ami-fd1e6d92)
|
||||
eu-west-2 | HVM | [ami-51776335](https://eu-west-2.console.aws.amazon.com/ec2/home?region=eu-west-2#launchInstanceWizard:ami=ami-51776335)
|
||||
eu-west-1 | HVM | [ami-481e232e](https://eu-west-1.console.aws.amazon.com/ec2/home?region=eu-west-1#launchInstanceWizard:ami=ami-481e232e)
|
||||
ap-northeast-2 | HVM | [ami-c32efdad](https://ap-northeast-2.console.aws.amazon.com/ec2/home?region=ap-northeast-2#launchInstanceWizard:ami=ami-c32efdad)
|
||||
ap-northeast-1 | HVM | [ami-33aaf154](https://ap-northeast-1.console.aws.amazon.com/ec2/home?region=ap-northeast-1#launchInstanceWizard:ami=ami-33aaf154)
|
||||
sa-east-1 | HVM | [ami-15ed8d79](https://sa-east-1.console.aws.amazon.com/ec2/home?region=sa-east-1#launchInstanceWizard:ami=ami-15ed8d79)
|
||||
ca-central-1 | HVM | [ami-e61fa282](https://ca-central-1.console.aws.amazon.com/ec2/home?region=ca-central-1#launchInstanceWizard:ami=ami-e61fa282)
|
||||
ap-southeast-1 | HVM | [ami-63b50900](https://ap-southeast-1.console.aws.amazon.com/ec2/home?region=ap-southeast-1#launchInstanceWizard:ami=ami-63b50900)
|
||||
ap-southeast-2 | HVM | [ami-86b7bbe5](https://ap-southeast-2.console.aws.amazon.com/ec2/home?region=ap-southeast-2#launchInstanceWizard:ami=ami-86b7bbe5)
|
||||
eu-central-1 | HVM | [ami-a71ecfc8](https://eu-central-1.console.aws.amazon.com/ec2/home?region=eu-central-1#launchInstanceWizard:ami=ami-a71ecfc8)
|
||||
us-east-1 | HVM | [ami-37b00f21](https://us-east-1.console.aws.amazon.com/ec2/home?region=us-east-1#launchInstanceWizard:ami=ami-37b00f21)
|
||||
us-east-2 | HVM | [ami-c61632a3](https://us-east-2.console.aws.amazon.com/ec2/home?region=us-east-2#launchInstanceWizard:ami=ami-c61632a3)
|
||||
us-west-1 | HVM | [ami-8998c3e9](https://us-west-1.console.aws.amazon.com/ec2/home?region=us-west-1#launchInstanceWizard:ami=ami-8998c3e9)
|
||||
us-west-2 | HVM | [ami-f6910496](https://us-west-2.console.aws.amazon.com/ec2/home?region=us-west-2#launchInstanceWizard:ami=ami-f6910496)
|
||||
|
||||
**Paravirtual**
|
||||
|
||||
Region | Type | AMI
|
||||
---|--- | ---
|
||||
ap-northeast-1 | PV | [ami-c9d8dea7](https://console.aws.amazon.com/ec2/home?region=ap-northeast-1#launchInstanceWizard:ami=ami-c9d8dea7)
|
||||
ap-southeast-1 | PV | [ami-727cb211](https://console.aws.amazon.com/ec2/home?region=ap-southeast-1#launchInstanceWizard:ami=ami-727cb211)
|
||||
ap-southeast-2 | PV | [ami-faa18699](https://console.aws.amazon.com/ec2/home?region=ap-southeast-2#launchInstanceWizard:ami=ami-faa18699)
|
||||
eu-central-1 | PV | [ami-11b5ae7d](https://console.aws.amazon.com/ec2/home?region=eu-central-1#launchInstanceWizard:ami=ami-11b5ae7d)
|
||||
eu-west-1 | PV | [ami-8fe755fc](https://console.aws.amazon.com/ec2/home?region=eu-west-1#launchInstanceWizard:ami=ami-8fe755fc)
|
||||
sa-east-1 | PV | [ami-9b4ac9f7](https://console.aws.amazon.com/ec2/home?region=sa-east-1#launchInstanceWizard:ami=ami-9b4ac9f7)
|
||||
us-east-1 | PV | [ami-b70136dd](https://console.aws.amazon.com/ec2/home?region=us-east-1#launchInstanceWizard:ami=ami-b70136dd)
|
||||
us-west-1 | PV | [ami-4a7a0b2a](https://console.aws.amazon.com/ec2/home?region=us-west-1#launchInstanceWizard:ami=ami-4a7a0b2a)
|
||||
us-west-2 | PV | [ami-d0a340b0](https://console.aws.amazon.com/ec2/home?region=us-west-2#launchInstanceWizard:ami=ami-d0a340b0)
|
||||
### Google Compute Engine
|
||||
|
||||
### Google Compute Engine (Experimental)
|
||||
We are providing a disk image that users can download and import for use in Google Compute Engine. The image can be obtained from the release artifacts for RancherOS.
|
||||
|
||||
We are providing a disk image that users can download and import for use in Google Compute Engine. The image can be obtained from the release artifacts for RancherOS v0.3.0 or later.
|
||||
[Download Image](https://github.com/rancher/os/releases/download/v0.9.1/rancheros-v0.9.1.tar.gz)
|
||||
|
||||
[Download Image](https://github.com/rancher/os/releases/download/v0.4.3/rancheros-v0.4.3.tar.gz)
|
||||
|
||||
Please follow the directions at our [docs to launch in GCE](http://docs.rancher.com/os/running-rancheros/cloud/gce/).
|
||||
|
||||
#### Known issues/ToDos
|
||||
* Add GCE daemon support. (Manages users)
|
||||
Please follow the directions at our [docs to launch in GCE](http://docs.rancher.com/os/running-rancheros/cloud/gce/).
|
||||
|
||||
## Documentation for RancherOS
|
||||
|
||||
@@ -87,12 +94,15 @@ Please refer to our [RancherOS Documentation](http://docs.rancher.com/os/) websi
|
||||
## Support, Discussion, and Community
|
||||
If you need any help with RancherOS or Rancher, please join us at either our [Rancher forums](http://forums.rancher.com) or [#rancher IRC channel](http://webchat.freenode.net/?channels=rancher) where most of our team hangs out at.
|
||||
|
||||
For security issues, please email security@rancher.com instead of posting a public issue in GitHub. You may (but are not required to) use the GPG key located on [Keybase](https://keybase.io/rancher).
|
||||
|
||||
|
||||
Please submit any **RancherOS** bugs, issues, and feature requests to [rancher/os](//github.com/rancher/os/issues).
|
||||
|
||||
Please submit any **Rancher** bugs, issues, and feature requests to [rancher/rancher](//github.com/rancher/rancher/issues).
|
||||
|
||||
#License
|
||||
Copyright (c) 2014-2016 [Rancher Labs, Inc.](http://rancher.com)
|
||||
Copyright (c) 2014-2017 [Rancher Labs, Inc.](http://rancher.com)
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -105,4 +115,3 @@ 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.
|
||||
|
||||
|
||||
12
assets/docker/cni/bridge.d/bridge.conf
Normal file
12
assets/docker/cni/bridge.d/bridge.conf
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "bridge",
|
||||
"type": "bridge",
|
||||
"bridge": "docker-sys",
|
||||
"isDefaultGateway": true,
|
||||
"ipMasq": true,
|
||||
"hairpinMode": true,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "172.18.42.1/16"
|
||||
}
|
||||
}
|
||||
1
assets/docker/cni/default.d
Symbolic link
1
assets/docker/cni/default.d
Symbolic link
@@ -0,0 +1 @@
|
||||
bridge.d/
|
||||
7
assets/docker/hooks/poststop.d/network.json
Normal file
7
assets/docker/hooks/poststop.d/network.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"path": "/usr/bin/ros",
|
||||
"args": [
|
||||
"cni-glue",
|
||||
"poststop"
|
||||
]
|
||||
}
|
||||
6
assets/docker/hooks/prestart.d/network.json
Normal file
6
assets/docker/hooks/prestart.d/network.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"path": "/usr/bin/ros",
|
||||
"args": [
|
||||
"cni-glue"
|
||||
]
|
||||
}
|
||||
18
assets/go-dnsclient.patch
Normal file
18
assets/go-dnsclient.patch
Normal file
@@ -0,0 +1,18 @@
|
||||
296a297,300
|
||||
> conf.update(name)
|
||||
> }
|
||||
>
|
||||
> func (conf *resolverConfig) update(name string) {
|
||||
300a305,316
|
||||
> }
|
||||
>
|
||||
> func UpdateDnsConf() {
|
||||
> resolvConf.initOnce.Do(resolvConf.init)
|
||||
>
|
||||
> // Ensure only one update at a time checks resolv.conf.
|
||||
> if !resolvConf.tryAcquireSema() {
|
||||
> return
|
||||
> }
|
||||
> defer resolvConf.releaseSema()
|
||||
>
|
||||
> resolvConf.update("/etc/resolv.conf")
|
||||
@@ -1 +0,0 @@
|
||||
system_r:kernel_t:s0
|
||||
1
assets/selinux/ros/contexts/failsafe_context
Normal file
1
assets/selinux/ros/contexts/failsafe_context
Normal file
@@ -0,0 +1 @@
|
||||
unconfined_r:unconfined_t:s0
|
||||
1
assets/selinux/ros/seusers
Normal file
1
assets/selinux/ros/seusers
Normal file
@@ -0,0 +1 @@
|
||||
__default__:unconfined_u:s0-s0:c0.c1023
|
||||
@@ -1 +0,0 @@
|
||||
__default__:system_u:s0-s0
|
||||
@@ -1,8 +0,0 @@
|
||||
IMAGE_NAME=rancher/os
|
||||
VERSION=v0.4.4-dev
|
||||
DFS_IMAGE=rancher/docker:v1.10.3
|
||||
SELINUX_POLICY_URL=https://github.com/rancher/refpolicy/releases/download/v0.0.1/policy.29
|
||||
|
||||
HOSTNAME_DEFAULT=rancher
|
||||
OS_IMAGES_ROOT=rancher
|
||||
OS_SERVICES_REPO=https://raw.githubusercontent.com/rancher/os-services
|
||||
@@ -1,7 +0,0 @@
|
||||
DAPPER_BASE=ubuntu:16.04
|
||||
TOOLCHAIN= #empty
|
||||
|
||||
COMPILED_KERNEL_URL=https://github.com/rancher/os-kernel/releases/download/Ubuntu-4.2.0-34.39-rancher/linux-4.2.8-ckt4-rancher-x86.tar.gz
|
||||
DOCKER_BINARY_URL=https://get.docker.com/builds/Linux/x86_64/docker-1.10.3
|
||||
OS_RELEASES_YML=https://releases.rancher.com/os/releases.yml
|
||||
VBOX_MODULES_URL=https://github.com/rancher/os-vbox/releases/download/v0.0.2/vbox-modules.tar.gz
|
||||
@@ -1,6 +0,0 @@
|
||||
DAPPER_BASE=armhf/ubuntu:16.04
|
||||
TOOLCHAIN=arm-linux-gnueabihf
|
||||
|
||||
COMPILED_KERNEL_URL= #empty
|
||||
DOCKER_BINARY_URL=https://github.com/rancher/docker/releases/download/v1.10.3-ros1/docker-1.10.3_arm
|
||||
OS_RELEASES_YML=https://releases.rancher.com/os/releases_arm.yml
|
||||
@@ -1,6 +0,0 @@
|
||||
DAPPER_BASE=aarch64/ubuntu:16.04
|
||||
TOOLCHAIN=aarch64-linux-gnu
|
||||
|
||||
COMPILED_KERNEL_URL=https://github.com/imikushin/os-kernel/releases/download/Estuary-4.1.18-arm64-3/linux-4.1.18-arm64.tar.gz
|
||||
DOCKER_BINARY_URL=https://github.com/rancher/docker/releases/download/v1.10.3-ros1/docker-1.10.3_arm64
|
||||
OS_RELEASES_YML=https://releases.rancher.com/os/releases_arm64.yml
|
||||
20
build.sh
20
build.sh
@@ -1,20 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
export ARCH=${ARCH:-amd64}
|
||||
|
||||
cd $(dirname $0)
|
||||
|
||||
if [ "$1" != "--dev" ]; then
|
||||
echo
|
||||
echo Running \"production\" build. Will use lzma to compress initrd, which is somewhat slow...
|
||||
echo Ctrl+C if you don\'t want this.
|
||||
echo
|
||||
echo For \"developer\" builds, run ./build.sh --dev
|
||||
echo
|
||||
./scripts/make.sh all
|
||||
else
|
||||
./scripts/make.sh DEV_BUILD=1 all
|
||||
fi
|
||||
|
||||
ls -lh dist/artifacts
|
||||
@@ -1,20 +0,0 @@
|
||||
package cloudinit
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
func authorizeSSHKeys(user string, authorizedKeys []string, name string) {
|
||||
for _, authorizedKey := range authorizedKeys {
|
||||
cmd := exec.Command("update-ssh-keys", user, authorizedKey)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{"err": err, "user": user, "auth_key": authorizedKey}).Error("Error updating SSH authorized_keys")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,383 +0,0 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
// Copyright 2015 Rancher Labs, Inc.
|
||||
//
|
||||
// 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.
|
||||
|
||||
package cloudinit
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
yaml "github.com/cloudfoundry-incubator/candiedyaml"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/coreos/coreos-cloudinit/config"
|
||||
"github.com/coreos/coreos-cloudinit/datasource"
|
||||
"github.com/coreos/coreos-cloudinit/datasource/configdrive"
|
||||
"github.com/coreos/coreos-cloudinit/datasource/file"
|
||||
"github.com/coreos/coreos-cloudinit/datasource/metadata/digitalocean"
|
||||
"github.com/coreos/coreos-cloudinit/datasource/metadata/ec2"
|
||||
"github.com/coreos/coreos-cloudinit/datasource/metadata/packet"
|
||||
"github.com/coreos/coreos-cloudinit/datasource/proc_cmdline"
|
||||
"github.com/coreos/coreos-cloudinit/datasource/url"
|
||||
"github.com/coreos/coreos-cloudinit/pkg"
|
||||
"github.com/coreos/coreos-cloudinit/system"
|
||||
"github.com/rancher/netconf"
|
||||
rancherConfig "github.com/rancher/os/config"
|
||||
)
|
||||
|
||||
const (
|
||||
datasourceInterval = 100 * time.Millisecond
|
||||
datasourceMaxInterval = 30 * time.Second
|
||||
datasourceTimeout = 5 * time.Minute
|
||||
sshKeyName = "rancheros-cloud-config"
|
||||
)
|
||||
|
||||
var (
|
||||
save bool
|
||||
execute bool
|
||||
network bool
|
||||
flags *flag.FlagSet
|
||||
)
|
||||
|
||||
func init() {
|
||||
flags = flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
|
||||
flags.BoolVar(&network, "network", true, "use network based datasources")
|
||||
flags.BoolVar(&save, "save", false, "save cloud config and exit")
|
||||
flags.BoolVar(&execute, "execute", false, "execute saved cloud config")
|
||||
}
|
||||
|
||||
func saveFiles(cloudConfigBytes, scriptBytes []byte, metadata datasource.Metadata) error {
|
||||
os.MkdirAll(rancherConfig.CloudConfigDir, os.ModeDir|0600)
|
||||
os.Remove(rancherConfig.CloudConfigScriptFile)
|
||||
os.Remove(rancherConfig.CloudConfigBootFile)
|
||||
os.Remove(rancherConfig.MetaDataFile)
|
||||
|
||||
if len(scriptBytes) > 0 {
|
||||
log.Infof("Writing to %s", rancherConfig.CloudConfigScriptFile)
|
||||
if err := ioutil.WriteFile(rancherConfig.CloudConfigScriptFile, scriptBytes, 500); err != nil {
|
||||
log.Errorf("Error while writing file %s: %v", rancherConfig.CloudConfigScriptFile, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(rancherConfig.CloudConfigBootFile, cloudConfigBytes, 400); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infof("Written to %s:\n%s", rancherConfig.CloudConfigBootFile, string(cloudConfigBytes))
|
||||
|
||||
metaDataBytes, err := yaml.Marshal(metadata)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = ioutil.WriteFile(rancherConfig.MetaDataFile, metaDataBytes, 400); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infof("Written to %s:\n%s", rancherConfig.MetaDataFile, string(metaDataBytes))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func currentDatasource() (datasource.Datasource, error) {
|
||||
cfg, err := rancherConfig.LoadConfig()
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{"err": err}).Error("Failed to read rancher config")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dss := getDatasources(cfg)
|
||||
if len(dss) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
ds := selectDatasource(dss)
|
||||
return ds, nil
|
||||
}
|
||||
|
||||
func saveCloudConfig() error {
|
||||
userDataBytes, metadata, err := fetchUserData()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
userData := string(userDataBytes)
|
||||
scriptBytes := []byte{}
|
||||
|
||||
if config.IsScript(userData) {
|
||||
scriptBytes = userDataBytes
|
||||
userDataBytes = []byte{}
|
||||
} else if isCompose(userData) {
|
||||
if userDataBytes, err = composeToCloudConfig(userDataBytes); err != nil {
|
||||
log.Errorf("Failed to convert compose to cloud-config syntax: %v", err)
|
||||
return err
|
||||
}
|
||||
} else if config.IsCloudConfig(userData) {
|
||||
if _, err := rancherConfig.ReadConfig(userDataBytes, false); err != nil {
|
||||
log.WithFields(log.Fields{"cloud-config": userData, "err": err}).Warn("Failed to parse cloud-config, not saving.")
|
||||
userDataBytes = []byte{}
|
||||
}
|
||||
} else {
|
||||
log.Errorf("Unrecognized user-data\n%s", userData)
|
||||
userDataBytes = []byte{}
|
||||
}
|
||||
|
||||
if _, err := rancherConfig.ReadConfig(userDataBytes, false); err != nil {
|
||||
log.WithFields(log.Fields{"cloud-config": userData, "err": err}).Warn("Failed to parse cloud-config")
|
||||
return errors.New("Failed to parse cloud-config")
|
||||
}
|
||||
|
||||
return saveFiles(userDataBytes, scriptBytes, metadata)
|
||||
}
|
||||
|
||||
func fetchUserData() ([]byte, datasource.Metadata, error) {
|
||||
var metadata datasource.Metadata
|
||||
ds, err := currentDatasource()
|
||||
if err != nil || ds == nil {
|
||||
log.Errorf("Failed to select datasource: %v", err)
|
||||
return nil, metadata, err
|
||||
}
|
||||
log.Infof("Fetching user-data from datasource %v", ds.Type())
|
||||
userDataBytes, err := ds.FetchUserdata()
|
||||
if err != nil {
|
||||
log.Errorf("Failed fetching user-data from datasource: %v", err)
|
||||
return nil, metadata, err
|
||||
}
|
||||
log.Infof("Fetching meta-data from datasource of type %v", ds.Type())
|
||||
metadata, err = ds.FetchMetadata()
|
||||
if err != nil {
|
||||
log.Errorf("Failed fetching meta-data from datasource: %v", err)
|
||||
return nil, metadata, err
|
||||
}
|
||||
return userDataBytes, metadata, nil
|
||||
}
|
||||
|
||||
func SetHostname(cc *rancherConfig.CloudConfig) (string, error) {
|
||||
name, _ := os.Hostname()
|
||||
if cc.Hostname != "" {
|
||||
name = cc.Hostname
|
||||
}
|
||||
if name != "" {
|
||||
//set hostname
|
||||
if err := syscall.Sethostname([]byte(name)); err != nil {
|
||||
log.WithFields(log.Fields{"err": err, "hostname": name}).Error("Error setting hostname")
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
return name, nil
|
||||
}
|
||||
|
||||
func executeCloudConfig() error {
|
||||
cc, err := rancherConfig.LoadConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := SetHostname(cc); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(cc.SSHAuthorizedKeys) > 0 {
|
||||
authorizeSSHKeys("rancher", cc.SSHAuthorizedKeys, sshKeyName)
|
||||
authorizeSSHKeys("docker", cc.SSHAuthorizedKeys, sshKeyName)
|
||||
}
|
||||
|
||||
for _, file := range cc.WriteFiles {
|
||||
f := system.File{File: file}
|
||||
fullPath, err := system.WriteFile(&f, "/")
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{"err": err, "path": fullPath}).Error("Error writing file")
|
||||
continue
|
||||
}
|
||||
log.Printf("Wrote file %s to filesystem", fullPath)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Main() {
|
||||
flags.Parse(os.Args[1:])
|
||||
|
||||
log.Infof("Running cloud-init: save=%v, execute=%v", save, execute)
|
||||
|
||||
if save {
|
||||
err := saveCloudConfig()
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{"err": err}).Error("Failed to save cloud-config")
|
||||
}
|
||||
}
|
||||
|
||||
if execute {
|
||||
err := executeCloudConfig()
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{"err": err}).Error("Failed to execute cloud-config")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// getDatasources creates a slice of possible Datasources for cloudinit based
|
||||
// on the different source command-line flags.
|
||||
func getDatasources(cfg *rancherConfig.CloudConfig) []datasource.Datasource {
|
||||
dss := make([]datasource.Datasource, 0, 5)
|
||||
|
||||
for _, ds := range cfg.Rancher.CloudInit.Datasources {
|
||||
parts := strings.SplitN(ds, ":", 2)
|
||||
|
||||
switch parts[0] {
|
||||
case "ec2":
|
||||
if network {
|
||||
if len(parts) == 1 {
|
||||
dss = append(dss, ec2.NewDatasource(ec2.DefaultAddress))
|
||||
} else {
|
||||
dss = append(dss, ec2.NewDatasource(parts[1]))
|
||||
}
|
||||
}
|
||||
case "file":
|
||||
if len(parts) == 2 {
|
||||
dss = append(dss, file.NewDatasource(parts[1]))
|
||||
}
|
||||
case "url":
|
||||
if network {
|
||||
if len(parts) == 2 {
|
||||
dss = append(dss, url.NewDatasource(parts[1]))
|
||||
}
|
||||
}
|
||||
case "cmdline":
|
||||
if network {
|
||||
if len(parts) == 1 {
|
||||
dss = append(dss, proc_cmdline.NewDatasource())
|
||||
}
|
||||
}
|
||||
case "configdrive":
|
||||
if len(parts) == 2 {
|
||||
dss = append(dss, configdrive.NewDatasource(parts[1]))
|
||||
}
|
||||
case "digitalocean":
|
||||
if network {
|
||||
if len(parts) == 1 {
|
||||
dss = append(dss, digitalocean.NewDatasource(digitalocean.DefaultAddress))
|
||||
} else {
|
||||
dss = append(dss, digitalocean.NewDatasource(parts[1]))
|
||||
}
|
||||
} else {
|
||||
enableDoLinkLocal()
|
||||
}
|
||||
case "gce":
|
||||
if network {
|
||||
gceCloudConfigFile, err := GetAndCreateGceDataSourceFilename()
|
||||
if err != nil {
|
||||
log.Errorf("Could not retrieve GCE CloudConfig %s", err)
|
||||
continue
|
||||
}
|
||||
dss = append(dss, file.NewDatasource(gceCloudConfigFile))
|
||||
}
|
||||
case "packet":
|
||||
if !network {
|
||||
enablePacketNetwork(&cfg.Rancher)
|
||||
}
|
||||
dss = append(dss, packet.NewDatasource("https://metadata.packet.net/"))
|
||||
}
|
||||
}
|
||||
|
||||
return dss
|
||||
}
|
||||
|
||||
func enableDoLinkLocal() {
|
||||
err := netconf.ApplyNetworkConfigs(&netconf.NetworkConfig{
|
||||
Interfaces: map[string]netconf.InterfaceConfig{
|
||||
"eth0": {
|
||||
IPV4LL: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("Failed to apply link local on eth0: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// selectDatasource attempts to choose a valid Datasource to use based on its
|
||||
// current availability. The first Datasource to report to be available is
|
||||
// returned. Datasources will be retried if possible if they are not
|
||||
// immediately available. If all Datasources are permanently unavailable or
|
||||
// datasourceTimeout is reached before one becomes available, nil is returned.
|
||||
func selectDatasource(sources []datasource.Datasource) datasource.Datasource {
|
||||
ds := make(chan datasource.Datasource)
|
||||
stop := make(chan struct{})
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for _, s := range sources {
|
||||
wg.Add(1)
|
||||
go func(s datasource.Datasource) {
|
||||
defer wg.Done()
|
||||
|
||||
duration := datasourceInterval
|
||||
for {
|
||||
log.Infof("Checking availability of %q\n", s.Type())
|
||||
if s.IsAvailable() {
|
||||
ds <- s
|
||||
return
|
||||
} else if !s.AvailabilityChanges() {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case <-stop:
|
||||
return
|
||||
case <-time.After(duration):
|
||||
duration = pkg.ExpBackoff(duration, datasourceMaxInterval)
|
||||
}
|
||||
}
|
||||
}(s)
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(done)
|
||||
}()
|
||||
|
||||
var s datasource.Datasource
|
||||
select {
|
||||
case s = <-ds:
|
||||
case <-done:
|
||||
case <-time.After(datasourceTimeout):
|
||||
}
|
||||
|
||||
close(stop)
|
||||
return s
|
||||
}
|
||||
|
||||
func isCompose(content string) bool {
|
||||
return strings.HasPrefix(content, "#compose\n")
|
||||
}
|
||||
|
||||
func composeToCloudConfig(bytes []byte) ([]byte, error) {
|
||||
compose := make(map[interface{}]interface{})
|
||||
err := yaml.Unmarshal(bytes, &compose)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return yaml.Marshal(map[interface{}]interface{}{
|
||||
"rancher": map[interface{}]interface{}{
|
||||
"services": compose,
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
package cloudinit
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
yaml "github.com/cloudfoundry-incubator/candiedyaml"
|
||||
"google.golang.org/cloud/compute/metadata"
|
||||
)
|
||||
|
||||
type GceCloudConfig struct {
|
||||
FileName string
|
||||
UserData string
|
||||
NonUserDataSSHKeys []string
|
||||
}
|
||||
|
||||
const (
|
||||
gceCloudConfigFile = "/var/lib/rancher/conf/gce_cloudinit_config.yml"
|
||||
)
|
||||
|
||||
func NewGceCloudConfig() *GceCloudConfig {
|
||||
|
||||
userData, err := metadata.InstanceAttributeValue("user-data")
|
||||
if err != nil {
|
||||
log.Errorf("Could not retrieve user-data: %s", err)
|
||||
}
|
||||
|
||||
projectSSHKeys, err := metadata.ProjectAttributeValue("sshKeys")
|
||||
if err != nil {
|
||||
log.Errorf("Could not retrieve project SSH Keys: %s", err)
|
||||
}
|
||||
|
||||
instanceSSHKeys, err := metadata.InstanceAttributeValue("sshKeys")
|
||||
if err != nil {
|
||||
log.Errorf("Could not retrieve instance SSH Keys: %s", err)
|
||||
}
|
||||
|
||||
nonUserDataSSHKeysRaw := projectSSHKeys + "\n" + instanceSSHKeys
|
||||
nonUserDataSSHKeys := gceSshKeyFormatter(nonUserDataSSHKeysRaw)
|
||||
|
||||
gceCC := &GceCloudConfig{
|
||||
FileName: gceCloudConfigFile,
|
||||
UserData: userData,
|
||||
NonUserDataSSHKeys: nonUserDataSSHKeys,
|
||||
}
|
||||
|
||||
return gceCC
|
||||
}
|
||||
|
||||
func GetAndCreateGceDataSourceFilename() (string, error) {
|
||||
gceCC := NewGceCloudConfig()
|
||||
err := gceCC.saveToFile(gceCC.FileName)
|
||||
if err != nil {
|
||||
log.Errorf("Error: %s", err)
|
||||
return "", err
|
||||
}
|
||||
return gceCC.FileName, nil
|
||||
}
|
||||
|
||||
func (cc *GceCloudConfig) saveToFile(filename string) error {
|
||||
//Get Merged UserData sshkeys
|
||||
data, err := cc.getMergedUserData()
|
||||
if err != nil {
|
||||
log.Errorf("Could not process userdata: %s", err)
|
||||
return err
|
||||
}
|
||||
//write file
|
||||
writeFile(filename, data)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cc *GceCloudConfig) getMergedUserData() ([]byte, error) {
|
||||
var returnUserData []byte
|
||||
userdata := make(map[string]interface{})
|
||||
|
||||
if cc.UserData != "" {
|
||||
log.Infof("Found UserData Config")
|
||||
err := yaml.Unmarshal([]byte(cc.UserData), &userdata)
|
||||
if err != nil {
|
||||
log.Errorf("Could not unmarshal data: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var auth_keys []string
|
||||
if _, exists := userdata["ssh_authorized_keys"]; exists {
|
||||
udSshKeys := userdata["ssh_authorized_keys"].([]interface{})
|
||||
log.Infof("userdata %s", udSshKeys)
|
||||
|
||||
for _, value := range udSshKeys {
|
||||
auth_keys = append(auth_keys, value.(string))
|
||||
}
|
||||
}
|
||||
if cc.NonUserDataSSHKeys != nil {
|
||||
for _, value := range cc.NonUserDataSSHKeys {
|
||||
auth_keys = append(auth_keys, value)
|
||||
}
|
||||
}
|
||||
userdata["ssh_authorized_keys"] = auth_keys
|
||||
|
||||
yamlUserData, err := yaml.Marshal(&userdata)
|
||||
if err != nil {
|
||||
log.Errorf("Could not Marshal userdata: %s", err)
|
||||
return nil, err
|
||||
} else {
|
||||
returnUserData = append([]byte("#cloud-config\n"), yamlUserData...)
|
||||
}
|
||||
|
||||
return returnUserData, nil
|
||||
}
|
||||
|
||||
func writeFile(filename string, data []byte) error {
|
||||
if err := ioutil.WriteFile(filename, data, 400); err != nil {
|
||||
log.Errorf("Could not write file %v", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func gceSshKeyFormatter(rawKeys string) []string {
|
||||
keySlice := strings.Split(rawKeys, "\n")
|
||||
var cloudFormatedKeys []string
|
||||
|
||||
if len(keySlice) > 0 {
|
||||
for i := range keySlice {
|
||||
keyString := keySlice[i]
|
||||
sIdx := strings.Index(keyString, ":")
|
||||
if sIdx != -1 {
|
||||
key := strings.TrimSpace(keyString[sIdx+1:])
|
||||
keyA := strings.Split(key, " ")
|
||||
key = strings.Join(keyA, " ")
|
||||
if key != "" {
|
||||
cloudFormatedKeys = append(cloudFormatedKeys, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return cloudFormatedKeys
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
package cloudinit
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
yaml "github.com/cloudfoundry-incubator/candiedyaml"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/packethost/packngo/metadata"
|
||||
"github.com/rancher/netconf"
|
||||
rancherConfig "github.com/rancher/os/config"
|
||||
)
|
||||
|
||||
func enablePacketNetwork(cfg *rancherConfig.RancherConfig) {
|
||||
bootStrapped := false
|
||||
for _, v := range cfg.Network.Interfaces {
|
||||
if v.Address != "" {
|
||||
if err := netconf.ApplyNetworkConfigs(&cfg.Network); err != nil {
|
||||
logrus.Errorf("Failed to bootstrap network: %v", err)
|
||||
return
|
||||
}
|
||||
bootStrapped = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !bootStrapped {
|
||||
return
|
||||
}
|
||||
|
||||
c := metadata.NewClient(http.DefaultClient)
|
||||
m, err := c.Metadata.Get()
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to get Packet metadata: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
bondCfg := netconf.InterfaceConfig{
|
||||
Addresses: []string{},
|
||||
BondOpts: map[string]string{
|
||||
"lacp_rate": "1",
|
||||
"xmit_hash_policy": "layer3+4",
|
||||
"downdelay": "200",
|
||||
"updelay": "200",
|
||||
"miimon": "100",
|
||||
"mode": "4",
|
||||
},
|
||||
}
|
||||
netCfg := netconf.NetworkConfig{
|
||||
Interfaces: map[string]netconf.InterfaceConfig{},
|
||||
}
|
||||
for _, iface := range m.Network.Interfaces {
|
||||
netCfg.Interfaces["mac="+iface.Mac] = netconf.InterfaceConfig{
|
||||
Bond: "bond0",
|
||||
}
|
||||
}
|
||||
for _, addr := range m.Network.Addresses {
|
||||
bondCfg.Addresses = append(bondCfg.Addresses, fmt.Sprintf("%s/%d", addr.Address, addr.Cidr))
|
||||
if addr.Gateway != "" {
|
||||
if addr.AddressFamily == 4 {
|
||||
if addr.Public {
|
||||
bondCfg.Gateway = addr.Gateway
|
||||
}
|
||||
} else {
|
||||
bondCfg.GatewayIpv6 = addr.Gateway
|
||||
}
|
||||
}
|
||||
|
||||
if addr.AddressFamily == 4 && strings.HasPrefix(addr.Gateway, "10.") {
|
||||
bondCfg.PostUp = append(bondCfg.PostUp, "ip route add 10.0.0.0/8 via "+addr.Gateway)
|
||||
}
|
||||
}
|
||||
|
||||
netCfg.Interfaces["bond0"] = bondCfg
|
||||
bytes, _ := yaml.Marshal(netCfg)
|
||||
logrus.Debugf("Generated network config: %s", string(bytes))
|
||||
|
||||
cc := rancherConfig.CloudConfig{
|
||||
Rancher: rancherConfig.RancherConfig{
|
||||
Network: netCfg,
|
||||
},
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(path.Dir(rancherConfig.CloudConfigNetworkFile), 0700); err != nil {
|
||||
logrus.Errorf("Failed to create directory for file %s: %v", rancherConfig.CloudConfigNetworkFile, err)
|
||||
}
|
||||
|
||||
if err := rancherConfig.WriteToFile(cc, rancherConfig.CloudConfigNetworkFile); err != nil {
|
||||
logrus.Errorf("Failed to save config file %s: %v", rancherConfig.CloudConfigNetworkFile, err)
|
||||
}
|
||||
}
|
||||
108
cmd/cloudinitexecute/authorize_ssh_keys.go
Normal file
108
cmd/cloudinitexecute/authorize_ssh_keys.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package cloudinitexecute
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/rancher/os/log"
|
||||
"github.com/rancher/os/util"
|
||||
)
|
||||
|
||||
const (
|
||||
sshDirName = ".ssh"
|
||||
authorizedKeysFileName = "authorized_keys"
|
||||
)
|
||||
|
||||
func authorizeSSHKeys(username string, authorizedKeys []string, name string) error {
|
||||
var uid int
|
||||
var gid int
|
||||
var homeDir string
|
||||
|
||||
bytes, err := ioutil.ReadFile("/etc/passwd")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, line := range strings.Split(string(bytes), "\n") {
|
||||
if strings.HasPrefix(line, username) {
|
||||
split := strings.Split(line, ":")
|
||||
if len(split) < 6 {
|
||||
break
|
||||
}
|
||||
uid, err = strconv.Atoi(split[2])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gid, err = strconv.Atoi(split[3])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
homeDir = split[5]
|
||||
}
|
||||
}
|
||||
|
||||
sshDir := path.Join(homeDir, sshDirName)
|
||||
authorizedKeysFile := path.Join(sshDir, authorizedKeysFileName)
|
||||
|
||||
if _, err := os.Stat(sshDir); os.IsNotExist(err) {
|
||||
if err = os.Mkdir(sshDir, 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = os.Chown(sshDir, uid, gid); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, authorizedKey := range authorizedKeys {
|
||||
if err = authorizeSSHKey(authorizedKey, authorizedKeysFile, uid, gid); err != nil {
|
||||
log.Errorf("Failed to authorize SSH key %s: %v", authorizedKey, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func authorizeSSHKey(authorizedKey, authorizedKeysFile string, uid, gid int) error {
|
||||
authorizedKeysFileInfo, err := os.Stat(authorizedKeysFile)
|
||||
if os.IsNotExist(err) {
|
||||
keysFile, err := os.Create(authorizedKeysFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = keysFile.Chmod(0600); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = keysFile.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
authorizedKeysFileInfo, err = os.Stat(authorizedKeysFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bytes, err := ioutil.ReadFile(authorizedKeysFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !strings.Contains(string(bytes), authorizedKey) {
|
||||
bytes = append(bytes, []byte(authorizedKey)...)
|
||||
bytes = append(bytes, '\n')
|
||||
}
|
||||
|
||||
perm := authorizedKeysFileInfo.Mode().Perm()
|
||||
if err = util.WriteFileAtomic(authorizedKeysFile, bytes, perm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.Chown(authorizedKeysFile, uid, gid)
|
||||
}
|
||||
180
cmd/cloudinitexecute/cloudinitexecute.go
Normal file
180
cmd/cloudinitexecute/cloudinitexecute.go
Normal file
@@ -0,0 +1,180 @@
|
||||
package cloudinitexecute
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
rancherConfig "github.com/rancher/os/config"
|
||||
"github.com/rancher/os/config/cloudinit/system"
|
||||
"github.com/rancher/os/docker"
|
||||
"github.com/rancher/os/log"
|
||||
"github.com/rancher/os/util"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
const (
|
||||
resizeStamp = "/var/lib/rancher/resizefs.done"
|
||||
sshKeyName = "rancheros-cloud-config"
|
||||
)
|
||||
|
||||
var (
|
||||
console bool
|
||||
preConsole bool
|
||||
flags *flag.FlagSet
|
||||
)
|
||||
|
||||
func init() {
|
||||
flags = flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
|
||||
flags.BoolVar(&console, "console", false, "apply console configuration")
|
||||
flags.BoolVar(&preConsole, "pre-console", false, "apply pre-console configuration")
|
||||
}
|
||||
|
||||
func Main() {
|
||||
flags.Parse(os.Args[1:])
|
||||
|
||||
log.InitLogger()
|
||||
log.Infof("Running cloud-init-execute: pre-console=%v, console=%v", preConsole, console)
|
||||
|
||||
cfg := rancherConfig.LoadConfig()
|
||||
|
||||
if !console && !preConsole {
|
||||
console = true
|
||||
preConsole = true
|
||||
}
|
||||
|
||||
if console {
|
||||
ApplyConsole(cfg)
|
||||
}
|
||||
if preConsole {
|
||||
applyPreConsole(cfg)
|
||||
}
|
||||
}
|
||||
|
||||
func ApplyConsole(cfg *rancherConfig.CloudConfig) {
|
||||
if len(cfg.SSHAuthorizedKeys) > 0 {
|
||||
if err := authorizeSSHKeys("rancher", cfg.SSHAuthorizedKeys, sshKeyName); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
if err := authorizeSSHKeys("docker", cfg.SSHAuthorizedKeys, sshKeyName); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
WriteFiles(cfg, "console")
|
||||
|
||||
for _, mount := range cfg.Mounts {
|
||||
if len(mount) != 4 {
|
||||
log.Errorf("Unable to mount %s: must specify exactly four arguments", mount[1])
|
||||
}
|
||||
device := util.ResolveDevice(mount[0])
|
||||
|
||||
if mount[2] == "swap" {
|
||||
cmd := exec.Command("swapon", device)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
log.Errorf("Unable to swapon %s: %v", device, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
cmdArgs := []string{device, mount[1]}
|
||||
if mount[2] != "" {
|
||||
cmdArgs = append(cmdArgs, "-t", mount[2])
|
||||
}
|
||||
if mount[3] != "" {
|
||||
cmdArgs = append(cmdArgs, "-o", mount[3])
|
||||
}
|
||||
cmd := exec.Command("mount", cmdArgs...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
log.Errorf("Failed to mount %s: %v", mount[1], err)
|
||||
}
|
||||
}
|
||||
|
||||
util.RunCommandSequence(cfg.Runcmd)
|
||||
}
|
||||
|
||||
func WriteFiles(cfg *rancherConfig.CloudConfig, container string) {
|
||||
for _, file := range cfg.WriteFiles {
|
||||
fileContainer := file.Container
|
||||
if fileContainer == "" {
|
||||
fileContainer = "console"
|
||||
}
|
||||
if fileContainer != container {
|
||||
continue
|
||||
}
|
||||
|
||||
f := system.File{
|
||||
File: file.File,
|
||||
}
|
||||
fullPath, err := system.WriteFile(&f, "/")
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{"err": err, "path": fullPath}).Error("Error writing file")
|
||||
continue
|
||||
}
|
||||
log.Printf("Wrote file %s to filesystem", fullPath)
|
||||
}
|
||||
}
|
||||
|
||||
func applyPreConsole(cfg *rancherConfig.CloudConfig) {
|
||||
if _, err := os.Stat(resizeStamp); os.IsNotExist(err) && cfg.Rancher.ResizeDevice != "" {
|
||||
if err := resizeDevice(cfg); err == nil {
|
||||
os.Create(resizeStamp)
|
||||
} else {
|
||||
log.Errorf("Failed to resize %s: %s", cfg.Rancher.ResizeDevice, err)
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range cfg.Rancher.Sysctl {
|
||||
elems := []string{"/proc", "sys"}
|
||||
elems = append(elems, strings.Split(k, ".")...)
|
||||
path := path.Join(elems...)
|
||||
if err := ioutil.WriteFile(path, []byte(v), 0644); err != nil {
|
||||
log.Errorf("Failed to set sysctl key %s: %s", k, err)
|
||||
}
|
||||
}
|
||||
|
||||
client, err := docker.NewSystemClient()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
for _, restart := range cfg.Rancher.RestartServices {
|
||||
if err = client.ContainerRestart(context.Background(), restart, 10); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func resizeDevice(cfg *rancherConfig.CloudConfig) error {
|
||||
cmd := exec.Command("growpart", cfg.Rancher.ResizeDevice, "1")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Run()
|
||||
|
||||
cmd = exec.Command("partprobe", cfg.Rancher.ResizeDevice)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd = exec.Command("resize2fs", fmt.Sprintf("%s1", cfg.Rancher.ResizeDevice))
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
349
cmd/cloudinitsave/cloudinitsave.go
Executable file
349
cmd/cloudinitsave/cloudinitsave.go
Executable file
@@ -0,0 +1,349 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
// Copyright 2015-2017 Rancher Labs, Inc.
|
||||
//
|
||||
// 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.
|
||||
|
||||
package cloudinitsave
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
yaml "github.com/cloudfoundry-incubator/candiedyaml"
|
||||
|
||||
"github.com/rancher/os/cmd/network"
|
||||
rancherConfig "github.com/rancher/os/config"
|
||||
"github.com/rancher/os/config/cloudinit/config"
|
||||
"github.com/rancher/os/config/cloudinit/datasource"
|
||||
"github.com/rancher/os/config/cloudinit/datasource/configdrive"
|
||||
"github.com/rancher/os/config/cloudinit/datasource/file"
|
||||
"github.com/rancher/os/config/cloudinit/datasource/metadata/digitalocean"
|
||||
"github.com/rancher/os/config/cloudinit/datasource/metadata/ec2"
|
||||
"github.com/rancher/os/config/cloudinit/datasource/metadata/gce"
|
||||
"github.com/rancher/os/config/cloudinit/datasource/metadata/packet"
|
||||
"github.com/rancher/os/config/cloudinit/datasource/proccmdline"
|
||||
"github.com/rancher/os/config/cloudinit/datasource/url"
|
||||
"github.com/rancher/os/config/cloudinit/pkg"
|
||||
"github.com/rancher/os/log"
|
||||
"github.com/rancher/os/netconf"
|
||||
"github.com/rancher/os/util"
|
||||
)
|
||||
|
||||
const (
|
||||
datasourceInterval = 100 * time.Millisecond
|
||||
datasourceMaxInterval = 30 * time.Second
|
||||
datasourceTimeout = 5 * time.Minute
|
||||
)
|
||||
|
||||
func Main() {
|
||||
log.InitLogger()
|
||||
log.Info("Running cloud-init-save")
|
||||
|
||||
if err := SaveCloudConfig(); err != nil {
|
||||
log.Errorf("Failed to save cloud-config: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func SaveCloudConfig() error {
|
||||
log.Debugf("SaveCloudConfig")
|
||||
|
||||
// TODO: can't run these here, but it needs to be triggered from here :()
|
||||
cfg := rancherConfig.LoadConfig()
|
||||
log.Debugf("init: SaveCloudConfig(pre ApplyNetworkConfig): %#v", cfg.Rancher.Network)
|
||||
network.ApplyNetworkConfig(cfg)
|
||||
|
||||
log.Debugf("datasources that will be consided: %#v", cfg.Rancher.CloudInit.Datasources)
|
||||
dss := getDatasources(cfg)
|
||||
if len(dss) == 0 {
|
||||
log.Errorf("currentDatasource - none found")
|
||||
return nil
|
||||
}
|
||||
|
||||
selectDatasource(dss)
|
||||
|
||||
// TODO: can't run these here, but it needs to be triggered from here :()
|
||||
// Apply any newly detected network config.
|
||||
cfg = rancherConfig.LoadConfig()
|
||||
log.Debugf("init: SaveCloudConfig(post ApplyNetworkConfig): %#v", cfg.Rancher.Network)
|
||||
network.ApplyNetworkConfig(cfg)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func RequiresNetwork(datasource string) bool {
|
||||
// TODO: move into the datasources (and metadatasources)
|
||||
// and then we can enable that platforms defaults..
|
||||
parts := strings.SplitN(datasource, ":", 2)
|
||||
requiresNetwork, ok := map[string]bool{
|
||||
"ec2": true,
|
||||
"file": false,
|
||||
"url": true,
|
||||
"cmdline": true,
|
||||
"configdrive": false,
|
||||
"digitalocean": true,
|
||||
"gce": true,
|
||||
"packet": true,
|
||||
}[parts[0]]
|
||||
return ok && requiresNetwork
|
||||
}
|
||||
|
||||
func saveFiles(cloudConfigBytes, scriptBytes []byte, metadata datasource.Metadata) error {
|
||||
os.MkdirAll(rancherConfig.CloudConfigDir, os.ModeDir|0600)
|
||||
|
||||
if len(scriptBytes) > 0 {
|
||||
log.Infof("Writing to %s", rancherConfig.CloudConfigScriptFile)
|
||||
if err := util.WriteFileAtomic(rancherConfig.CloudConfigScriptFile, scriptBytes, 500); err != nil {
|
||||
log.Errorf("Error while writing file %s: %v", rancherConfig.CloudConfigScriptFile, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(cloudConfigBytes) > 0 {
|
||||
if err := util.WriteFileAtomic(rancherConfig.CloudConfigBootFile, cloudConfigBytes, 400); err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO: Don't put secrets into the log
|
||||
log.Infof("Written to %s:\n%s", rancherConfig.CloudConfigBootFile, string(cloudConfigBytes))
|
||||
}
|
||||
|
||||
metaDataBytes, err := yaml.Marshal(metadata)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = util.WriteFileAtomic(rancherConfig.MetaDataFile, metaDataBytes, 400); err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO: Don't put secrets into the log
|
||||
log.Infof("Written to %s:\n%s", rancherConfig.MetaDataFile, string(metaDataBytes))
|
||||
|
||||
// if we write the empty meta yml, the merge fails.
|
||||
// TODO: the problem is that a partially filled one will still have merge issues, so that needs fixing - presumably by making merge more clever, and making more fields optional
|
||||
emptyMeta, err := yaml.Marshal(datasource.Metadata{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if bytes.Compare(metaDataBytes, emptyMeta) == 0 {
|
||||
log.Infof("not writing %s: its all defaults.", rancherConfig.CloudConfigNetworkFile)
|
||||
return nil
|
||||
}
|
||||
|
||||
type nonRancherCfg struct {
|
||||
Network netconf.NetworkConfig `yaml:"network,omitempty"`
|
||||
}
|
||||
type nonCfg struct {
|
||||
Rancher nonRancherCfg `yaml:"rancher,omitempty"`
|
||||
}
|
||||
// write the network.yml file from metadata
|
||||
cc := nonCfg{
|
||||
Rancher: nonRancherCfg{
|
||||
Network: metadata.NetworkConfig,
|
||||
},
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(path.Dir(rancherConfig.CloudConfigNetworkFile), 0700); err != nil {
|
||||
log.Errorf("Failed to create directory for file %s: %v", rancherConfig.CloudConfigNetworkFile, err)
|
||||
}
|
||||
|
||||
if err := rancherConfig.WriteToFile(cc, rancherConfig.CloudConfigNetworkFile); err != nil {
|
||||
log.Errorf("Failed to save config file %s: %v", rancherConfig.CloudConfigNetworkFile, err)
|
||||
}
|
||||
log.Infof("Written to %s:", rancherConfig.CloudConfigNetworkFile)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func fetchAndSave(ds datasource.Datasource) error {
|
||||
var metadata datasource.Metadata
|
||||
|
||||
log.Infof("Fetching user-data from datasource %s", ds)
|
||||
userDataBytes, err := ds.FetchUserdata()
|
||||
if err != nil {
|
||||
log.Errorf("Failed fetching user-data from datasource: %v", err)
|
||||
return err
|
||||
}
|
||||
log.Infof("Fetching meta-data from datasource of type %v", ds.Type())
|
||||
metadata, err = ds.FetchMetadata()
|
||||
if err != nil {
|
||||
log.Errorf("Failed fetching meta-data from datasource: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
userData := string(userDataBytes)
|
||||
scriptBytes := []byte{}
|
||||
|
||||
if config.IsScript(userData) {
|
||||
scriptBytes = userDataBytes
|
||||
userDataBytes = []byte{}
|
||||
} else if isCompose(userData) {
|
||||
if userDataBytes, err = composeToCloudConfig(userDataBytes); err != nil {
|
||||
log.Errorf("Failed to convert compose to cloud-config syntax: %v", err)
|
||||
return err
|
||||
}
|
||||
} else if config.IsCloudConfig(userData) {
|
||||
if _, err := rancherConfig.ReadConfig(userDataBytes, false); err != nil {
|
||||
log.WithFields(log.Fields{"cloud-config": userData, "err": err}).Warn("Failed to parse cloud-config, not saving.")
|
||||
userDataBytes = []byte{}
|
||||
}
|
||||
} else {
|
||||
log.Errorf("Unrecognized user-data\n(%s)", userData)
|
||||
userDataBytes = []byte{}
|
||||
}
|
||||
|
||||
if _, err := rancherConfig.ReadConfig(userDataBytes, false); err != nil {
|
||||
log.WithFields(log.Fields{"cloud-config": userData, "err": err}).Warn("Failed to parse cloud-config")
|
||||
return errors.New("Failed to parse cloud-config")
|
||||
}
|
||||
|
||||
return saveFiles(userDataBytes, scriptBytes, metadata)
|
||||
}
|
||||
|
||||
// getDatasources creates a slice of possible Datasources for cloudinit based
|
||||
// on the different source command-line flags.
|
||||
func getDatasources(cfg *rancherConfig.CloudConfig) []datasource.Datasource {
|
||||
dss := make([]datasource.Datasource, 0, 5)
|
||||
|
||||
for _, ds := range cfg.Rancher.CloudInit.Datasources {
|
||||
parts := strings.SplitN(ds, ":", 2)
|
||||
|
||||
root := ""
|
||||
if len(parts) > 1 {
|
||||
root = parts[1]
|
||||
}
|
||||
|
||||
switch parts[0] {
|
||||
case "ec2":
|
||||
dss = append(dss, ec2.NewDatasource(root))
|
||||
case "file":
|
||||
if root != "" {
|
||||
dss = append(dss, file.NewDatasource(root))
|
||||
}
|
||||
case "url":
|
||||
if root != "" {
|
||||
dss = append(dss, url.NewDatasource(root))
|
||||
}
|
||||
case "cmdline":
|
||||
if len(parts) == 1 {
|
||||
dss = append(dss, proccmdline.NewDatasource())
|
||||
}
|
||||
case "configdrive":
|
||||
if root != "" {
|
||||
dss = append(dss, configdrive.NewDatasource(root))
|
||||
}
|
||||
case "digitalocean":
|
||||
// TODO: should we enableDoLinkLocal() - to avoid the need for the other kernel/oem options?
|
||||
dss = append(dss, digitalocean.NewDatasource(root))
|
||||
case "gce":
|
||||
dss = append(dss, gce.NewDatasource(root))
|
||||
case "packet":
|
||||
dss = append(dss, packet.NewDatasource(root))
|
||||
}
|
||||
}
|
||||
|
||||
return dss
|
||||
}
|
||||
|
||||
func enableDoLinkLocal() {
|
||||
err := netconf.ApplyNetworkConfigs(&netconf.NetworkConfig{
|
||||
Interfaces: map[string]netconf.InterfaceConfig{
|
||||
"eth0": {
|
||||
IPV4LL: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("Failed to apply link local on eth0: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// selectDatasource attempts to choose a valid Datasource to use based on its
|
||||
// current availability. The first Datasource to report to be available is
|
||||
// returned. Datasources will be retried if possible if they are not
|
||||
// immediately available. If all Datasources are permanently unavailable or
|
||||
// datasourceTimeout is reached before one becomes available, nil is returned.
|
||||
func selectDatasource(sources []datasource.Datasource) datasource.Datasource {
|
||||
ds := make(chan datasource.Datasource)
|
||||
stop := make(chan struct{})
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for _, s := range sources {
|
||||
wg.Add(1)
|
||||
go func(s datasource.Datasource) {
|
||||
defer wg.Done()
|
||||
|
||||
duration := datasourceInterval
|
||||
for {
|
||||
log.Infof("cloud-init: Checking availability of %q\n", s.Type())
|
||||
if s.IsAvailable() {
|
||||
log.Infof("cloud-init: Datasource available: %s", s)
|
||||
ds <- s
|
||||
return
|
||||
}
|
||||
if !s.AvailabilityChanges() {
|
||||
log.Infof("cloud-init: Datasource unavailable, skipping: %s", s)
|
||||
return
|
||||
}
|
||||
log.Errorf("cloud-init: Datasource not ready, will retry: %s", s)
|
||||
select {
|
||||
case <-stop:
|
||||
return
|
||||
case <-time.After(duration):
|
||||
duration = pkg.ExpBackoff(duration, datasourceMaxInterval)
|
||||
}
|
||||
}
|
||||
}(s)
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(done)
|
||||
}()
|
||||
|
||||
var s datasource.Datasource
|
||||
select {
|
||||
case s = <-ds:
|
||||
err := fetchAndSave(s)
|
||||
if err != nil {
|
||||
log.Errorf("Error fetching cloud-init datasource(%s): %s", s, err)
|
||||
}
|
||||
case <-done:
|
||||
case <-time.After(datasourceTimeout):
|
||||
}
|
||||
|
||||
close(stop)
|
||||
return s
|
||||
}
|
||||
|
||||
func isCompose(content string) bool {
|
||||
return strings.HasPrefix(content, "#compose\n")
|
||||
}
|
||||
|
||||
func composeToCloudConfig(bytes []byte) ([]byte, error) {
|
||||
compose := make(map[interface{}]interface{})
|
||||
err := yaml.Unmarshal(bytes, &compose)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return yaml.Marshal(map[interface{}]interface{}{
|
||||
"rancher": map[interface{}]interface{}{
|
||||
"services": compose,
|
||||
},
|
||||
})
|
||||
}
|
||||
116
cmd/control/bootstrap.go
Normal file
116
cmd/control/bootstrap.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package control
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
|
||||
"github.com/rancher/os/config"
|
||||
"github.com/rancher/os/log"
|
||||
"github.com/rancher/os/util"
|
||||
)
|
||||
|
||||
func bootstrapAction(c *cli.Context) error {
|
||||
log.Debugf("bootstrapAction")
|
||||
if err := UdevSettle(); err != nil {
|
||||
log.Errorf("Failed to run udev settle: %v", err)
|
||||
}
|
||||
|
||||
log.Debugf("bootstrapAction: loadingConfig")
|
||||
cfg := config.LoadConfig()
|
||||
|
||||
log.Debugf("bootstrapAction: MdadmScan(%v)", cfg.Rancher.State.MdadmScan)
|
||||
if cfg.Rancher.State.MdadmScan {
|
||||
if err := mdadmScan(); err != nil {
|
||||
log.Errorf("Failed to run mdadm scan: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
stateScript := cfg.Rancher.State.Script
|
||||
log.Debugf("bootstrapAction: stateScript(%v)", stateScript)
|
||||
if stateScript != "" {
|
||||
if err := runStateScript(stateScript); err != nil {
|
||||
log.Errorf("Failed to run state script: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("bootstrapAction: RunCommandSequence(%v)", cfg.Bootcmd)
|
||||
util.RunCommandSequence(cfg.Bootcmd)
|
||||
|
||||
if cfg.Rancher.State.Dev != "" && cfg.Rancher.State.Wait {
|
||||
waitForRoot(cfg)
|
||||
}
|
||||
|
||||
autoformatDevices := cfg.Rancher.State.Autoformat
|
||||
log.Debugf("bootstrapAction: Autoformat(%v)", cfg.Rancher.State.Autoformat)
|
||||
if len(autoformatDevices) > 0 {
|
||||
if err := autoformat(autoformatDevices); err != nil {
|
||||
log.Errorf("Failed to run autoformat: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("bootstrapAction: udev settle2")
|
||||
if err := UdevSettle(); err != nil {
|
||||
log.Errorf("Failed to run udev settle: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func mdadmScan() error {
|
||||
cmd := exec.Command("mdadm", "--assemble", "--scan")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
func runStateScript(script string) error {
|
||||
f, err := ioutil.TempFile("", "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := f.WriteString(script); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := f.Chmod(os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return util.RunScript(f.Name())
|
||||
}
|
||||
|
||||
func waitForRoot(cfg *config.CloudConfig) {
|
||||
var dev string
|
||||
for i := 0; i < 30; i++ {
|
||||
dev = util.ResolveDevice(cfg.Rancher.State.Dev)
|
||||
if dev != "" {
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Millisecond * 1000)
|
||||
}
|
||||
if dev == "" {
|
||||
return
|
||||
}
|
||||
for i := 0; i < 30; i++ {
|
||||
if _, err := os.Stat(dev); err == nil {
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Millisecond * 1000)
|
||||
}
|
||||
}
|
||||
|
||||
func autoformat(autoformatDevices []string) error {
|
||||
cmd := exec.Command("/usr/sbin/auto-format.sh")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Env = []string{
|
||||
"AUTOFORMAT=" + strings.Join(autoformatDevices, " "),
|
||||
}
|
||||
return cmd.Run()
|
||||
}
|
||||
@@ -3,17 +3,19 @@ package control
|
||||
import (
|
||||
"os"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/rancher/os/cmd/control/service"
|
||||
"github.com/rancher/os/config"
|
||||
"github.com/rancher/os/log"
|
||||
)
|
||||
|
||||
func Main() {
|
||||
log.InitLogger()
|
||||
app := cli.NewApp()
|
||||
|
||||
app.Name = os.Args[0]
|
||||
app.Usage = "Control and configure RancherOS"
|
||||
app.Version = config.VERSION
|
||||
app.Version = config.Version
|
||||
app.Author = "Rancher Labs, Inc."
|
||||
app.EnableBashCompletion = true
|
||||
app.Before = func(c *cli.Context) error {
|
||||
@@ -24,6 +26,13 @@ func Main() {
|
||||
}
|
||||
|
||||
app.Commands = []cli.Command{
|
||||
{
|
||||
Name: "bootstrap",
|
||||
Hidden: true,
|
||||
HideHelp: true,
|
||||
SkipFlagParsing: true,
|
||||
Action: bootstrapAction,
|
||||
},
|
||||
{
|
||||
Name: "config",
|
||||
ShortName: "c",
|
||||
@@ -31,35 +40,94 @@ func Main() {
|
||||
HideHelp: true,
|
||||
Subcommands: configSubcommands(),
|
||||
},
|
||||
{
|
||||
Name: "console",
|
||||
Usage: "manage which console container is used",
|
||||
HideHelp: true,
|
||||
Subcommands: consoleSubcommands(),
|
||||
},
|
||||
{
|
||||
Name: "console-init",
|
||||
Hidden: true,
|
||||
HideHelp: true,
|
||||
SkipFlagParsing: true,
|
||||
Action: consoleInitAction,
|
||||
},
|
||||
{
|
||||
Name: "dev",
|
||||
ShortName: "d",
|
||||
Usage: "dev spec",
|
||||
Hidden: true,
|
||||
HideHelp: true,
|
||||
SkipFlagParsing: true,
|
||||
Action: devAction,
|
||||
},
|
||||
{
|
||||
Name: "docker-init",
|
||||
Hidden: true,
|
||||
HideHelp: true,
|
||||
SkipFlagParsing: true,
|
||||
Action: dockerInitAction,
|
||||
},
|
||||
{
|
||||
Name: "engine",
|
||||
Usage: "manage which Docker engine is used",
|
||||
HideHelp: true,
|
||||
Subcommands: engineSubcommands(),
|
||||
},
|
||||
{
|
||||
Name: "entrypoint",
|
||||
Hidden: true,
|
||||
HideHelp: true,
|
||||
SkipFlagParsing: true,
|
||||
Action: entrypointAction,
|
||||
},
|
||||
{
|
||||
Name: "env",
|
||||
ShortName: "e",
|
||||
Usage: "env command",
|
||||
Hidden: true,
|
||||
HideHelp: true,
|
||||
SkipFlagParsing: true,
|
||||
Action: envAction,
|
||||
},
|
||||
serviceCommand(),
|
||||
service.Commands(),
|
||||
{
|
||||
Name: "os",
|
||||
Usage: "operating system upgrade/downgrade",
|
||||
HideHelp: true,
|
||||
Subcommands: osSubcommands(),
|
||||
},
|
||||
{
|
||||
Name: "preload-images",
|
||||
Hidden: true,
|
||||
HideHelp: true,
|
||||
SkipFlagParsing: true,
|
||||
Action: preloadImagesAction,
|
||||
},
|
||||
{
|
||||
Name: "switch-console",
|
||||
Hidden: true,
|
||||
HideHelp: true,
|
||||
SkipFlagParsing: true,
|
||||
Action: switchConsoleAction,
|
||||
},
|
||||
{
|
||||
Name: "tls",
|
||||
Usage: "setup tls configuration",
|
||||
HideHelp: true,
|
||||
Subcommands: tlsConfCommands(),
|
||||
},
|
||||
{
|
||||
Name: "udev-settle",
|
||||
Hidden: true,
|
||||
HideHelp: true,
|
||||
SkipFlagParsing: true,
|
||||
Action: udevSettleAction,
|
||||
},
|
||||
{
|
||||
Name: "user-docker",
|
||||
Hidden: true,
|
||||
HideHelp: true,
|
||||
SkipFlagParsing: true,
|
||||
Action: userDockerAction,
|
||||
},
|
||||
installCommand,
|
||||
selinuxCommand(),
|
||||
}
|
||||
|
||||
@@ -9,11 +9,12 @@ import (
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
yaml "github.com/cloudfoundry-incubator/candiedyaml"
|
||||
"github.com/rancher/os/log"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/rancher/os/config"
|
||||
"github.com/rancher/os/util"
|
||||
)
|
||||
|
||||
func configSubcommands() []cli.Command {
|
||||
@@ -28,17 +29,6 @@ func configSubcommands() []cli.Command {
|
||||
Usage: "set a value",
|
||||
Action: configSet,
|
||||
},
|
||||
{
|
||||
Name: "import",
|
||||
Usage: "import configuration from standard in or a file",
|
||||
Action: runImport,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "input, i",
|
||||
Usage: "File from which to read",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "images",
|
||||
Usage: "List Docker images for a configuration from a file",
|
||||
@@ -79,6 +69,23 @@ func configSubcommands() []cli.Command {
|
||||
Name: "merge",
|
||||
Usage: "merge configuration from stdin",
|
||||
Action: merge,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "input, i",
|
||||
Usage: "File from which to read",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "validate",
|
||||
Usage: "validate configuration from stdin",
|
||||
Action: validate,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "input, i",
|
||||
Usage: "File from which to read",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -89,9 +96,6 @@ func imagesFromConfig(cfg *config.CloudConfig) []string {
|
||||
for _, service := range cfg.Rancher.BootstrapContainers {
|
||||
imagesMap[service.Image] = 1
|
||||
}
|
||||
for _, service := range cfg.Rancher.Autoformat {
|
||||
imagesMap[service.Image] = 1
|
||||
}
|
||||
for _, service := range cfg.Rancher.Services {
|
||||
imagesMap[service.Image] = 1
|
||||
}
|
||||
@@ -100,13 +104,13 @@ func imagesFromConfig(cfg *config.CloudConfig) []string {
|
||||
i := 0
|
||||
for image := range imagesMap {
|
||||
images[i] = image
|
||||
i += 1
|
||||
i++
|
||||
}
|
||||
sort.Strings(images)
|
||||
return images
|
||||
}
|
||||
|
||||
func runImages(c *cli.Context) {
|
||||
func runImages(c *cli.Context) error {
|
||||
configFile := c.String("input")
|
||||
cfg, err := config.ReadConfig(nil, false, configFile)
|
||||
if err != nil {
|
||||
@@ -114,12 +118,14 @@ func runImages(c *cli.Context) {
|
||||
}
|
||||
images := imagesFromConfig(cfg)
|
||||
fmt.Println(strings.Join(images, " "))
|
||||
return nil
|
||||
}
|
||||
|
||||
func runGenerate(c *cli.Context) {
|
||||
func runGenerate(c *cli.Context) error {
|
||||
if err := genTpl(os.Stdin, os.Stdout); err != nil {
|
||||
log.Fatalf("Failed to generate config, err: '%s'", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func genTpl(in io.Reader, out io.Writer) error {
|
||||
@@ -140,76 +146,34 @@ func env2map(env []string) map[string]string {
|
||||
return m
|
||||
}
|
||||
|
||||
func runImport(c *cli.Context) {
|
||||
var input io.ReadCloser
|
||||
var err error
|
||||
input = os.Stdin
|
||||
cfg, err := config.LoadConfig()
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
func configSet(c *cli.Context) error {
|
||||
if c.NArg() < 2 {
|
||||
return nil
|
||||
}
|
||||
|
||||
inputFile := c.String("input")
|
||||
if inputFile != "" {
|
||||
input, err = os.Open(inputFile)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer input.Close()
|
||||
}
|
||||
|
||||
bytes, err := ioutil.ReadAll(input)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
cfg, err = cfg.Import(bytes)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err := cfg.Save(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func configSet(c *cli.Context) {
|
||||
key := c.Args().Get(0)
|
||||
value := c.Args().Get(1)
|
||||
if key == "" {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
cfg, err := config.LoadConfig()
|
||||
err := config.Set(key, value)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
cfg, err = cfg.Set(key, value)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err := cfg.Save(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func configGet(c *cli.Context) {
|
||||
func configGet(c *cli.Context) error {
|
||||
arg := c.Args().Get(0)
|
||||
if arg == "" {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
cfg, err := config.LoadConfig()
|
||||
val, err := config.Get(arg)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{"err": err}).Fatal("config get: failed to load config")
|
||||
}
|
||||
|
||||
val, err := cfg.GetIgnoreOmitEmpty(arg)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{"cfg": cfg, "key": arg, "val": val, "err": err}).Fatal("config get: failed to retrieve value")
|
||||
log.WithFields(log.Fields{"key": arg, "val": val, "err": err}).Fatal("config get: failed to retrieve value")
|
||||
}
|
||||
|
||||
printYaml := false
|
||||
@@ -229,31 +193,25 @@ func configGet(c *cli.Context) {
|
||||
} else {
|
||||
fmt.Println(val)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func merge(c *cli.Context) {
|
||||
bytes, err := ioutil.ReadAll(os.Stdin)
|
||||
func merge(c *cli.Context) error {
|
||||
bytes, err := inputBytes(c)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
cfg, err := config.LoadConfig()
|
||||
if err != nil {
|
||||
if err = config.Merge(bytes); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
cfg, err = cfg.MergeBytes(bytes)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err := cfg.Save(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func export(c *cli.Context) {
|
||||
content, err := config.Dump(c.Bool("private"), c.Bool("full"))
|
||||
func export(c *cli.Context) error {
|
||||
content, err := config.Export(c.Bool("private"), c.Bool("full"))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@@ -262,9 +220,40 @@ func export(c *cli.Context) {
|
||||
if output == "" {
|
||||
fmt.Println(content)
|
||||
} else {
|
||||
err := ioutil.WriteFile(output, []byte(content), 0400)
|
||||
err := util.WriteFileAtomic(output, []byte(content), 0400)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validate(c *cli.Context) error {
|
||||
bytes, err := inputBytes(c)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
validationErrors, err := config.Validate(bytes)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
for _, validationError := range validationErrors.Errors() {
|
||||
log.Error(validationError)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func inputBytes(c *cli.Context) ([]byte, error) {
|
||||
input := os.Stdin
|
||||
inputFile := c.String("input")
|
||||
if inputFile != "" {
|
||||
var err error
|
||||
input, err = os.Open(inputFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer input.Close()
|
||||
}
|
||||
return ioutil.ReadAll(input)
|
||||
}
|
||||
|
||||
170
cmd/control/console.go
Normal file
170
cmd/control/console.go
Normal file
@@ -0,0 +1,170 @@
|
||||
package control
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
composeConfig "github.com/docker/libcompose/config"
|
||||
"github.com/docker/libcompose/project/options"
|
||||
"github.com/rancher/os/cmd/control/service"
|
||||
"github.com/rancher/os/compose"
|
||||
"github.com/rancher/os/config"
|
||||
"github.com/rancher/os/log"
|
||||
"github.com/rancher/os/util"
|
||||
"github.com/rancher/os/util/network"
|
||||
)
|
||||
|
||||
func consoleSubcommands() []cli.Command {
|
||||
return []cli.Command{
|
||||
{
|
||||
Name: "switch",
|
||||
Usage: "switch console without a reboot",
|
||||
Action: consoleSwitch,
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "force, f",
|
||||
Usage: "do not prompt for input",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "no-pull",
|
||||
Usage: "don't pull console image",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "enable",
|
||||
Usage: "set console to be switched on next reboot",
|
||||
Action: consoleEnable,
|
||||
},
|
||||
{
|
||||
Name: "list",
|
||||
Usage: "list available consoles",
|
||||
Action: consoleList,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func consoleSwitch(c *cli.Context) error {
|
||||
if len(c.Args()) != 1 {
|
||||
log.Fatal("Must specify exactly one console to switch to")
|
||||
}
|
||||
newConsole := c.Args()[0]
|
||||
|
||||
cfg := config.LoadConfig()
|
||||
validateConsole(newConsole, cfg)
|
||||
if newConsole == currentConsole() {
|
||||
log.Warnf("Console is already set to %s", newConsole)
|
||||
}
|
||||
|
||||
if !c.Bool("force") {
|
||||
fmt.Println(`Switching consoles will
|
||||
1. destroy the current console container
|
||||
2. log you out
|
||||
3. restart Docker`)
|
||||
if !yes("Continue") {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if !c.Bool("no-pull") && newConsole != "default" {
|
||||
if err := compose.StageServices(cfg, newConsole); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
service, err := compose.CreateService(nil, "switch-console", &composeConfig.ServiceConfigV1{
|
||||
LogDriver: "json-file",
|
||||
Privileged: true,
|
||||
Net: "host",
|
||||
Pid: "host",
|
||||
Image: config.OsBase,
|
||||
Labels: map[string]string{
|
||||
config.ScopeLabel: config.System,
|
||||
},
|
||||
Command: []string{"/usr/bin/ros", "switch-console", newConsole},
|
||||
VolumesFrom: []string{"all-volumes"},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = service.Delete(context.Background(), options.Delete{}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = service.Up(context.Background(), options.Up{}); err != nil {
|
||||
return err
|
||||
}
|
||||
return service.Log(context.Background(), true)
|
||||
}
|
||||
|
||||
func consoleEnable(c *cli.Context) error {
|
||||
if len(c.Args()) != 1 {
|
||||
log.Fatal("Must specify exactly one console to enable")
|
||||
}
|
||||
newConsole := c.Args()[0]
|
||||
|
||||
cfg := config.LoadConfig()
|
||||
validateConsole(newConsole, cfg)
|
||||
|
||||
if newConsole != "default" {
|
||||
if err := compose.StageServices(cfg, newConsole); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := config.Set("rancher.console", newConsole); err != nil {
|
||||
log.Errorf("Failed to update 'rancher.console': %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func consoleList(c *cli.Context) error {
|
||||
cfg := config.LoadConfig()
|
||||
consoles := availableConsoles(cfg)
|
||||
currentConsole := currentConsole()
|
||||
|
||||
for _, console := range consoles {
|
||||
if console == currentConsole {
|
||||
fmt.Printf("current %s\n", console)
|
||||
} else if console == cfg.Rancher.Console {
|
||||
fmt.Printf("enabled %s\n", console)
|
||||
} else {
|
||||
fmt.Printf("disabled %s\n", console)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateConsole(console string, cfg *config.CloudConfig) {
|
||||
consoles := availableConsoles(cfg)
|
||||
if !service.IsLocalOrURL(console) && !util.Contains(consoles, console) {
|
||||
log.Fatalf("%s is not a valid console", console)
|
||||
}
|
||||
}
|
||||
|
||||
func availableConsoles(cfg *config.CloudConfig) []string {
|
||||
consoles, err := network.GetConsoles(cfg.Rancher.Repositories.ToArray())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
consoles = append(consoles, "default")
|
||||
sort.Strings(consoles)
|
||||
return consoles
|
||||
}
|
||||
|
||||
func currentConsole() (console string) {
|
||||
consoleBytes, err := ioutil.ReadFile("/run/console-done")
|
||||
if err == nil {
|
||||
console = strings.TrimSpace(string(consoleBytes))
|
||||
} else {
|
||||
log.Warnf("Failed to detect current console: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
280
cmd/control/console_init.go
Normal file
280
cmd/control/console_init.go
Normal file
@@ -0,0 +1,280 @@
|
||||
package control
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/rancher/os/cmd/cloudinitexecute"
|
||||
"github.com/rancher/os/config"
|
||||
"github.com/rancher/os/log"
|
||||
"github.com/rancher/os/util"
|
||||
)
|
||||
|
||||
const (
|
||||
consoleDone = "/run/console-done"
|
||||
dockerHome = "/home/docker"
|
||||
gettyCmd = "/sbin/agetty"
|
||||
rancherHome = "/home/rancher"
|
||||
startScript = "/opt/rancher/bin/start.sh"
|
||||
)
|
||||
|
||||
type symlink struct {
|
||||
oldname, newname string
|
||||
}
|
||||
|
||||
func ConsoleInitMain() {
|
||||
if err := consoleInitFunc(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func consoleInitAction(c *cli.Context) error {
|
||||
return consoleInitFunc()
|
||||
}
|
||||
|
||||
func createHomeDir(homedir string, uid, gid int) {
|
||||
if _, err := os.Stat(homedir); os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(homedir, 0755); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
if err := os.Chown(homedir, uid, gid); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func consoleInitFunc() error {
|
||||
cfg := config.LoadConfig()
|
||||
|
||||
// Now that we're booted, stop writing debug messages to the console
|
||||
cmd := exec.Command("sudo", "dmesg", "--console-off")
|
||||
if err := cmd.Run(); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
createHomeDir(rancherHome, 1100, 1100)
|
||||
createHomeDir(dockerHome, 1101, 1101)
|
||||
|
||||
password := config.GetCmdline("rancher.password")
|
||||
if password != "" {
|
||||
cmd := exec.Command("chpasswd")
|
||||
cmd.Stdin = strings.NewReader(fmt.Sprint("rancher:", password))
|
||||
if err := cmd.Run(); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
cmd = exec.Command("bash", "-c", `sed -E -i 's/(rancher:.*:).*(:.*:.*:.*:.*:.*:.*)$/\1\2/' /etc/shadow`)
|
||||
if err := cmd.Run(); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := setupSSH(cfg); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
if err := writeRespawn(); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
if err := modifySshdConfig(); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
for _, link := range []symlink{
|
||||
{"/var/lib/rancher/engine/docker", "/usr/bin/docker"},
|
||||
{"/var/lib/rancher/engine/docker-containerd", "/usr/bin/docker-containerd"},
|
||||
{"/var/lib/rancher/engine/docker-containerd-ctr", "/usr/bin/docker-containerd-ctr"},
|
||||
{"/var/lib/rancher/engine/docker-containerd-shim", "/usr/bin/docker-containerd-shim"},
|
||||
{"/var/lib/rancher/engine/dockerd", "/usr/bin/dockerd"},
|
||||
{"/var/lib/rancher/engine/docker-proxy", "/usr/bin/docker-proxy"},
|
||||
{"/var/lib/rancher/engine/docker-runc", "/usr/bin/docker-runc"},
|
||||
{"/usr/share/ros/os-release", "/usr/lib/os-release"},
|
||||
{"/usr/share/ros/os-release", "/etc/os-release"},
|
||||
} {
|
||||
syscall.Unlink(link.newname)
|
||||
if err := os.Symlink(link.oldname, link.newname); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
// font backslashes need to be escaped for when issue is output! (but not the others..)
|
||||
if err := ioutil.WriteFile("/etc/issue", []byte(`
|
||||
, , ______ _ _____ _____TM
|
||||
,------------|'------'| | ___ \\ | | / _ / ___|
|
||||
/ . '-' |- | |_/ /__ _ _ __ ___| |__ ___ _ __ | | | \\ '--.
|
||||
\\/| | | | // _' | '_ \\ / __| '_ \\ / _ \\ '__' | | | |'--. \\
|
||||
| .________.'----' | |\\ \\ (_| | | | | (__| | | | __/ | | \\_/ /\\__/ /
|
||||
| | | | \\_| \\_\\__,_|_| |_|\\___|_| |_|\\___|_| \\___/\\____/
|
||||
\\___/ \\___/ \s \r
|
||||
|
||||
RancherOS `+config.Version+` \n \l
|
||||
`), 0644); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
cmd = exec.Command("bash", "-c", `echo $(/sbin/ifconfig | grep -B1 "inet addr" |awk '{ if ( $1 == "inet" ) { print $2 } else if ( $2 == "Link" ) { printf "%s:" ,$1 } }' |awk -F: '{ print $1 ": " $3}') >> /etc/issue`)
|
||||
if err := cmd.Run(); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
cloudinitexecute.ApplyConsole(cfg)
|
||||
|
||||
if err := util.RunScript(config.CloudConfigScriptFile); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
if err := util.RunScript(startScript); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(consoleDone, []byte(cfg.Rancher.Console), 0644); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
if err := util.RunScript("/etc/rc.local"); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
os.Setenv("TERM", "linux")
|
||||
|
||||
respawnBinPath, err := exec.LookPath("respawn")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return syscall.Exec(respawnBinPath, []string{"respawn", "-f", "/etc/respawn.conf"}, os.Environ())
|
||||
}
|
||||
|
||||
func generateRespawnConf(cmdline string) string {
|
||||
var respawnConf bytes.Buffer
|
||||
|
||||
for i := 1; i < 7; i++ {
|
||||
tty := fmt.Sprintf("tty%d", i)
|
||||
|
||||
respawnConf.WriteString(gettyCmd)
|
||||
if strings.Contains(cmdline, fmt.Sprintf("rancher.autologin=%s", tty)) {
|
||||
respawnConf.WriteString(" --autologin rancher")
|
||||
}
|
||||
respawnConf.WriteString(fmt.Sprintf(" 115200 %s\n", tty))
|
||||
}
|
||||
|
||||
for _, tty := range []string{"ttyS0", "ttyS1", "ttyS2", "ttyS3", "ttyAMA0"} {
|
||||
if !strings.Contains(cmdline, fmt.Sprintf("console=%s", tty)) {
|
||||
continue
|
||||
}
|
||||
|
||||
respawnConf.WriteString(gettyCmd)
|
||||
if strings.Contains(cmdline, fmt.Sprintf("rancher.autologin=%s", tty)) {
|
||||
respawnConf.WriteString(" --autologin rancher")
|
||||
}
|
||||
respawnConf.WriteString(fmt.Sprintf(" 115200 %s\n", tty))
|
||||
}
|
||||
|
||||
respawnConf.WriteString("/usr/sbin/sshd -D")
|
||||
|
||||
return respawnConf.String()
|
||||
}
|
||||
|
||||
func writeRespawn() error {
|
||||
cmdline, err := ioutil.ReadFile("/proc/cmdline")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
respawn := generateRespawnConf(string(cmdline))
|
||||
|
||||
files, err := ioutil.ReadDir("/etc/respawn.conf.d")
|
||||
if err == nil {
|
||||
for _, f := range files {
|
||||
p := path.Join("/etc/respawn.conf.d", f.Name())
|
||||
content, err := ioutil.ReadFile(p)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to read %s: %v", p, err)
|
||||
continue
|
||||
}
|
||||
respawn += fmt.Sprintf("\n%s", string(content))
|
||||
}
|
||||
} else if !os.IsNotExist(err) {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
return ioutil.WriteFile("/etc/respawn.conf", []byte(respawn), 0644)
|
||||
}
|
||||
|
||||
func modifySshdConfig() error {
|
||||
sshdConfig, err := ioutil.ReadFile("/etc/ssh/sshd_config")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sshdConfigString := string(sshdConfig)
|
||||
|
||||
for _, item := range []string{
|
||||
"UseDNS no",
|
||||
"PermitRootLogin no",
|
||||
"ServerKeyBits 2048",
|
||||
"AllowGroups docker",
|
||||
} {
|
||||
match, err := regexp.Match("^"+item, sshdConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !match {
|
||||
sshdConfigString += fmt.Sprintf("%s\n", item)
|
||||
}
|
||||
}
|
||||
|
||||
return ioutil.WriteFile("/etc/ssh/sshd_config", []byte(sshdConfigString), 0644)
|
||||
}
|
||||
|
||||
func setupSSH(cfg *config.CloudConfig) error {
|
||||
for _, keyType := range []string{"rsa", "dsa", "ecdsa", "ed25519"} {
|
||||
outputFile := fmt.Sprintf("/etc/ssh/ssh_host_%s_key", keyType)
|
||||
outputFilePub := fmt.Sprintf("/etc/ssh/ssh_host_%s_key.pub", keyType)
|
||||
|
||||
if _, err := os.Stat(outputFile); err == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
saved, savedExists := cfg.Rancher.SSH.Keys[keyType]
|
||||
pub, pubExists := cfg.Rancher.SSH.Keys[keyType+"-pub"]
|
||||
|
||||
if savedExists && pubExists {
|
||||
// TODO check permissions
|
||||
if err := util.WriteFileAtomic(outputFile, []byte(saved), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := util.WriteFileAtomic(outputFilePub, []byte(pub), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
cmd := exec.Command("bash", "-c", fmt.Sprintf("ssh-keygen -f %s -N '' -t %s", outputFile, keyType))
|
||||
if err := cmd.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
savedBytes, err := ioutil.ReadFile(outputFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pubBytes, err := ioutil.ReadFile(outputFilePub)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config.Set(fmt.Sprintf("rancher.ssh.keys.%s", keyType), string(savedBytes))
|
||||
config.Set(fmt.Sprintf("rancher.ssh.keys.%s-pub", keyType), string(pubBytes))
|
||||
}
|
||||
|
||||
return os.MkdirAll("/var/run/sshd", 0644)
|
||||
}
|
||||
@@ -7,8 +7,9 @@ import (
|
||||
"github.com/rancher/os/util"
|
||||
)
|
||||
|
||||
func devAction(c *cli.Context) {
|
||||
func devAction(c *cli.Context) error {
|
||||
if len(c.Args()) > 0 {
|
||||
fmt.Println(util.ResolveDevice(c.Args()[0]))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
79
cmd/control/docker_init.go
Normal file
79
cmd/control/docker_init.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package control
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/rancher/os/config"
|
||||
"github.com/rancher/os/log"
|
||||
"github.com/rancher/os/util"
|
||||
)
|
||||
|
||||
const (
|
||||
dockerConf = "/var/lib/rancher/conf/docker"
|
||||
dockerDone = "/run/docker-done"
|
||||
dockerLog = "/var/log/docker.log"
|
||||
)
|
||||
|
||||
func dockerInitAction(c *cli.Context) error {
|
||||
for {
|
||||
if _, err := os.Stat(consoleDone); err == nil {
|
||||
break
|
||||
}
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}
|
||||
|
||||
dockerBin := "/usr/bin/docker"
|
||||
for _, binPath := range []string{
|
||||
"/opt/bin",
|
||||
"/usr/local/bin",
|
||||
"/var/lib/rancher/docker",
|
||||
} {
|
||||
if util.ExistsAndExecutable(path.Join(binPath, "dockerd")) {
|
||||
dockerBin = path.Join(binPath, "dockerd")
|
||||
break
|
||||
}
|
||||
if util.ExistsAndExecutable(path.Join(binPath, "docker")) {
|
||||
dockerBin = path.Join(binPath, "docker")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err := syscall.Mount("", "/", "", syscall.MS_SHARED|syscall.MS_REC, ""); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
if err := syscall.Mount("", "/run", "", syscall.MS_SHARED|syscall.MS_REC, ""); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
mountInfo, err := ioutil.ReadFile("/proc/self/mountinfo")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, mount := range strings.Split(string(mountInfo), "\n") {
|
||||
if strings.Contains(mount, "/var/lib/docker /var/lib/docker") && strings.Contains(mount, "rootfs") {
|
||||
os.Setenv("DOCKER_RAMDISK", "1")
|
||||
}
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"bash",
|
||||
"-c",
|
||||
fmt.Sprintf(`[ -e %s ] && source %s; exec /usr/bin/dockerlaunch %s %s $DOCKER_OPTS >> %s 2>&1`, dockerConf, dockerConf, dockerBin, strings.Join(c.Args(), " "), dockerLog),
|
||||
}
|
||||
|
||||
cfg := config.LoadConfig()
|
||||
|
||||
if err := ioutil.WriteFile(dockerDone, []byte(cfg.Rancher.Docker.Engine), 0644); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
return syscall.Exec("/bin/bash", args, os.Environ())
|
||||
}
|
||||
146
cmd/control/engine.go
Normal file
146
cmd/control/engine.go
Normal file
@@ -0,0 +1,146 @@
|
||||
package control
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/docker/libcompose/project/options"
|
||||
"github.com/rancher/os/cmd/control/service"
|
||||
"github.com/rancher/os/compose"
|
||||
"github.com/rancher/os/config"
|
||||
"github.com/rancher/os/log"
|
||||
"github.com/rancher/os/util"
|
||||
"github.com/rancher/os/util/network"
|
||||
)
|
||||
|
||||
func engineSubcommands() []cli.Command {
|
||||
return []cli.Command{
|
||||
{
|
||||
Name: "switch",
|
||||
Usage: "switch Docker engine without a reboot",
|
||||
Action: engineSwitch,
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "force, f",
|
||||
Usage: "do not prompt for input",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "no-pull",
|
||||
Usage: "don't pull console image",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "enable",
|
||||
Usage: "set Docker engine to be switched on next reboot",
|
||||
Action: engineEnable,
|
||||
},
|
||||
{
|
||||
Name: "list",
|
||||
Usage: "list available Docker engines",
|
||||
Action: engineList,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func engineSwitch(c *cli.Context) error {
|
||||
if len(c.Args()) != 1 {
|
||||
log.Fatal("Must specify exactly one Docker engine to switch to")
|
||||
}
|
||||
newEngine := c.Args()[0]
|
||||
|
||||
cfg := config.LoadConfig()
|
||||
validateEngine(newEngine, cfg)
|
||||
|
||||
project, err := compose.GetProject(cfg, true, false)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err = project.Stop(context.Background(), 10, "docker"); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err = compose.LoadSpecialService(project, cfg, "docker", newEngine); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err = project.Up(context.Background(), options.Up{}, "docker"); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err := config.Set("rancher.docker.engine", newEngine); err != nil {
|
||||
log.Errorf("Failed to update rancher.docker.engine: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func engineEnable(c *cli.Context) error {
|
||||
if len(c.Args()) != 1 {
|
||||
log.Fatal("Must specify exactly one Docker engine to enable")
|
||||
}
|
||||
newEngine := c.Args()[0]
|
||||
|
||||
cfg := config.LoadConfig()
|
||||
validateEngine(newEngine, cfg)
|
||||
|
||||
if err := compose.StageServices(cfg, newEngine); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := config.Set("rancher.docker.engine", newEngine); err != nil {
|
||||
log.Errorf("Failed to update 'rancher.docker.engine': %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func engineList(c *cli.Context) error {
|
||||
cfg := config.LoadConfig()
|
||||
engines := availableEngines(cfg)
|
||||
currentEngine := currentEngine()
|
||||
|
||||
for _, engine := range engines {
|
||||
if engine == currentEngine {
|
||||
fmt.Printf("current %s\n", engine)
|
||||
} else if engine == cfg.Rancher.Docker.Engine {
|
||||
fmt.Printf("enabled %s\n", engine)
|
||||
} else {
|
||||
fmt.Printf("disabled %s\n", engine)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateEngine(engine string, cfg *config.CloudConfig) {
|
||||
engines := availableEngines(cfg)
|
||||
if !service.IsLocalOrURL(engine) && !util.Contains(engines, engine) {
|
||||
log.Fatalf("%s is not a valid engine", engine)
|
||||
}
|
||||
}
|
||||
|
||||
func availableEngines(cfg *config.CloudConfig) []string {
|
||||
engines, err := network.GetEngines(cfg.Rancher.Repositories.ToArray())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
sort.Strings(engines)
|
||||
return engines
|
||||
}
|
||||
|
||||
func currentEngine() (engine string) {
|
||||
engineBytes, err := ioutil.ReadFile(dockerDone)
|
||||
if err == nil {
|
||||
engine = strings.TrimSpace(string(engineBytes))
|
||||
} else {
|
||||
log.Warnf("Failed to detect current Docker engine: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
111
cmd/control/entrypoint.go
Normal file
111
cmd/control/entrypoint.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package control
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/rancher/os/log"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/rancher/os/cmd/cloudinitexecute"
|
||||
"github.com/rancher/os/config"
|
||||
"github.com/rancher/os/docker"
|
||||
"github.com/rancher/os/util"
|
||||
)
|
||||
|
||||
const (
|
||||
ca = "/etc/ssl/certs/ca-certificates.crt"
|
||||
caBase = "/etc/ssl/certs/ca-certificates.crt.rancher"
|
||||
)
|
||||
|
||||
func entrypointAction(c *cli.Context) error {
|
||||
if _, err := os.Stat("/host/dev"); err == nil {
|
||||
cmd := exec.Command("mount", "--rbind", "/host/dev", "/dev")
|
||||
if err := cmd.Run(); err != nil {
|
||||
log.Errorf("Failed to mount /dev: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := util.FileCopy(caBase, ca); err != nil && !os.IsNotExist(err) {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
cfg := config.LoadConfig()
|
||||
|
||||
shouldWriteFiles := false
|
||||
for _, file := range cfg.WriteFiles {
|
||||
if file.Container != "" {
|
||||
shouldWriteFiles = true
|
||||
}
|
||||
}
|
||||
|
||||
if shouldWriteFiles {
|
||||
writeFiles(cfg)
|
||||
}
|
||||
|
||||
setupCommandSymlinks()
|
||||
|
||||
if len(os.Args) < 3 {
|
||||
return nil
|
||||
}
|
||||
|
||||
binary, err := exec.LookPath(os.Args[2])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return syscall.Exec(binary, os.Args[2:], os.Environ())
|
||||
}
|
||||
|
||||
func writeFiles(cfg *config.CloudConfig) error {
|
||||
id, err := util.GetCurrentContainerID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client, err := docker.NewSystemClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
info, err := client.ContainerInspect(context.Background(), id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cloudinitexecute.WriteFiles(cfg, info.Name[1:])
|
||||
return nil
|
||||
}
|
||||
|
||||
func setupCommandSymlinks() {
|
||||
for _, powerOperation := range []string{
|
||||
"/sbin/poweroff",
|
||||
"/sbin/shutdown",
|
||||
"/sbin/reboot",
|
||||
"/sbin/halt",
|
||||
"/usr/sbin/poweroff",
|
||||
"/usr/sbin/shutdown",
|
||||
"/usr/sbin/reboot",
|
||||
"/usr/sbin/halt",
|
||||
} {
|
||||
os.Remove(powerOperation)
|
||||
}
|
||||
|
||||
for _, link := range []symlink{
|
||||
{config.RosBin, "/usr/bin/cloud-init-execute"},
|
||||
{config.RosBin, "/usr/bin/cloud-init-save"},
|
||||
{config.RosBin, "/usr/bin/dockerlaunch"},
|
||||
{config.RosBin, "/usr/bin/respawn"},
|
||||
{config.RosBin, "/usr/bin/system-docker"},
|
||||
{config.RosBin, "/usr/sbin/netconf"},
|
||||
{config.RosBin, "/usr/sbin/wait-for-docker"},
|
||||
{config.RosBin, "/sbin/poweroff"},
|
||||
{config.RosBin, "/sbin/reboot"},
|
||||
{config.RosBin, "/sbin/halt"},
|
||||
{config.RosBin, "/sbin/shutdown"},
|
||||
} {
|
||||
if err := os.Symlink(link.oldname, link.newname); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,15 +11,12 @@ import (
|
||||
"github.com/rancher/os/util"
|
||||
)
|
||||
|
||||
func envAction(c *cli.Context) {
|
||||
cfg, err := config.LoadConfig()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
func envAction(c *cli.Context) error {
|
||||
cfg := config.LoadConfig()
|
||||
|
||||
args := c.Args()
|
||||
if len(args) == 0 {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
osEnv := os.Environ()
|
||||
|
||||
@@ -39,4 +36,6 @@ func envAction(c *cli.Context) {
|
||||
if err := syscall.Exec(args[0], args, util.Map2KVPairs(envMap)); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
1028
cmd/control/install.go
Normal file → Executable file
1028
cmd/control/install.go
Normal file → Executable file
File diff suppressed because it is too large
Load Diff
102
cmd/control/install/grub.go
Normal file
102
cmd/control/install/grub.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package install
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/rancher/os/log"
|
||||
)
|
||||
|
||||
func RunGrub(baseName, device string) error {
|
||||
log.Debugf("installGrub")
|
||||
|
||||
//grub-install --boot-directory=${baseName}/boot ${device}
|
||||
cmd := exec.Command("grub-install", "--boot-directory="+baseName+"/boot", device)
|
||||
if err := cmd.Run(); err != nil {
|
||||
log.Errorf("%s", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func grubConfig(menu BootVars) error {
|
||||
log.Debugf("grubConfig")
|
||||
|
||||
filetmpl, err := template.New("grub2config").Parse(`{{define "grub2menu"}}menuentry "{{.Name}}" {
|
||||
set root=(hd0,msdos1)
|
||||
linux /{{.bootDir}}vmlinuz-{{.Version}}-rancheros {{.KernelArgs}} {{.Append}}
|
||||
initrd /{{.bootDir}}initrd-{{.Version}}-rancheros
|
||||
}
|
||||
|
||||
{{end}}
|
||||
set default="0"
|
||||
set timeout="{{.Timeout}}"
|
||||
{{if .Fallback}}set fallback={{.Fallback}}{{end}}
|
||||
|
||||
{{- range .Entries}}
|
||||
{{template "grub2menu" .}}
|
||||
{{- end}}
|
||||
|
||||
`)
|
||||
if err != nil {
|
||||
log.Errorf("grub2config %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
cfgFile := filepath.Join(menu.BaseName, menu.BootDir+"grub/grub.cfg")
|
||||
log.Debugf("grubConfig written to %s", cfgFile)
|
||||
|
||||
f, err := os.Create(cfgFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = filetmpl.Execute(f, menu)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func PvGrubConfig(menu BootVars) error {
|
||||
log.Debugf("pvGrubConfig")
|
||||
|
||||
filetmpl, err := template.New("grublst").Parse(`{{define "grubmenu"}}
|
||||
title RancherOS {{.Version}}-({{.Name}})
|
||||
root (hd0)
|
||||
kernel /${bootDir}vmlinuz-{{.Version}}-rancheros {{.KernelArgs}} {{.Append}}
|
||||
initrd /${bootDir}initrd-{{.Version}}-rancheros
|
||||
|
||||
{{end}}
|
||||
default 0
|
||||
timeout {{.Timeout}}
|
||||
{{if .Fallback}}fallback {{.Fallback}}{{end}}
|
||||
hiddenmenu
|
||||
|
||||
{{- range .Entries}}
|
||||
{{template "grubmenu" .}}
|
||||
{{- end}}
|
||||
|
||||
`)
|
||||
if err != nil {
|
||||
log.Errorf("pv grublst: %s", err)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
cfgFile := filepath.Join(menu.BaseName, menu.BootDir+"grub/menu.lst")
|
||||
log.Debugf("grubMenu written to %s", cfgFile)
|
||||
f, err := os.Create(cfgFile)
|
||||
if err != nil {
|
||||
log.Errorf("Create(%s) %s", cfgFile, err)
|
||||
|
||||
return err
|
||||
}
|
||||
err = filetmpl.Execute(f, menu)
|
||||
if err != nil {
|
||||
log.Errorf("execute %s", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
11
cmd/control/install/install.go
Normal file
11
cmd/control/install/install.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package install
|
||||
|
||||
type MenuEntry struct {
|
||||
Name, BootDir, Version, KernelArgs, Append string
|
||||
}
|
||||
type BootVars struct {
|
||||
BaseName, BootDir string
|
||||
Timeout uint
|
||||
Fallback int
|
||||
Entries []MenuEntry
|
||||
}
|
||||
45
cmd/control/install/syslinux.go
Normal file
45
cmd/control/install/syslinux.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package install
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/rancher/os/log"
|
||||
)
|
||||
|
||||
func syslinuxConfig(menu BootVars) error {
|
||||
log.Debugf("syslinuxConfig")
|
||||
|
||||
filetmpl, err := template.New("syslinuxconfig").Parse(`{{define "syslinuxmenu"}}
|
||||
LABEL {{.Name}}
|
||||
LINUX ../vmlinuz-{{.Version}}-rancheros
|
||||
APPEND {{.KernelArgs}} {{.Append}}
|
||||
INITRD ../initrd-{{.Version}}-rancheros
|
||||
{{end}}
|
||||
TIMEOUT 20 #2 seconds
|
||||
DEFAULT RancherOS-current
|
||||
|
||||
{{- range .Entries}}
|
||||
{{template "syslinuxmenu" .}}
|
||||
{{- end}}
|
||||
|
||||
`)
|
||||
if err != nil {
|
||||
log.Errorf("syslinuxconfig %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
cfgFile := filepath.Join(menu.BaseName, menu.BootDir+"syslinux/syslinux.cfg")
|
||||
log.Debugf("syslinuxConfig written to %s", cfgFile)
|
||||
f, err := os.Create(cfgFile)
|
||||
if err != nil {
|
||||
log.Errorf("Create(%s) %s", cfgFile, err)
|
||||
return err
|
||||
}
|
||||
err = filetmpl.Execute(f, menu)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,21 +1,23 @@
|
||||
package control
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
yaml "github.com/cloudfoundry-incubator/candiedyaml"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
dockerClient "github.com/fsouza/go-dockerclient"
|
||||
yaml "github.com/cloudfoundry-incubator/candiedyaml"
|
||||
"github.com/rancher/os/log"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/docker/libcompose/project"
|
||||
dockerClient "github.com/docker/engine-api/client"
|
||||
composeConfig "github.com/docker/libcompose/config"
|
||||
"github.com/docker/libcompose/project/options"
|
||||
"github.com/rancher/os/cmd/power"
|
||||
"github.com/rancher/os/compose"
|
||||
"github.com/rancher/os/config"
|
||||
@@ -56,7 +58,11 @@ func osSubcommands() []cli.Command {
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "append",
|
||||
Usage: "kernel args to append by kexec",
|
||||
Usage: "append additional kernel parameters",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "upgrade-console",
|
||||
Usage: "upgrade console even if persistent",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -73,31 +79,32 @@ func osSubcommands() []cli.Command {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: this and the getLatestImage should probably move to utils/network and be suitably cached.
|
||||
func getImages() (*Images, error) {
|
||||
upgradeUrl, err := getUpgradeUrl()
|
||||
upgradeURL, err := getUpgradeURL()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var body []byte
|
||||
|
||||
if strings.HasPrefix(upgradeUrl, "/") {
|
||||
body, err = ioutil.ReadFile(upgradeUrl)
|
||||
if strings.HasPrefix(upgradeURL, "/") {
|
||||
body, err = ioutil.ReadFile(upgradeURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
u, err := url.Parse(upgradeUrl)
|
||||
u, err := url.Parse(upgradeURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
q := u.Query()
|
||||
q.Set("current", config.VERSION)
|
||||
q.Set("current", config.Version)
|
||||
u.RawQuery = q.Encode()
|
||||
upgradeUrl = u.String()
|
||||
upgradeURL = u.String()
|
||||
|
||||
resp, err := http.Get(upgradeUrl)
|
||||
resp, err := http.Get(upgradeURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -111,7 +118,7 @@ func getImages() (*Images, error) {
|
||||
return parseBody(body)
|
||||
}
|
||||
|
||||
func osMetaDataGet(c *cli.Context) {
|
||||
func osMetaDataGet(c *cli.Context) error {
|
||||
images, err := getImages()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
@@ -122,14 +129,33 @@ func osMetaDataGet(c *cli.Context) {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, image := range images.Available {
|
||||
_, err := client.InspectImage(image)
|
||||
if err == dockerClient.ErrNoSuchImage {
|
||||
fmt.Println(image, "remote")
|
||||
} else {
|
||||
fmt.Println(image, "local")
|
||||
cfg := config.LoadConfig()
|
||||
runningName := cfg.Rancher.Upgrade.Image + ":" + config.Version
|
||||
|
||||
foundRunning := false
|
||||
for i := len(images.Available) - 1; i >= 0; i-- {
|
||||
image := images.Available[i]
|
||||
_, _, err := client.ImageInspectWithRaw(context.Background(), image, false)
|
||||
local := "local"
|
||||
if dockerClient.IsErrImageNotFound(err) {
|
||||
local = "remote"
|
||||
}
|
||||
available := "available"
|
||||
if image == images.Current {
|
||||
available = "latest"
|
||||
}
|
||||
var running string
|
||||
if image == runningName {
|
||||
foundRunning = true
|
||||
running = "running"
|
||||
}
|
||||
fmt.Println(image, local, available, running)
|
||||
}
|
||||
if !foundRunning {
|
||||
fmt.Println(config.Version, "running")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getLatestImage() (string, error) {
|
||||
@@ -141,7 +167,11 @@ func getLatestImage() (string, error) {
|
||||
return images.Current, nil
|
||||
}
|
||||
|
||||
func osUpgrade(c *cli.Context) {
|
||||
func osUpgrade(c *cli.Context) error {
|
||||
if runtime.GOARCH != "amd64" {
|
||||
log.Fatalf("ros install / upgrade only supported on 'amd64', not '%s'", runtime.GOARCH)
|
||||
}
|
||||
|
||||
image := c.String("image")
|
||||
|
||||
if image == "" {
|
||||
@@ -157,52 +187,59 @@ func osUpgrade(c *cli.Context) {
|
||||
if c.Args().Present() {
|
||||
log.Fatalf("invalid arguments %v", c.Args())
|
||||
}
|
||||
if err := startUpgradeContainer(image, c.Bool("stage"), c.Bool("force"), !c.Bool("no-reboot"), c.Bool("kexec"), c.String("append")); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func osVersion(c *cli.Context) {
|
||||
fmt.Println(config.VERSION)
|
||||
}
|
||||
|
||||
func yes(in *bufio.Reader, question string) bool {
|
||||
fmt.Printf("%s [y/N]: ", question)
|
||||
line, err := in.ReadString('\n')
|
||||
if err != nil {
|
||||
if err := startUpgradeContainer(image, c.Bool("stage"), c.Bool("force"), !c.Bool("no-reboot"), c.Bool("kexec"), c.Bool("upgrade-console"), c.String("append")); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return strings.ToLower(line[0:1]) == "y"
|
||||
return nil
|
||||
}
|
||||
|
||||
func startUpgradeContainer(image string, stage, force, reboot, kexec bool, kernelArgs string) error {
|
||||
in := bufio.NewReader(os.Stdin)
|
||||
func osVersion(c *cli.Context) error {
|
||||
fmt.Println(config.Version)
|
||||
return nil
|
||||
}
|
||||
|
||||
func startUpgradeContainer(image string, stage, force, reboot, kexec bool, upgradeConsole bool, kernelArgs string) error {
|
||||
command := []string{
|
||||
"-t", "rancher-upgrade",
|
||||
"-r", config.VERSION,
|
||||
"-r", config.Version,
|
||||
}
|
||||
|
||||
if kexec {
|
||||
command = append(command, "-k")
|
||||
}
|
||||
|
||||
kernelArgs = strings.TrimSpace(kernelArgs)
|
||||
if kernelArgs != "" {
|
||||
command = append(command, "-a", kernelArgs)
|
||||
kernelArgs = strings.TrimSpace(kernelArgs)
|
||||
if kernelArgs != "" {
|
||||
command = append(command, "-a", kernelArgs)
|
||||
}
|
||||
|
||||
if upgradeConsole {
|
||||
if err := config.Set("rancher.force_console_rebuild", true); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
container, err := compose.CreateService(nil, "os-upgrade", &project.ServiceConfig{
|
||||
fmt.Printf("Upgrading to %s\n", image)
|
||||
confirmation := "Continue"
|
||||
imageSplit := strings.Split(image, ":")
|
||||
if len(imageSplit) > 1 && imageSplit[1] == config.Version+config.Suffix {
|
||||
confirmation = fmt.Sprintf("Already at version %s. Continue anyway", imageSplit[1])
|
||||
}
|
||||
if !force && !yes(confirmation) {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
container, err := compose.CreateService(nil, "os-upgrade", &composeConfig.ServiceConfigV1{
|
||||
LogDriver: "json-file",
|
||||
Privileged: true,
|
||||
Net: "host",
|
||||
Pid: "host",
|
||||
Image: image,
|
||||
Labels: project.NewSliceorMap(map[string]string{
|
||||
config.SCOPE: config.SYSTEM,
|
||||
}),
|
||||
Command: project.NewCommand(command...),
|
||||
Labels: map[string]string{
|
||||
config.ScopeLabel: config.System,
|
||||
},
|
||||
Command: command,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -214,45 +251,32 @@ func startUpgradeContainer(image string, stage, force, reboot, kexec bool, kerne
|
||||
}
|
||||
|
||||
// Only pull image if not found locally
|
||||
if _, err := client.InspectImage(image); err != nil {
|
||||
if err := container.Pull(); err != nil {
|
||||
if _, _, err := client.ImageInspectWithRaw(context.Background(), image, false); err != nil {
|
||||
if err := container.Pull(context.Background()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !stage {
|
||||
imageSplit := strings.Split(image, ":")
|
||||
if len(imageSplit) > 1 && imageSplit[1] == config.VERSION {
|
||||
if !force && !yes(in, fmt.Sprintf("Already at version %s. Continue anyways", imageSplit[1])) {
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("Upgrading to %s\n", image)
|
||||
|
||||
if !force && !yes(in, "Continue") {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// If there is already an upgrade container, delete it
|
||||
// Up() should to this, but currently does not due to a bug
|
||||
if err := container.Delete(); err != nil {
|
||||
if err := container.Delete(context.Background(), options.Delete{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := container.Up(); err != nil {
|
||||
if err := container.Up(context.Background(), options.Up{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := container.Log(); err != nil {
|
||||
if err := container.Log(context.Background(), true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := container.Delete(); err != nil {
|
||||
if err := container.Delete(context.Background(), options.Delete{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if reboot && (force || yes(in, "Continue with reboot")) {
|
||||
if reboot && (force || yes("Continue with reboot")) {
|
||||
log.Info("Rebooting")
|
||||
power.Reboot()
|
||||
}
|
||||
@@ -271,11 +295,7 @@ func parseBody(body []byte) (*Images, error) {
|
||||
return update, nil
|
||||
}
|
||||
|
||||
func getUpgradeUrl() (string, error) {
|
||||
cfg, err := config.LoadConfig()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return cfg.Rancher.Upgrade.Url, nil
|
||||
func getUpgradeURL() (string, error) {
|
||||
cfg := config.LoadConfig()
|
||||
return cfg.Rancher.Upgrade.URL, nil
|
||||
}
|
||||
|
||||
106
cmd/control/preload.go
Normal file
106
cmd/control/preload.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package control
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
|
||||
dockerClient "github.com/docker/engine-api/client"
|
||||
"github.com/rancher/os/docker"
|
||||
"github.com/rancher/os/log"
|
||||
)
|
||||
|
||||
const (
|
||||
userImagesPreloadDirectory = "/var/lib/rancher/preload/docker"
|
||||
)
|
||||
|
||||
func preloadImagesAction(c *cli.Context) error {
|
||||
return PreloadImages(docker.NewDefaultClient, userImagesPreloadDirectory)
|
||||
}
|
||||
|
||||
func shouldLoad(file string) bool {
|
||||
if strings.HasSuffix(file, ".done") {
|
||||
return false
|
||||
}
|
||||
if _, err := os.Stat(fmt.Sprintf("%s.done", file)); err == nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func PreloadImages(clientFactory func() (dockerClient.APIClient, error), imagesDir string) error {
|
||||
var client dockerClient.APIClient
|
||||
clientInitialized := false
|
||||
|
||||
if _, err := os.Stat(imagesDir); os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(imagesDir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
files, err := ioutil.ReadDir(imagesDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
filename := path.Join(imagesDir, file.Name())
|
||||
if !shouldLoad(filename) {
|
||||
continue
|
||||
}
|
||||
|
||||
image, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var imageReader io.Reader
|
||||
imageReader = image
|
||||
match, err := regexp.MatchString(".t?gz$", file.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if match {
|
||||
imageReader, err = gzip.NewReader(image)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !clientInitialized {
|
||||
client, err = clientFactory()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
clientInitialized = true
|
||||
}
|
||||
|
||||
log.Infof("Loading image %s", filename)
|
||||
if _, err = client.ImageLoad(context.Background(), imageReader, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = image.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
doneStamp, err := os.Create(fmt.Sprintf("%s.done", filename))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = doneStamp.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -2,16 +2,17 @@ package control
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/rancher/os/config"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func selinuxCommand() cli.Command {
|
||||
app := cli.Command{}
|
||||
app.Name = "selinux"
|
||||
app.Usage = "Launch SELinux tools container."
|
||||
app.Action = func(c *cli.Context) {
|
||||
app.Action = func(c *cli.Context) error {
|
||||
argv := []string{"system-docker", "run", "-it", "--privileged", "--rm",
|
||||
"--net", "host", "--pid", "host", "--ipc", "host",
|
||||
"-v", "/usr/bin/docker:/usr/bin/docker.dist:ro",
|
||||
@@ -49,8 +50,9 @@ func selinuxCommand() cli.Command {
|
||||
"-v", "/etc/selinux:/etc/selinux",
|
||||
"-v", "/var/lib/selinux:/var/lib/selinux",
|
||||
"-v", "/usr/share/selinux:/usr/share/selinux",
|
||||
fmt.Sprintf("rancher/os-selinuxtools:%s", config.VERSION + config.SUFFIX), "bash"}
|
||||
fmt.Sprintf("%s/os-selinuxtools:%s%s", config.OsRepo, config.Version, config.Suffix), "bash"}
|
||||
syscall.Exec("/bin/system-docker", argv, []string{})
|
||||
return nil
|
||||
}
|
||||
|
||||
return app
|
||||
|
||||
@@ -1,200 +0,0 @@
|
||||
package control
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/docker/libcompose/cli/command"
|
||||
dockerApp "github.com/docker/libcompose/cli/docker/app"
|
||||
"github.com/docker/libcompose/project"
|
||||
"github.com/rancher/os/compose"
|
||||
"github.com/rancher/os/config"
|
||||
"github.com/rancher/os/util"
|
||||
)
|
||||
|
||||
type projectFactory struct {
|
||||
}
|
||||
|
||||
func (p *projectFactory) Create(c *cli.Context) (*project.Project, error) {
|
||||
cfg, err := config.LoadConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return compose.GetProject(cfg, true)
|
||||
}
|
||||
|
||||
func beforeApp(c *cli.Context) error {
|
||||
if c.GlobalBool("verbose") {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func serviceCommand() cli.Command {
|
||||
factory := &projectFactory{}
|
||||
|
||||
app := cli.Command{}
|
||||
app.Name = "service"
|
||||
app.ShortName = "s"
|
||||
app.Usage = "Command line interface for services and compose."
|
||||
app.Before = beforeApp
|
||||
app.Flags = append(command.CommonFlags(), dockerApp.DockerClientFlags()...)
|
||||
app.Subcommands = append(serviceSubCommands(),
|
||||
command.BuildCommand(factory),
|
||||
command.CreateCommand(factory),
|
||||
command.UpCommand(factory),
|
||||
command.StartCommand(factory),
|
||||
command.LogsCommand(factory),
|
||||
command.RestartCommand(factory),
|
||||
command.StopCommand(factory),
|
||||
command.ScaleCommand(factory),
|
||||
command.RmCommand(factory),
|
||||
command.PullCommand(factory),
|
||||
command.KillCommand(factory),
|
||||
command.PortCommand(factory),
|
||||
command.PsCommand(factory),
|
||||
)
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
func serviceSubCommands() []cli.Command {
|
||||
return []cli.Command{
|
||||
{
|
||||
Name: "enable",
|
||||
Usage: "turn on an service",
|
||||
Action: enable,
|
||||
},
|
||||
{
|
||||
Name: "disable",
|
||||
Usage: "turn off an service",
|
||||
Action: disable,
|
||||
},
|
||||
{
|
||||
Name: "list",
|
||||
Usage: "list services and state",
|
||||
Action: list,
|
||||
},
|
||||
{
|
||||
Name: "delete",
|
||||
Usage: "delete a service",
|
||||
Action: del,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func disable(c *cli.Context) {
|
||||
changed := false
|
||||
cfg, err := config.LoadConfig()
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
for _, service := range c.Args() {
|
||||
if _, ok := cfg.Rancher.ServicesInclude[service]; !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
cfg.Rancher.ServicesInclude[service] = false
|
||||
changed = true
|
||||
}
|
||||
|
||||
if changed {
|
||||
if err = cfg.Save(); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func del(c *cli.Context) {
|
||||
changed := false
|
||||
cfg, err := config.LoadConfig()
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
for _, service := range c.Args() {
|
||||
if _, ok := cfg.Rancher.ServicesInclude[service]; !ok {
|
||||
continue
|
||||
}
|
||||
delete(cfg.Rancher.ServicesInclude, service)
|
||||
changed = true
|
||||
}
|
||||
|
||||
if changed {
|
||||
if err = cfg.Save(); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func enable(c *cli.Context) {
|
||||
cfg, err := config.LoadConfig()
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
var enabledServices []string
|
||||
|
||||
for _, service := range c.Args() {
|
||||
if val, ok := cfg.Rancher.ServicesInclude[service]; !ok || !val {
|
||||
if strings.HasPrefix(service, "/") && !strings.HasPrefix(service, "/var/lib/rancher/conf") {
|
||||
logrus.Fatalf("ERROR: Service should be in path /var/lib/rancher/conf")
|
||||
}
|
||||
|
||||
cfg.Rancher.ServicesInclude[service] = true
|
||||
enabledServices = append(enabledServices, service)
|
||||
}
|
||||
}
|
||||
|
||||
if len(enabledServices) > 0 {
|
||||
if err := compose.StageServices(cfg, enabledServices...); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if err := cfg.Save(); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func list(c *cli.Context) {
|
||||
cfg, err := config.LoadConfig()
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
clone := make(map[string]bool)
|
||||
for service, enabled := range cfg.Rancher.ServicesInclude {
|
||||
clone[service] = enabled
|
||||
}
|
||||
|
||||
services, err := util.GetServices(cfg.Rancher.Repositories.ToArray())
|
||||
if err != nil {
|
||||
logrus.Fatalf("Failed to get services: %v", err)
|
||||
}
|
||||
|
||||
for _, service := range services {
|
||||
if enabled, ok := clone[service]; ok {
|
||||
delete(clone, service)
|
||||
if enabled {
|
||||
fmt.Printf("enabled %s\n", service)
|
||||
} else {
|
||||
fmt.Printf("disabled %s\n", service)
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("disabled %s\n", service)
|
||||
}
|
||||
}
|
||||
|
||||
for service, enabled := range clone {
|
||||
if enabled {
|
||||
fmt.Printf("enabled %s\n", service)
|
||||
} else {
|
||||
fmt.Printf("disabled %s\n", service)
|
||||
}
|
||||
}
|
||||
}
|
||||
180
cmd/control/service/app/app.go
Normal file
180
cmd/control/service/app/app.go
Normal file
@@ -0,0 +1,180 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/rancher/os/log"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/docker/libcompose/project"
|
||||
"github.com/docker/libcompose/project/options"
|
||||
)
|
||||
|
||||
func ProjectPs(p project.APIProject, c *cli.Context) error {
|
||||
qFlag := c.Bool("q")
|
||||
allInfo, err := p.Ps(context.Background(), qFlag, c.Args()...)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err.Error(), 1)
|
||||
}
|
||||
os.Stdout.WriteString(allInfo.String(!qFlag))
|
||||
return nil
|
||||
}
|
||||
|
||||
func ProjectStop(p project.APIProject, c *cli.Context) error {
|
||||
err := p.Stop(context.Background(), c.Int("timeout"), c.Args()...)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err.Error(), 1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ProjectDown(p project.APIProject, c *cli.Context) error {
|
||||
options := options.Down{
|
||||
RemoveVolume: c.Bool("volumes"),
|
||||
RemoveImages: options.ImageType(c.String("rmi")),
|
||||
RemoveOrphans: c.Bool("remove-orphans"),
|
||||
}
|
||||
err := p.Down(context.Background(), options, c.Args()...)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err.Error(), 1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ProjectBuild(p project.APIProject, c *cli.Context) error {
|
||||
config := options.Build{
|
||||
NoCache: c.Bool("no-cache"),
|
||||
ForceRemove: c.Bool("force-rm"),
|
||||
Pull: c.Bool("pull"),
|
||||
}
|
||||
err := p.Build(context.Background(), config, c.Args()...)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err.Error(), 1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ProjectCreate(p project.APIProject, c *cli.Context) error {
|
||||
options := options.Create{
|
||||
NoRecreate: c.Bool("no-recreate"),
|
||||
ForceRecreate: c.Bool("force-recreate"),
|
||||
NoBuild: c.Bool("no-build"),
|
||||
}
|
||||
err := p.Create(context.Background(), options, c.Args()...)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err.Error(), 1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ProjectUp(p project.APIProject, c *cli.Context) error {
|
||||
options := options.Up{
|
||||
Create: options.Create{
|
||||
NoRecreate: c.Bool("no-recreate"),
|
||||
ForceRecreate: c.Bool("force-recreate"),
|
||||
NoBuild: c.Bool("no-build"),
|
||||
},
|
||||
}
|
||||
ctx, cancelFun := context.WithCancel(context.Background())
|
||||
err := p.Up(ctx, options, c.Args()...)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err.Error(), 1)
|
||||
}
|
||||
if c.Bool("foreground") {
|
||||
signalChan := make(chan os.Signal, 1)
|
||||
cleanupDone := make(chan bool)
|
||||
signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
|
||||
errChan := make(chan error)
|
||||
go func() {
|
||||
errChan <- p.Log(ctx, true, c.Args()...)
|
||||
}()
|
||||
go func() {
|
||||
select {
|
||||
case <-signalChan:
|
||||
fmt.Printf("\nGracefully stopping...\n")
|
||||
cancelFun()
|
||||
ProjectStop(p, c)
|
||||
cleanupDone <- true
|
||||
case err := <-errChan:
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
cleanupDone <- true
|
||||
}
|
||||
}()
|
||||
<-cleanupDone
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ProjectStart(p project.APIProject, c *cli.Context) error {
|
||||
err := p.Start(context.Background(), c.Args()...)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err.Error(), 1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ProjectRestart(p project.APIProject, c *cli.Context) error {
|
||||
err := p.Restart(context.Background(), c.Int("timeout"), c.Args()...)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err.Error(), 1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ProjectLog(p project.APIProject, c *cli.Context) error {
|
||||
err := p.Log(context.Background(), c.Bool("follow"), c.Args()...)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err.Error(), 1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ProjectPull(p project.APIProject, c *cli.Context) error {
|
||||
err := p.Pull(context.Background(), c.Args()...)
|
||||
if err != nil && !c.Bool("ignore-pull-failures") {
|
||||
return cli.NewExitError(err.Error(), 1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ProjectDelete(p project.APIProject, c *cli.Context) error {
|
||||
options := options.Delete{
|
||||
RemoveVolume: c.Bool("v"),
|
||||
}
|
||||
if !c.Bool("force") {
|
||||
options.BeforeDeleteCallback = func(stoppedContainers []string) bool {
|
||||
fmt.Printf("Going to remove %v\nAre you sure? [yN]\n", strings.Join(stoppedContainers, ", "))
|
||||
var answer string
|
||||
_, err := fmt.Scanln(&answer)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return false
|
||||
}
|
||||
if answer != "y" && answer != "Y" {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
err := p.Delete(context.Background(), options, c.Args()...)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err.Error(), 1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ProjectKill(p project.APIProject, c *cli.Context) error {
|
||||
err := p.Kill(context.Background(), c.String("signal"), c.Args()...)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err.Error(), 1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
243
cmd/control/service/command/command.go
Normal file
243
cmd/control/service/command/command.go
Normal file
@@ -0,0 +1,243 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
composeApp "github.com/docker/libcompose/cli/app"
|
||||
"github.com/rancher/os/cmd/control/service/app"
|
||||
)
|
||||
|
||||
func verifyOneOrMoreServices(c *cli.Context) error {
|
||||
if len(c.Args()) == 0 {
|
||||
return errors.New("Must specify one or more services")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func CreateCommand(factory composeApp.ProjectFactory) cli.Command {
|
||||
return cli.Command{
|
||||
Name: "create",
|
||||
Usage: "Create services",
|
||||
Before: verifyOneOrMoreServices,
|
||||
Action: composeApp.WithProject(factory, app.ProjectCreate),
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "no-recreate",
|
||||
Usage: "If containers already exist, don't recreate them. Incompatible with --force-recreate.",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "force-recreate",
|
||||
Usage: "Recreate containers even if their configuration and image haven't changed. Incompatible with --no-recreate.",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "no-build",
|
||||
Usage: "Don't build an image, even if it's missing.",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func BuildCommand(factory composeApp.ProjectFactory) cli.Command {
|
||||
return cli.Command{
|
||||
Name: "build",
|
||||
Usage: "Build or rebuild services",
|
||||
Before: verifyOneOrMoreServices,
|
||||
Action: composeApp.WithProject(factory, app.ProjectBuild),
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "no-cache",
|
||||
Usage: "Do not use cache when building the image",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "force-rm",
|
||||
Usage: "Always remove intermediate containers",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "pull",
|
||||
Usage: "Always attempt to pull a newer version of the image",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func PsCommand(factory composeApp.ProjectFactory) cli.Command {
|
||||
return cli.Command{
|
||||
Name: "ps",
|
||||
Usage: "List containers",
|
||||
Action: composeApp.WithProject(factory, app.ProjectPs),
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "q",
|
||||
Usage: "Only display IDs",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func UpCommand(factory composeApp.ProjectFactory) cli.Command {
|
||||
return cli.Command{
|
||||
Name: "up",
|
||||
Usage: "Create and start containers",
|
||||
Before: verifyOneOrMoreServices,
|
||||
Action: composeApp.WithProject(factory, app.ProjectUp),
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "foreground",
|
||||
Usage: "Run in foreground and log",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "no-build",
|
||||
Usage: "Don't build an image, even if it's missing.",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "no-recreate",
|
||||
Usage: "If containers already exist, don't recreate them. Incompatible with --force-recreate.",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "force-recreate",
|
||||
Usage: "Recreate containers even if their configuration and image haven't changed. Incompatible with --no-recreate.",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func StartCommand(factory composeApp.ProjectFactory) cli.Command {
|
||||
return cli.Command{
|
||||
Name: "start",
|
||||
Usage: "Start services",
|
||||
Before: verifyOneOrMoreServices,
|
||||
Action: composeApp.WithProject(factory, app.ProjectStart),
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolTFlag{
|
||||
Name: "foreground",
|
||||
Usage: "Run in foreground and log",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func PullCommand(factory composeApp.ProjectFactory) cli.Command {
|
||||
return cli.Command{
|
||||
Name: "pull",
|
||||
Usage: "Pulls service images",
|
||||
Before: verifyOneOrMoreServices,
|
||||
Action: composeApp.WithProject(factory, app.ProjectPull),
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "ignore-pull-failures",
|
||||
Usage: "Pull what it can and ignores images with pull failures.",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func LogsCommand(factory composeApp.ProjectFactory) cli.Command {
|
||||
return cli.Command{
|
||||
Name: "logs",
|
||||
Usage: "View output from containers",
|
||||
Before: verifyOneOrMoreServices,
|
||||
Action: composeApp.WithProject(factory, app.ProjectLog),
|
||||
Flags: []cli.Flag{
|
||||
cli.IntFlag{
|
||||
Name: "lines",
|
||||
Usage: "number of lines to tail",
|
||||
Value: 100,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "follow",
|
||||
Usage: "Follow log output.",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func RestartCommand(factory composeApp.ProjectFactory) cli.Command {
|
||||
return cli.Command{
|
||||
Name: "restart",
|
||||
Usage: "Restart services",
|
||||
Before: verifyOneOrMoreServices,
|
||||
Action: composeApp.WithProject(factory, app.ProjectRestart),
|
||||
Flags: []cli.Flag{
|
||||
cli.IntFlag{
|
||||
Name: "timeout,t",
|
||||
Usage: "Specify a shutdown timeout in seconds.",
|
||||
Value: 10,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func StopCommand(factory composeApp.ProjectFactory) cli.Command {
|
||||
return cli.Command{
|
||||
Name: "stop",
|
||||
Usage: "Stop services",
|
||||
Before: verifyOneOrMoreServices,
|
||||
Action: composeApp.WithProject(factory, app.ProjectStop),
|
||||
Flags: []cli.Flag{
|
||||
cli.IntFlag{
|
||||
Name: "timeout,t",
|
||||
Usage: "Specify a shutdown timeout in seconds.",
|
||||
Value: 10,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func DownCommand(factory composeApp.ProjectFactory) cli.Command {
|
||||
return cli.Command{
|
||||
Name: "down",
|
||||
Usage: "Stop and remove containers, networks, images, and volumes",
|
||||
Before: verifyOneOrMoreServices,
|
||||
Action: composeApp.WithProject(factory, app.ProjectDown),
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "volumes,v",
|
||||
Usage: "Remove data volumes",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "rmi",
|
||||
Usage: "Remove images, type may be one of: 'all' to remove all images, or 'local' to remove only images that don't have an custom name set by the `image` field",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "remove-orphans",
|
||||
Usage: "Remove containers for services not defined in the Compose file",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func RmCommand(factory composeApp.ProjectFactory) cli.Command {
|
||||
return cli.Command{
|
||||
Name: "rm",
|
||||
Usage: "Delete services",
|
||||
Before: verifyOneOrMoreServices,
|
||||
Action: composeApp.WithProject(factory, app.ProjectDelete),
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "force,f",
|
||||
Usage: "Allow deletion of all services",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "v",
|
||||
Usage: "Remove volumes associated with containers",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func KillCommand(factory composeApp.ProjectFactory) cli.Command {
|
||||
return cli.Command{
|
||||
Name: "kill",
|
||||
Usage: "Kill containers",
|
||||
Before: verifyOneOrMoreServices,
|
||||
Action: composeApp.WithProject(factory, app.ProjectKill),
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "signal,s",
|
||||
Usage: "SIGNAL to send to the container",
|
||||
Value: "SIGKILL",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
224
cmd/control/service/service.go
Normal file
224
cmd/control/service/service.go
Normal file
@@ -0,0 +1,224 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
dockerApp "github.com/docker/libcompose/cli/docker/app"
|
||||
"github.com/docker/libcompose/project"
|
||||
"github.com/rancher/os/cmd/control/service/command"
|
||||
"github.com/rancher/os/compose"
|
||||
"github.com/rancher/os/config"
|
||||
"github.com/rancher/os/log"
|
||||
"github.com/rancher/os/util"
|
||||
"github.com/rancher/os/util/network"
|
||||
)
|
||||
|
||||
type projectFactory struct {
|
||||
}
|
||||
|
||||
func (p *projectFactory) Create(c *cli.Context) (project.APIProject, error) {
|
||||
cfg := config.LoadConfig()
|
||||
return compose.GetProject(cfg, true, false)
|
||||
}
|
||||
|
||||
func beforeApp(c *cli.Context) error {
|
||||
if c.GlobalBool("verbose") {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Commands() cli.Command {
|
||||
factory := &projectFactory{}
|
||||
|
||||
app := cli.Command{}
|
||||
app.Name = "service"
|
||||
app.ShortName = "s"
|
||||
app.Usage = "Command line interface for services and compose."
|
||||
app.Before = beforeApp
|
||||
app.Flags = append(dockerApp.DockerClientFlags(), cli.BoolFlag{
|
||||
Name: "verbose,debug",
|
||||
})
|
||||
app.Subcommands = append(serviceSubCommands(),
|
||||
command.BuildCommand(factory),
|
||||
command.CreateCommand(factory),
|
||||
command.UpCommand(factory),
|
||||
command.StartCommand(factory),
|
||||
command.LogsCommand(factory),
|
||||
command.RestartCommand(factory),
|
||||
command.StopCommand(factory),
|
||||
command.RmCommand(factory),
|
||||
command.PullCommand(factory),
|
||||
command.KillCommand(factory),
|
||||
command.PsCommand(factory),
|
||||
)
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
func serviceSubCommands() []cli.Command {
|
||||
return []cli.Command{
|
||||
{
|
||||
Name: "enable",
|
||||
Usage: "turn on an service",
|
||||
Action: enable,
|
||||
},
|
||||
{
|
||||
Name: "disable",
|
||||
Usage: "turn off an service",
|
||||
Action: disable,
|
||||
},
|
||||
{
|
||||
Name: "list",
|
||||
Usage: "list services and state",
|
||||
Action: list,
|
||||
},
|
||||
{
|
||||
Name: "delete",
|
||||
Usage: "delete a service",
|
||||
Action: del,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func updateIncludedServices(cfg *config.CloudConfig) error {
|
||||
return config.Set("rancher.services_include", cfg.Rancher.ServicesInclude)
|
||||
}
|
||||
|
||||
func disable(c *cli.Context) error {
|
||||
changed := false
|
||||
cfg := config.LoadConfig()
|
||||
|
||||
for _, service := range c.Args() {
|
||||
validateService(service, cfg)
|
||||
|
||||
if _, ok := cfg.Rancher.ServicesInclude[service]; !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
cfg.Rancher.ServicesInclude[service] = false
|
||||
changed = true
|
||||
}
|
||||
|
||||
if changed {
|
||||
if err := updateIncludedServices(cfg); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func del(c *cli.Context) error {
|
||||
changed := false
|
||||
cfg := config.LoadConfig()
|
||||
|
||||
for _, service := range c.Args() {
|
||||
validateService(service, cfg)
|
||||
|
||||
if _, ok := cfg.Rancher.ServicesInclude[service]; !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
delete(cfg.Rancher.ServicesInclude, service)
|
||||
changed = true
|
||||
}
|
||||
|
||||
if changed {
|
||||
if err := updateIncludedServices(cfg); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func enable(c *cli.Context) error {
|
||||
cfg := config.LoadConfig()
|
||||
|
||||
var enabledServices []string
|
||||
|
||||
for _, service := range c.Args() {
|
||||
validateService(service, cfg)
|
||||
|
||||
if val, ok := cfg.Rancher.ServicesInclude[service]; !ok || !val {
|
||||
if isLocal(service) && !strings.HasPrefix(service, "/var/lib/rancher/conf") {
|
||||
log.Fatalf("ERROR: Service should be in path /var/lib/rancher/conf")
|
||||
}
|
||||
|
||||
cfg.Rancher.ServicesInclude[service] = true
|
||||
enabledServices = append(enabledServices, service)
|
||||
}
|
||||
}
|
||||
|
||||
if len(enabledServices) > 0 {
|
||||
if err := compose.StageServices(cfg, enabledServices...); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err := updateIncludedServices(cfg); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func list(c *cli.Context) error {
|
||||
cfg := config.LoadConfig()
|
||||
|
||||
clone := make(map[string]bool)
|
||||
for service, enabled := range cfg.Rancher.ServicesInclude {
|
||||
clone[service] = enabled
|
||||
}
|
||||
|
||||
services := availableService(cfg)
|
||||
|
||||
for _, service := range services {
|
||||
if enabled, ok := clone[service]; ok {
|
||||
delete(clone, service)
|
||||
if enabled {
|
||||
fmt.Printf("enabled %s\n", service)
|
||||
} else {
|
||||
fmt.Printf("disabled %s\n", service)
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("disabled %s\n", service)
|
||||
}
|
||||
}
|
||||
|
||||
for service, enabled := range clone {
|
||||
if enabled {
|
||||
fmt.Printf("enabled %s\n", service)
|
||||
} else {
|
||||
fmt.Printf("disabled %s\n", service)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func isLocal(service string) bool {
|
||||
return strings.HasPrefix(service, "/")
|
||||
}
|
||||
|
||||
func IsLocalOrURL(service string) bool {
|
||||
return isLocal(service) || strings.HasPrefix(service, "http:/") || strings.HasPrefix(service, "https:/")
|
||||
}
|
||||
|
||||
func validateService(service string, cfg *config.CloudConfig) {
|
||||
services := availableService(cfg)
|
||||
if !IsLocalOrURL(service) && !util.Contains(services, service) {
|
||||
log.Fatalf("%s is not a valid service", service)
|
||||
}
|
||||
}
|
||||
|
||||
func availableService(cfg *config.CloudConfig) []string {
|
||||
services, err := network.GetServices(cfg.Rancher.Repositories.ToArray())
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to get services: %v", err)
|
||||
}
|
||||
return services
|
||||
}
|
||||
48
cmd/control/switch_console.go
Normal file
48
cmd/control/switch_console.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package control
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/docker/libcompose/project/options"
|
||||
"github.com/rancher/os/compose"
|
||||
"github.com/rancher/os/config"
|
||||
"github.com/rancher/os/log"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func switchConsoleAction(c *cli.Context) error {
|
||||
if len(c.Args()) != 1 {
|
||||
return errors.New("Must specify exactly one existing container")
|
||||
}
|
||||
newConsole := c.Args()[0]
|
||||
|
||||
cfg := config.LoadConfig()
|
||||
|
||||
project, err := compose.GetProject(cfg, true, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if newConsole != "default" {
|
||||
if err = compose.LoadSpecialService(project, cfg, "console", newConsole); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err = config.Set("rancher.console", newConsole); err != nil {
|
||||
log.Errorf("Failed to update 'rancher.console': %v", err)
|
||||
}
|
||||
|
||||
if err = project.Up(context.Background(), options.Up{
|
||||
Log: true,
|
||||
}, "console"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = project.Restart(context.Background(), 10, "docker"); err != nil {
|
||||
log.Errorf("Failed to restart Docker: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -5,16 +5,25 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/rancher/os/log"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
machineUtil "github.com/docker/machine/utils"
|
||||
"github.com/rancher/os/config"
|
||||
"github.com/rancher/os/util"
|
||||
)
|
||||
|
||||
const (
|
||||
NAME string = "rancher"
|
||||
BITS int = 2048
|
||||
NAME string = "rancher"
|
||||
BITS int = 2048
|
||||
ServerTLSPath string = "/etc/docker/tls"
|
||||
ClientTLSPath string = "/home/rancher/.docker"
|
||||
Cert string = "cert.pem"
|
||||
Key string = "key.pem"
|
||||
ServerCert string = "server-cert.pem"
|
||||
ServerKey string = "server-key.pem"
|
||||
CaCert string = "ca.pem"
|
||||
CaKey string = "ca-key.pem"
|
||||
)
|
||||
|
||||
func tlsConfCommands() []cli.Command {
|
||||
@@ -44,101 +53,81 @@ func tlsConfCommands() []cli.Command {
|
||||
}
|
||||
}
|
||||
|
||||
func writeCerts(generateServer bool, hostname []string, cfg *config.CloudConfig, certPath, keyPath, caCertPath, caKeyPath string) error {
|
||||
func writeCerts(generateServer bool, hostname []string, certPath, keyPath, caCertPath, caKeyPath string) error {
|
||||
if !generateServer {
|
||||
return machineUtil.GenerateCert([]string{""}, certPath, keyPath, caCertPath, caKeyPath, NAME, BITS)
|
||||
}
|
||||
|
||||
if cfg.Rancher.Docker.ServerKey == "" || cfg.Rancher.Docker.ServerCert == "" {
|
||||
err := machineUtil.GenerateCert(hostname, certPath, keyPath, caCertPath, caKeyPath, NAME, BITS)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cert, err := ioutil.ReadFile(certPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key, err := ioutil.ReadFile(keyPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg, err = cfg.Merge(map[interface{}]interface{}{
|
||||
"rancher": map[interface{}]interface{}{
|
||||
"docker": map[interface{}]interface{}{
|
||||
"server_cert": string(cert),
|
||||
"server_key": string(key),
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return cfg.Save() // certPath, keyPath are already written to by machineUtil.GenerateCert()
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(certPath, []byte(cfg.Rancher.Docker.ServerCert), 0400); err != nil {
|
||||
if err := machineUtil.GenerateCert(hostname, certPath, keyPath, caCertPath, caKeyPath, NAME, BITS); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ioutil.WriteFile(keyPath, []byte(cfg.Rancher.Docker.ServerKey), 0400)
|
||||
cert, err := ioutil.ReadFile(certPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key, err := ioutil.ReadFile(keyPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// certPath, keyPath are already written to by machineUtil.GenerateCert()
|
||||
if err := config.Set("rancher.docker.server_cert", string(cert)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := config.Set("rancher.docker.server_key", string(key)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeCaCerts(cfg *config.CloudConfig, caCertPath, caKeyPath string) (*config.CloudConfig, error) {
|
||||
func writeCaCerts(cfg *config.CloudConfig, caCertPath, caKeyPath string) error {
|
||||
if cfg.Rancher.Docker.CACert == "" {
|
||||
if err := machineUtil.GenerateCACertificate(caCertPath, caKeyPath, NAME, BITS); err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
caCert, err := ioutil.ReadFile(caCertPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
caKey, err := ioutil.ReadFile(caKeyPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
cfg, err = cfg.Merge(map[interface{}]interface{}{
|
||||
"rancher": map[interface{}]interface{}{
|
||||
"docker": map[interface{}]interface{}{
|
||||
"ca_key": string(caKey),
|
||||
"ca_cert": string(caCert),
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// caCertPath, caKeyPath are already written to by machineUtil.GenerateCACertificate()
|
||||
if err := config.Set("rancher.docker.ca_cert", string(caCert)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := config.Set("rancher.docker.ca_key", string(caKey)); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
cfg = config.LoadConfig()
|
||||
|
||||
if err := util.WriteFileAtomic(caCertPath, []byte(cfg.Rancher.Docker.CACert), 0400); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = cfg.Save(); err != nil {
|
||||
return nil, err
|
||||
if err := util.WriteFileAtomic(caKeyPath, []byte(cfg.Rancher.Docker.CAKey), 0400); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return cfg, nil // caCertPath, caKeyPath are already written to by machineUtil.GenerateCACertificate()
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(caCertPath, []byte(cfg.Rancher.Docker.CACert), 0400); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(caKeyPath, []byte(cfg.Rancher.Docker.CAKey), 0400); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func tlsConfCreate(c *cli.Context) {
|
||||
func tlsConfCreate(c *cli.Context) error {
|
||||
err := generate(c)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func generate(c *cli.Context) error {
|
||||
@@ -150,27 +139,22 @@ func generate(c *cli.Context) error {
|
||||
}
|
||||
|
||||
func Generate(generateServer bool, outDir string, hostnames []string) error {
|
||||
cfg, err := config.LoadConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if outDir == "" {
|
||||
if generateServer {
|
||||
outDir = "/etc/docker/tls"
|
||||
outDir = ServerTLSPath
|
||||
} else {
|
||||
outDir = "/home/rancher/.docker"
|
||||
outDir = ClientTLSPath
|
||||
}
|
||||
log.Infof("Out directory (-d, --dir) not specified, using default: %s", outDir)
|
||||
}
|
||||
caCertPath := filepath.Join(outDir, "ca.pem")
|
||||
caKeyPath := filepath.Join(outDir, "ca-key.pem")
|
||||
certPath := filepath.Join(outDir, "cert.pem")
|
||||
keyPath := filepath.Join(outDir, "key.pem")
|
||||
caCertPath := filepath.Join(outDir, CaCert)
|
||||
caKeyPath := filepath.Join(outDir, CaKey)
|
||||
certPath := filepath.Join(outDir, Cert)
|
||||
keyPath := filepath.Join(outDir, Key)
|
||||
|
||||
if generateServer {
|
||||
certPath = filepath.Join(outDir, "server-cert.pem")
|
||||
keyPath = filepath.Join(outDir, "server-key.pem")
|
||||
certPath = filepath.Join(outDir, ServerCert)
|
||||
keyPath = filepath.Join(outDir, ServerKey)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(outDir); os.IsNotExist(err) {
|
||||
@@ -179,11 +163,11 @@ func Generate(generateServer bool, outDir string, hostnames []string) error {
|
||||
}
|
||||
}
|
||||
|
||||
cfg, err = writeCaCerts(cfg, caCertPath, caKeyPath)
|
||||
if err != nil {
|
||||
cfg := config.LoadConfig()
|
||||
if err := writeCaCerts(cfg, caCertPath, caKeyPath); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeCerts(generateServer, hostnames, cfg, certPath, keyPath, caCertPath, caKeyPath); err != nil {
|
||||
if err := writeCerts(generateServer, hostnames, certPath, keyPath, caCertPath, caKeyPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
36
cmd/control/udevsettle.go
Normal file
36
cmd/control/udevsettle.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package control
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/rancher/os/log"
|
||||
)
|
||||
|
||||
func udevSettleAction(c *cli.Context) {
|
||||
if err := UdevSettle(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func UdevSettle() error {
|
||||
cmd := exec.Command("udevd", "--daemon")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd = exec.Command("udevadm", "trigger", "--action=add")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd = exec.Command("udevadm", "settle")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run()
|
||||
}
|
||||
238
cmd/control/user_docker.go
Normal file
238
cmd/control/user_docker.go
Normal file
@@ -0,0 +1,238 @@
|
||||
package control
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"path/filepath"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
composeClient "github.com/docker/libcompose/docker/client"
|
||||
"github.com/docker/libcompose/project"
|
||||
"github.com/rancher/os/compose"
|
||||
"github.com/rancher/os/config"
|
||||
rosDocker "github.com/rancher/os/docker"
|
||||
"github.com/rancher/os/log"
|
||||
"github.com/rancher/os/util"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultStorageContext = "console"
|
||||
dockerPidFile = "/var/run/docker.pid"
|
||||
userDocker = "user-docker"
|
||||
sourceDirectory = "/engine"
|
||||
destDirectory = "/var/lib/rancher/engine"
|
||||
)
|
||||
|
||||
var (
|
||||
dockerCommand = []string{
|
||||
"ros",
|
||||
"docker-init",
|
||||
}
|
||||
)
|
||||
|
||||
func userDockerAction(c *cli.Context) error {
|
||||
if err := copyBinaries(sourceDirectory, destDirectory); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := syscall.Mount("/host/sys", "/sys", "", syscall.MS_BIND|syscall.MS_REC, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg := config.LoadConfig()
|
||||
|
||||
return startDocker(cfg)
|
||||
}
|
||||
|
||||
func copyBinaries(source, dest string) error {
|
||||
if err := os.MkdirAll(dest, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
files, err := ioutil.ReadDir(dest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if err = os.RemoveAll(path.Join(dest, file.Name())); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
files, err = ioutil.ReadDir(source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
sourceFile := path.Join(source, file.Name())
|
||||
destFile := path.Join(dest, file.Name())
|
||||
|
||||
in, err := os.Open(sourceFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out, err := os.Create(destFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = io.Copy(out, in); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = out.Sync(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = in.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = out.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.Chmod(destFile, 0751); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeConfigCerts(cfg *config.CloudConfig) error {
|
||||
outDir := ServerTLSPath
|
||||
if err := os.MkdirAll(outDir, 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
caCertPath := filepath.Join(outDir, CaCert)
|
||||
caKeyPath := filepath.Join(outDir, CaKey)
|
||||
serverCertPath := filepath.Join(outDir, ServerCert)
|
||||
serverKeyPath := filepath.Join(outDir, ServerKey)
|
||||
if cfg.Rancher.Docker.CACert != "" {
|
||||
if err := util.WriteFileAtomic(caCertPath, []byte(cfg.Rancher.Docker.CACert), 0400); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := util.WriteFileAtomic(caKeyPath, []byte(cfg.Rancher.Docker.CAKey), 0400); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if cfg.Rancher.Docker.ServerCert != "" {
|
||||
if err := util.WriteFileAtomic(serverCertPath, []byte(cfg.Rancher.Docker.ServerCert), 0400); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := util.WriteFileAtomic(serverKeyPath, []byte(cfg.Rancher.Docker.ServerKey), 0400); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func startDocker(cfg *config.CloudConfig) error {
|
||||
storageContext := cfg.Rancher.Docker.StorageContext
|
||||
if storageContext == "" {
|
||||
storageContext = defaultStorageContext
|
||||
}
|
||||
|
||||
log.Infof("Starting Docker in context: %s", storageContext)
|
||||
|
||||
p, err := compose.GetProject(cfg, true, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pid, err := waitForPid(storageContext, p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("%s PID %d", storageContext, pid)
|
||||
|
||||
client, err := rosDocker.NewSystemClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dockerCfg := cfg.Rancher.Docker
|
||||
|
||||
args := dockerCfg.FullArgs()
|
||||
|
||||
log.Debugf("User Docker args: %v", args)
|
||||
|
||||
if dockerCfg.TLS {
|
||||
if err := writeConfigCerts(cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
info, err := client.ContainerInspect(context.Background(), storageContext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := []string{"docker-runc", "exec", "--", info.ID, "env"}
|
||||
log.Info(dockerCfg.AppendEnv())
|
||||
cmd = append(cmd, dockerCfg.AppendEnv()...)
|
||||
cmd = append(cmd, dockerCommand...)
|
||||
cmd = append(cmd, args...)
|
||||
log.Infof("Running %v", cmd)
|
||||
|
||||
return syscall.Exec("/usr/bin/ros", cmd, os.Environ())
|
||||
}
|
||||
|
||||
func waitForPid(service string, project *project.Project) (int, error) {
|
||||
log.Infof("Getting PID for service: %s", service)
|
||||
for {
|
||||
if pid, err := getPid(service, project); err != nil || pid == 0 {
|
||||
log.Infof("Waiting for %s : %d : %v", service, pid, err)
|
||||
time.Sleep(1 * time.Second)
|
||||
} else {
|
||||
return pid, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getPid(service string, project *project.Project) (int, error) {
|
||||
s, err := project.CreateService(service)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
containers, err := s.Containers(context.Background())
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if len(containers) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
client, err := composeClient.Create(composeClient.Options{
|
||||
Host: config.SystemDockerHost,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
id, err := containers[0].ID()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
info, err := client.ContainerInspect(context.Background(), id)
|
||||
if err != nil || info.ID == "" {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if info.State.Running {
|
||||
return info.State.Pid, nil
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
21
cmd/control/util.go
Normal file
21
cmd/control/util.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package control
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/rancher/os/log"
|
||||
)
|
||||
|
||||
func yes(question string) bool {
|
||||
fmt.Printf("%s [y/N]: ", question)
|
||||
in := bufio.NewReader(os.Stdin)
|
||||
line, err := in.ReadString('\n')
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return strings.ToLower(line[0:1]) == "y"
|
||||
}
|
||||
100
cmd/network/network.go
Normal file → Executable file
100
cmd/network/network.go
Normal file → Executable file
@@ -1,58 +1,39 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"flag"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/rancher/os/log"
|
||||
|
||||
"github.com/docker/libnetwork/resolvconf"
|
||||
"github.com/rancher/netconf"
|
||||
"github.com/rancher/os/cmd/cloudinit"
|
||||
"github.com/rancher/os/config"
|
||||
"github.com/rancher/os/hostname"
|
||||
"github.com/rancher/os/netconf"
|
||||
)
|
||||
|
||||
const (
|
||||
NETWORK_DONE = "/var/run/network.done"
|
||||
WAIT_FOR_NETWORK = "wait-for-network"
|
||||
)
|
||||
|
||||
var (
|
||||
daemon bool
|
||||
flags *flag.FlagSet
|
||||
)
|
||||
|
||||
func init() {
|
||||
flags = flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
|
||||
flags.BoolVar(&daemon, "daemon", false, "run dhcpd as daemon")
|
||||
}
|
||||
|
||||
func sendTerm(proc string) {
|
||||
cmd := exec.Command("killall", "-TERM", proc)
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Run()
|
||||
}
|
||||
|
||||
func Main() {
|
||||
flags.Parse(os.Args[1:])
|
||||
log.InitLogger()
|
||||
|
||||
log.Infof("Running network: daemon=%v", daemon)
|
||||
cfg := config.LoadConfig()
|
||||
ApplyNetworkConfig(cfg)
|
||||
|
||||
os.Remove(NETWORK_DONE) // ignore error
|
||||
cfg, err := config.LoadConfig()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
select {}
|
||||
}
|
||||
|
||||
func ApplyNetworkConfig(cfg *config.CloudConfig) {
|
||||
log.Infof("Apply Network Config")
|
||||
nameservers := cfg.Rancher.Network.DNS.Nameservers
|
||||
search := cfg.Rancher.Network.DNS.Search
|
||||
userSetDNS := len(nameservers) > 0 || len(search) > 0
|
||||
if !userSetDNS {
|
||||
nameservers = cfg.Rancher.Defaults.Network.DNS.Nameservers
|
||||
search = cfg.Rancher.Defaults.Network.DNS.Search
|
||||
}
|
||||
|
||||
hostname, _ := cloudinit.SetHostname(cfg) // ignore error
|
||||
log.Infof("Network: hostname: '%s'", hostname)
|
||||
// TODO: don't write to the file if nameservers is still empty
|
||||
if _, err := resolvconf.Build("/etc/resolv.conf", nameservers, search, nil); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
if _, err := resolvconf.Build("/etc/resolv.conf", cfg.Rancher.Network.Dns.Nameservers, cfg.Rancher.Network.Dns.Search, nil); err != nil {
|
||||
if err := hostname.SetHostnameFromCloudConfig(cfg); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
@@ -60,38 +41,15 @@ func Main() {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
hostname, _ = cloudinit.SetHostname(cfg) // ignore error
|
||||
log.Infof("Network: hostname: '%s' (from DHCP, if not set by cloud-config)", hostname)
|
||||
if hostname != "" {
|
||||
hosts, err := os.Open("/etc/hosts")
|
||||
defer hosts.Close()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
lines := bufio.NewScanner(hosts)
|
||||
hostsContent := ""
|
||||
for lines.Scan() {
|
||||
line := strings.TrimSpace(lines.Text())
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) > 0 && fields[0] == "127.0.1.1" {
|
||||
hostsContent += "127.0.1.1 " + hostname + "\n"
|
||||
continue
|
||||
}
|
||||
hostsContent += line + "\n"
|
||||
}
|
||||
if err := ioutil.WriteFile("/etc/hosts", []byte(hostsContent), 0600); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
if f, err := os.Create(NETWORK_DONE); err != nil {
|
||||
// TODO: seems wrong to do this outside netconf
|
||||
userSetHostname := cfg.Hostname != ""
|
||||
log.Infof("Apply Network Config RunDhcp")
|
||||
if err := netconf.RunDhcp(&cfg.Rancher.Network, !userSetHostname, !userSetDNS); err != nil {
|
||||
log.Error(err)
|
||||
} else {
|
||||
f.Close()
|
||||
}
|
||||
sendTerm(WAIT_FOR_NETWORK)
|
||||
|
||||
if daemon {
|
||||
select {}
|
||||
log.Infof("Apply Network Config SyncHostname")
|
||||
if err := hostname.SyncHostname(); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package power
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -9,14 +8,15 @@ import (
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
dockerClient "github.com/fsouza/go-dockerclient"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/docker/engine-api/types/container"
|
||||
"github.com/docker/engine-api/types/filters"
|
||||
"github.com/rancher/os/log"
|
||||
|
||||
"github.com/rancher/os/docker"
|
||||
)
|
||||
|
||||
const (
|
||||
DOCKER_CGROUPS_FILE = "/proc/self/cgroup"
|
||||
"github.com/rancher/os/util"
|
||||
)
|
||||
|
||||
func runDocker(name string) error {
|
||||
@@ -36,11 +36,10 @@ func runDocker(name string) error {
|
||||
cmd = os.Args
|
||||
}
|
||||
|
||||
exiting, err := client.InspectContainer(name)
|
||||
if exiting != nil {
|
||||
err := client.RemoveContainer(dockerClient.RemoveContainerOptions{
|
||||
ID: exiting.ID,
|
||||
Force: true,
|
||||
existing, err := client.ContainerInspect(context.Background(), name)
|
||||
if err == nil && existing.ID != "" {
|
||||
err := client.ContainerRemove(context.Background(), types.ContainerRemoveOptions{
|
||||
ContainerID: existing.ID,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@@ -48,53 +47,50 @@ func runDocker(name string) error {
|
||||
}
|
||||
}
|
||||
|
||||
currentContainerId, err := getCurrentContainerId()
|
||||
currentContainerID, err := util.GetCurrentContainerID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
currentContainer, err := client.InspectContainer(currentContainerId)
|
||||
currentContainer, err := client.ContainerInspect(context.Background(), currentContainerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
powerContainer, err := client.CreateContainer(dockerClient.CreateContainerOptions{
|
||||
Name: name,
|
||||
Config: &dockerClient.Config{
|
||||
powerContainer, err := client.ContainerCreate(context.Background(),
|
||||
&container.Config{
|
||||
Image: currentContainer.Config.Image,
|
||||
Cmd: cmd,
|
||||
Env: []string{
|
||||
"IN_DOCKER=true",
|
||||
},
|
||||
},
|
||||
HostConfig: &dockerClient.HostConfig{
|
||||
&container.HostConfig{
|
||||
PidMode: "host",
|
||||
VolumesFrom: []string{
|
||||
currentContainer.ID,
|
||||
},
|
||||
Privileged: true,
|
||||
},
|
||||
})
|
||||
}, nil, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
client.AttachToContainer(dockerClient.AttachToContainerOptions{
|
||||
Container: powerContainer.ID,
|
||||
OutputStream: os.Stdout,
|
||||
ErrorStream: os.Stderr,
|
||||
Stderr: true,
|
||||
Stdout: true,
|
||||
client.ContainerAttach(context.Background(), types.ContainerAttachOptions{
|
||||
ContainerID: powerContainer.ID,
|
||||
Stream: true,
|
||||
Stderr: true,
|
||||
Stdout: true,
|
||||
})
|
||||
}()
|
||||
|
||||
err = client.StartContainer(powerContainer.ID, powerContainer.HostConfig)
|
||||
err = client.ContainerStart(context.Background(), powerContainer.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = client.WaitContainer(powerContainer.ID)
|
||||
_, err = client.ContainerWait(context.Background(), powerContainer.ID)
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
@@ -114,7 +110,7 @@ func common(name string) {
|
||||
}
|
||||
}
|
||||
|
||||
func PowerOff() {
|
||||
func Off() {
|
||||
common("poweroff")
|
||||
reboot(syscall.LINUX_REBOOT_CMD_POWER_OFF)
|
||||
}
|
||||
@@ -172,19 +168,20 @@ func shutDownContainers() error {
|
||||
return err
|
||||
}
|
||||
|
||||
opts := dockerClient.ListContainersOptions{
|
||||
All: true,
|
||||
Filters: map[string][]string{
|
||||
"status": {"running"},
|
||||
},
|
||||
filter := filters.NewArgs()
|
||||
filter.Add("status", "running")
|
||||
|
||||
opts := types.ContainerListOptions{
|
||||
All: true,
|
||||
Filter: filter,
|
||||
}
|
||||
|
||||
containers, err := client.ListContainers(opts)
|
||||
containers, err := client.ContainerList(context.Background(), opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
currentContainerId, err := getCurrentContainerId()
|
||||
currentContainerID, err := util.GetCurrentContainerID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -192,12 +189,12 @@ func shutDownContainers() error {
|
||||
var stopErrorStrings []string
|
||||
|
||||
for _, container := range containers {
|
||||
if container.ID == currentContainerId {
|
||||
if container.ID == currentContainerID {
|
||||
continue
|
||||
}
|
||||
|
||||
log.Infof("Stopping %s : %v", container.ID[:12], container.Names)
|
||||
stopErr := client.StopContainer(container.ID, uint(timeout))
|
||||
stopErr := client.ContainerStop(context.Background(), container.ID, timeout)
|
||||
if stopErr != nil {
|
||||
stopErrorStrings = append(stopErrorStrings, " ["+container.ID+"] "+stopErr.Error())
|
||||
}
|
||||
@@ -206,10 +203,10 @@ func shutDownContainers() error {
|
||||
var waitErrorStrings []string
|
||||
|
||||
for _, container := range containers {
|
||||
if container.ID == currentContainerId {
|
||||
if container.ID == currentContainerID {
|
||||
continue
|
||||
}
|
||||
_, waitErr := client.WaitContainer(container.ID)
|
||||
_, waitErr := client.ContainerWait(context.Background(), container.ID)
|
||||
if waitErr != nil {
|
||||
waitErrorStrings = append(waitErrorStrings, " ["+container.ID+"] "+waitErr.Error())
|
||||
}
|
||||
@@ -221,35 +218,3 @@ func shutDownContainers() error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getCurrentContainerId() (string, error) {
|
||||
file, err := os.Open(DOCKER_CGROUPS_FILE)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
fileReader := bufio.NewScanner(file)
|
||||
if !fileReader.Scan() {
|
||||
return "", errors.New("Empty file /proc/self/cgroup")
|
||||
}
|
||||
line := fileReader.Text()
|
||||
parts := strings.Split(line, "/")
|
||||
|
||||
for len(parts) != 3 {
|
||||
if !fileReader.Scan() {
|
||||
return "", errors.New("Found no docker cgroups")
|
||||
}
|
||||
line = fileReader.Text()
|
||||
parts = strings.Split(line, "/")
|
||||
if len(parts) == 3 {
|
||||
if strings.HasSuffix(parts[1], "docker") {
|
||||
break
|
||||
} else {
|
||||
parts = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return parts[len(parts)-1:][0], nil
|
||||
}
|
||||
|
||||
@@ -5,14 +5,16 @@ import (
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/rancher/os/config"
|
||||
"github.com/rancher/os/log"
|
||||
)
|
||||
|
||||
func Main() {
|
||||
log.InitLogger()
|
||||
app := cli.NewApp()
|
||||
|
||||
app.Name = os.Args[0]
|
||||
app.Usage = "Control and configure RancherOS"
|
||||
app.Version = config.VERSION
|
||||
app.Version = config.Version
|
||||
app.Author = "Rancher Labs, Inc."
|
||||
app.Email = "sid@rancher.com"
|
||||
app.EnableBashCompletion = true
|
||||
@@ -31,7 +33,7 @@ func Main() {
|
||||
app.Run(os.Args)
|
||||
}
|
||||
|
||||
func shutdown(c *cli.Context) {
|
||||
func shutdown(c *cli.Context) error {
|
||||
common("")
|
||||
reboot := c.String("r")
|
||||
poweroff := c.String("h")
|
||||
@@ -39,6 +41,8 @@ func shutdown(c *cli.Context) {
|
||||
if reboot == "now" {
|
||||
Reboot()
|
||||
} else if poweroff == "now" {
|
||||
PowerOff()
|
||||
Off()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -12,17 +12,18 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/rancher/os/log"
|
||||
)
|
||||
|
||||
var (
|
||||
running bool = true
|
||||
processes map[int]*os.Process = map[int]*os.Process{}
|
||||
processLock = sync.Mutex{}
|
||||
running = true
|
||||
processes = map[int]*os.Process{}
|
||||
processLock = sync.Mutex{}
|
||||
)
|
||||
|
||||
func Main() {
|
||||
log.InitLogger()
|
||||
runtime.GOMAXPROCS(1)
|
||||
runtime.LockOSThread()
|
||||
app := cli.NewApp()
|
||||
@@ -48,7 +49,7 @@ func setupSigterm() {
|
||||
}()
|
||||
}
|
||||
|
||||
func run(c *cli.Context) {
|
||||
func run(c *cli.Context) error {
|
||||
setupSigterm()
|
||||
|
||||
var stream io.Reader = os.Stdin
|
||||
@@ -79,6 +80,7 @@ func run(c *cli.Context) {
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
func addProcess(process *os.Process) {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package sysinit
|
||||
|
||||
import (
|
||||
log "github.com/Sirupsen/logrus"
|
||||
initPkg "github.com/rancher/os/init"
|
||||
"github.com/rancher/os/log"
|
||||
)
|
||||
|
||||
func Main() {
|
||||
log.InitLogger()
|
||||
if err := initPkg.SysInit(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -2,29 +2,21 @@ package systemdocker
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/docker"
|
||||
"github.com/rancher/os/config"
|
||||
"github.com/rancher/os/log"
|
||||
)
|
||||
|
||||
func Main() {
|
||||
var newEnv []string
|
||||
for _, env := range os.Environ() {
|
||||
if !strings.HasPrefix(env, "DOCKER_HOST=") {
|
||||
newEnv = append(newEnv, env)
|
||||
}
|
||||
}
|
||||
|
||||
newEnv = append(newEnv, "DOCKER_HOST="+config.DOCKER_SYSTEM_HOST)
|
||||
|
||||
log.InitLogger()
|
||||
if os.Geteuid() != 0 {
|
||||
log.Fatalf("%s: Need to be root", os.Args[0])
|
||||
}
|
||||
|
||||
os.Args[0] = config.DOCKER_DIST_BIN
|
||||
if err := syscall.Exec(os.Args[0], os.Args, newEnv); err != nil {
|
||||
log.Fatal(err)
|
||||
if os.Getenv("DOCKER_HOST") == "" {
|
||||
os.Setenv("DOCKER_HOST", config.SystemDockerHost)
|
||||
}
|
||||
|
||||
docker.Main()
|
||||
}
|
||||
|
||||
@@ -1,268 +0,0 @@
|
||||
package userdocker
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/docker/libcompose/docker"
|
||||
"github.com/docker/libcompose/project"
|
||||
"github.com/rancher/os/cmd/control"
|
||||
"github.com/rancher/os/compose"
|
||||
"github.com/rancher/os/config"
|
||||
|
||||
"github.com/opencontainers/runc/libcontainer/cgroups"
|
||||
_ "github.com/opencontainers/runc/libcontainer/nsenter"
|
||||
"github.com/opencontainers/runc/libcontainer/system"
|
||||
)
|
||||
|
||||
const (
|
||||
DEFAULT_STORAGE_CONTEXT = "console"
|
||||
userDocker = "user-docker"
|
||||
)
|
||||
|
||||
func Main() {
|
||||
cfg, err := config.LoadConfig()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if len(os.Args) == 1 {
|
||||
if err := enter(cfg); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
if err := main(cfg); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func enter(cfg *config.CloudConfig) error {
|
||||
context := cfg.Rancher.Docker.StorageContext
|
||||
if context == "" {
|
||||
context = DEFAULT_STORAGE_CONTEXT
|
||||
}
|
||||
|
||||
log.Infof("Starting Docker in context: %s", context)
|
||||
|
||||
p, err := compose.GetProject(cfg, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pid, err := waitForPid(context, p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("%s PID %d", context, pid)
|
||||
|
||||
return runNsenter(pid)
|
||||
}
|
||||
|
||||
type result struct {
|
||||
Pid int `json:"Pid"`
|
||||
}
|
||||
|
||||
func findProgram(searchPaths ...string) string {
|
||||
prog := ""
|
||||
|
||||
for _, i := range searchPaths {
|
||||
var err error
|
||||
prog, err = exec.LookPath(i)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
prog = i
|
||||
}
|
||||
|
||||
return prog
|
||||
}
|
||||
|
||||
func runNsenter(pid int) error {
|
||||
args := []string{findProgram(userDocker), "main"}
|
||||
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := &exec.Cmd{
|
||||
Path: args[0],
|
||||
Args: args,
|
||||
Stdin: os.Stdin,
|
||||
Stdout: os.Stdout,
|
||||
Stderr: os.Stderr,
|
||||
ExtraFiles: []*os.File{w},
|
||||
Env: append(os.Environ(),
|
||||
"_LIBCONTAINER_INITPIPE=3",
|
||||
fmt.Sprintf("_LIBCONTAINER_INITPID=%d", pid),
|
||||
),
|
||||
}
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
w.Close()
|
||||
|
||||
var result result
|
||||
if err := json.NewDecoder(r).Decode(&result); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := cmd.Wait(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("Docker PID %d", result.Pid)
|
||||
|
||||
p, err := os.FindProcess(result.Pid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
handleTerm(p)
|
||||
|
||||
if err := switchCgroup(result.Pid, pid); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = p.Wait()
|
||||
return err
|
||||
}
|
||||
|
||||
func handleTerm(p *os.Process) {
|
||||
term := make(chan os.Signal)
|
||||
signal.Notify(term, syscall.SIGTERM)
|
||||
go func() {
|
||||
<-term
|
||||
p.Signal(syscall.SIGTERM)
|
||||
}()
|
||||
}
|
||||
|
||||
func waitForPid(service string, project *project.Project) (int, error) {
|
||||
log.Infof("Getting PID for service: %s", service)
|
||||
for {
|
||||
if pid, err := getPid(service, project); err != nil || pid == 0 {
|
||||
log.Infof("Waiting for %s : %d : %v", service, pid, err)
|
||||
time.Sleep(1 * time.Second)
|
||||
} else {
|
||||
return pid, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getPid(service string, project *project.Project) (int, error) {
|
||||
s, err := project.CreateService(service)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
containers, err := s.Containers()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if len(containers) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
client, err := docker.CreateClient(docker.ClientOpts{
|
||||
Host: config.DOCKER_SYSTEM_HOST,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
id, err := containers[0].ID()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
info, err := client.InspectContainer(id)
|
||||
if err != nil || info == nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if info.State.Running {
|
||||
return info.State.Pid, nil
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func main(cfg *config.CloudConfig) error {
|
||||
os.Unsetenv("_LIBCONTAINER_INITPIPE")
|
||||
os.Unsetenv("_LIBCONTAINER_INITPID")
|
||||
|
||||
if err := system.ParentDeathSignal(syscall.SIGKILL).Set(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.Remove("/var/run/docker.pid"); err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
dockerCfg := cfg.Rancher.Docker
|
||||
|
||||
args := dockerCfg.FullArgs()
|
||||
|
||||
log.Debugf("User Docker args: %v", args)
|
||||
|
||||
if dockerCfg.TLS {
|
||||
log.Debug("Generating TLS certs if needed")
|
||||
if err := control.Generate(true, "/etc/docker/tls", []string{"127.0.0.1", "*", "*.*", "*.*.*", "*.*.*.*"}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
prog := findProgram("docker-init", "dockerlaunch", "docker")
|
||||
if strings.Contains(prog, "dockerlaunch") {
|
||||
args = append([]string{prog, "docker"}, args...)
|
||||
} else {
|
||||
args = append([]string{prog}, args...)
|
||||
}
|
||||
|
||||
log.Infof("Running %v", args)
|
||||
return syscall.Exec(args[0], args, dockerCfg.AppendEnv())
|
||||
}
|
||||
|
||||
func switchCgroup(src, target int) error {
|
||||
cgroupFile := fmt.Sprintf("/proc/%d/cgroup", target)
|
||||
f, err := os.Open(cgroupFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
targetCgroups := map[string]string{}
|
||||
|
||||
s := bufio.NewScanner(f)
|
||||
for s.Scan() {
|
||||
text := s.Text()
|
||||
parts := strings.Split(text, ":")
|
||||
subparts := strings.Split(parts[1], "=")
|
||||
subsystem := subparts[0]
|
||||
if len(subparts) > 1 {
|
||||
subsystem = subparts[1]
|
||||
}
|
||||
|
||||
targetPath := fmt.Sprintf("/host/sys/fs/cgroup/%s%s", subsystem, parts[2])
|
||||
log.Infof("Moving Docker to cgroup %s", targetPath)
|
||||
targetCgroups[subsystem] = targetPath
|
||||
}
|
||||
|
||||
if err := s.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return cgroups.EnterPid(targetCgroups, src)
|
||||
}
|
||||
@@ -3,17 +3,18 @@ package wait
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/rancher/os/config"
|
||||
"github.com/rancher/os/docker"
|
||||
"github.com/rancher/os/log"
|
||||
)
|
||||
|
||||
func Main() {
|
||||
_, err := docker.NewClient(config.DOCKER_HOST)
|
||||
log.InitLogger()
|
||||
_, err := docker.NewClient(config.DockerHost)
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to conect to Docker")
|
||||
log.Errorf("Failed to connect to Docker")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
logrus.Infof("Docker is ready")
|
||||
log.Infof("Docker is ready")
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
package waitfornetwork
|
||||
|
||||
import (
|
||||
"github.com/rancher/os/cmd/network"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func handleTerm() {
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, syscall.SIGTERM)
|
||||
<-c
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func Main() {
|
||||
go handleTerm()
|
||||
if _, err := os.Stat(network.NETWORK_DONE); err == nil {
|
||||
os.Exit(0)
|
||||
}
|
||||
select {}
|
||||
}
|
||||
@@ -2,26 +2,30 @@ package compose
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
yaml "github.com/cloudfoundry-incubator/candiedyaml"
|
||||
"github.com/docker/libcompose/cli/logger"
|
||||
composeConfig "github.com/docker/libcompose/config"
|
||||
"github.com/docker/libcompose/docker"
|
||||
composeClient "github.com/docker/libcompose/docker/client"
|
||||
"github.com/docker/libcompose/project"
|
||||
"github.com/docker/libcompose/project/events"
|
||||
"github.com/docker/libcompose/project/options"
|
||||
"github.com/rancher/os/config"
|
||||
rosDocker "github.com/rancher/os/docker"
|
||||
"github.com/rancher/os/log"
|
||||
"github.com/rancher/os/util"
|
||||
"github.com/rancher/os/util/network"
|
||||
)
|
||||
|
||||
func CreateService(cfg *config.CloudConfig, name string, serviceConfig *project.ServiceConfig) (project.Service, error) {
|
||||
func CreateService(cfg *config.CloudConfig, name string, serviceConfig *composeConfig.ServiceConfigV1) (project.Service, error) {
|
||||
if cfg == nil {
|
||||
var err error
|
||||
cfg, err = config.LoadConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg = config.LoadConfig()
|
||||
}
|
||||
|
||||
p, err := CreateServiceSet("once", cfg, map[string]*project.ServiceConfig{
|
||||
p, err := CreateServiceSet("once", cfg, map[string]*composeConfig.ServiceConfigV1{
|
||||
name: serviceConfig,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -31,8 +35,8 @@ func CreateService(cfg *config.CloudConfig, name string, serviceConfig *project.
|
||||
return p.CreateService(name)
|
||||
}
|
||||
|
||||
func CreateServiceSet(name string, cfg *config.CloudConfig, configs map[string]*project.ServiceConfig) (*project.Project, error) {
|
||||
p, err := newProject(name, cfg, nil)
|
||||
func CreateServiceSet(name string, cfg *config.CloudConfig, configs map[string]*composeConfig.ServiceConfigV1) (*project.Project, error) {
|
||||
p, err := newProject(name, cfg, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -42,21 +46,22 @@ func CreateServiceSet(name string, cfg *config.CloudConfig, configs map[string]*
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func RunServiceSet(name string, cfg *config.CloudConfig, configs map[string]*project.ServiceConfig) (*project.Project, error) {
|
||||
func RunServiceSet(name string, cfg *config.CloudConfig, configs map[string]*composeConfig.ServiceConfigV1) (*project.Project, error) {
|
||||
p, err := CreateServiceSet(name, cfg, configs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return p, p.Up()
|
||||
return p, p.Up(context.Background(), options.Up{
|
||||
Log: cfg.Rancher.Log,
|
||||
})
|
||||
}
|
||||
|
||||
func GetProject(cfg *config.CloudConfig, networkingAvailable bool) (*project.Project, error) {
|
||||
return newCoreServiceProject(cfg, networkingAvailable)
|
||||
func GetProject(cfg *config.CloudConfig, networkingAvailable, loadConsole bool) (*project.Project, error) {
|
||||
return newCoreServiceProject(cfg, networkingAvailable, loadConsole)
|
||||
}
|
||||
|
||||
func newProject(name string, cfg *config.CloudConfig, environmentLookup project.EnvironmentLookup) (*project.Project, error) {
|
||||
clientFactory, err := rosDocker.NewClientFactory(docker.ClientOpts{})
|
||||
func newProject(name string, cfg *config.CloudConfig, environmentLookup composeConfig.EnvironmentLookup, authLookup *rosDocker.ConfigAuthLookup) (*project.Project, error) {
|
||||
clientFactory, err := rosDocker.NewClientFactory(composeClient.Options{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -64,31 +69,89 @@ func newProject(name string, cfg *config.CloudConfig, environmentLookup project.
|
||||
if environmentLookup == nil {
|
||||
environmentLookup = rosDocker.NewConfigEnvironment(cfg)
|
||||
}
|
||||
if authLookup == nil {
|
||||
authLookup = rosDocker.NewConfigAuthLookup(cfg)
|
||||
}
|
||||
|
||||
serviceFactory := &rosDocker.ServiceFactory{
|
||||
Deps: map[string][]string{},
|
||||
}
|
||||
context := &docker.Context{
|
||||
ClientFactory: clientFactory,
|
||||
AuthLookup: authLookup,
|
||||
Context: project.Context{
|
||||
ProjectName: name,
|
||||
NoRecreate: true, // for libcompose to not recreate on project reload, looping up the boot :)
|
||||
EnvironmentLookup: environmentLookup,
|
||||
ServiceFactory: serviceFactory,
|
||||
Log: cfg.Rancher.Log,
|
||||
LoggerFactory: logger.NewColorLoggerFactory(),
|
||||
},
|
||||
}
|
||||
serviceFactory.Context = context
|
||||
|
||||
return docker.NewProject(context)
|
||||
authLookup.SetContext(context)
|
||||
|
||||
return docker.NewProject(context, &composeConfig.ParseOptions{
|
||||
Interpolate: true,
|
||||
Validate: false,
|
||||
Preprocess: preprocessServiceMap,
|
||||
})
|
||||
}
|
||||
|
||||
func addServices(p *project.Project, enabled map[interface{}]interface{}, configs map[string]*project.ServiceConfig) map[interface{}]interface{} {
|
||||
func preprocessServiceMap(serviceMap composeConfig.RawServiceMap) (composeConfig.RawServiceMap, error) {
|
||||
newServiceMap := make(composeConfig.RawServiceMap)
|
||||
|
||||
for k, v := range serviceMap {
|
||||
newServiceMap[k] = make(composeConfig.RawService)
|
||||
|
||||
for k2, v2 := range v {
|
||||
if k2 == "environment" || k2 == "labels" {
|
||||
newServiceMap[k][k2] = preprocess(v2, true)
|
||||
} else {
|
||||
newServiceMap[k][k2] = preprocess(v2, false)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return newServiceMap, nil
|
||||
}
|
||||
|
||||
func preprocess(item interface{}, replaceTypes bool) interface{} {
|
||||
switch typedDatas := item.(type) {
|
||||
|
||||
case map[interface{}]interface{}:
|
||||
newMap := make(map[interface{}]interface{})
|
||||
|
||||
for key, value := range typedDatas {
|
||||
newMap[key] = preprocess(value, replaceTypes)
|
||||
}
|
||||
return newMap
|
||||
|
||||
case []interface{}:
|
||||
// newArray := make([]interface{}, 0) will cause golint to complain
|
||||
var newArray []interface{}
|
||||
newArray = make([]interface{}, 0)
|
||||
|
||||
for _, value := range typedDatas {
|
||||
newArray = append(newArray, preprocess(value, replaceTypes))
|
||||
}
|
||||
return newArray
|
||||
|
||||
default:
|
||||
if replaceTypes {
|
||||
return fmt.Sprint(item)
|
||||
}
|
||||
return item
|
||||
}
|
||||
}
|
||||
|
||||
func addServices(p *project.Project, enabled map[interface{}]interface{}, configs map[string]*composeConfig.ServiceConfigV1) map[interface{}]interface{} {
|
||||
serviceConfigsV2, _ := composeConfig.ConvertServices(configs)
|
||||
|
||||
// Note: we ignore errors while loading services
|
||||
unchanged := true
|
||||
for name, serviceConfig := range configs {
|
||||
hash := project.GetServiceHash(name, serviceConfig)
|
||||
for name, serviceConfig := range serviceConfigsV2 {
|
||||
hash := composeConfig.GetServiceHash(name, serviceConfig)
|
||||
|
||||
if enabled[name] == hash {
|
||||
continue
|
||||
@@ -121,72 +184,25 @@ func adjustContainerNames(m map[interface{}]interface{}) map[interface{}]interfa
|
||||
return m
|
||||
}
|
||||
|
||||
func newCoreServiceProject(cfg *config.CloudConfig, network bool) (*project.Project, error) {
|
||||
projectEvents := make(chan project.Event)
|
||||
enabled := map[interface{}]interface{}{}
|
||||
|
||||
func newCoreServiceProject(cfg *config.CloudConfig, useNetwork, loadConsole bool) (*project.Project, error) {
|
||||
environmentLookup := rosDocker.NewConfigEnvironment(cfg)
|
||||
authLookup := rosDocker.NewConfigAuthLookup(cfg)
|
||||
|
||||
p, err := newProject("os", cfg, environmentLookup)
|
||||
p, err := newProject("os", cfg, environmentLookup, authLookup)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
projectEvents := make(chan events.Event)
|
||||
p.AddListener(project.NewDefaultListener(p))
|
||||
p.AddListener(projectEvents)
|
||||
|
||||
p.ReloadCallback = func() error {
|
||||
var err error
|
||||
cfg, err = config.LoadConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
environmentLookup.SetConfig(cfg)
|
||||
|
||||
enabled = addServices(p, enabled, cfg.Rancher.Services)
|
||||
|
||||
for service, serviceEnabled := range cfg.Rancher.ServicesInclude {
|
||||
if _, ok := enabled[service]; ok || !serviceEnabled {
|
||||
continue
|
||||
}
|
||||
|
||||
bytes, err := LoadServiceResource(service, network, cfg)
|
||||
if err != nil {
|
||||
if err == util.ErrNoNetwork {
|
||||
log.Debugf("Can not load %s, networking not enabled", service)
|
||||
} else {
|
||||
log.Errorf("Failed to load %s : %v", service, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
m := map[interface{}]interface{}{}
|
||||
if err := yaml.Unmarshal(bytes, &m); err != nil {
|
||||
log.Errorf("Failed to parse YAML configuration: %s : %v", service, err)
|
||||
continue
|
||||
}
|
||||
bytes, err = yaml.Marshal(adjustContainerNames(config.StringifyValues(m)))
|
||||
if err != nil {
|
||||
log.Errorf("Failed to marshal YAML configuration: %s : %v", service, err)
|
||||
continue
|
||||
}
|
||||
err = p.Load(bytes)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to load %s : %v", service, err)
|
||||
continue
|
||||
}
|
||||
|
||||
enabled[service] = service
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
p.ReloadCallback = projectReload(p, &useNetwork, loadConsole, environmentLookup, authLookup)
|
||||
|
||||
go func() {
|
||||
for event := range projectEvents {
|
||||
if event.EventType == project.EventContainerStarted && event.ServiceName == "ntp" {
|
||||
network = true
|
||||
if event.EventType == events.ContainerStarted && event.ServiceName == "network" {
|
||||
useNetwork = true
|
||||
}
|
||||
}
|
||||
}()
|
||||
@@ -201,13 +217,13 @@ func newCoreServiceProject(cfg *config.CloudConfig, network bool) (*project.Proj
|
||||
}
|
||||
|
||||
func StageServices(cfg *config.CloudConfig, services ...string) error {
|
||||
p, err := newProject("stage-services", cfg, nil)
|
||||
p, err := newProject("stage-services", cfg, nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, service := range services {
|
||||
bytes, err := LoadServiceResource(service, true, cfg)
|
||||
bytes, err := network.LoadServiceResource(service, true, cfg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to load %s : %v", service, err)
|
||||
}
|
||||
@@ -217,29 +233,25 @@ func StageServices(cfg *config.CloudConfig, services ...string) error {
|
||||
return fmt.Errorf("Failed to parse YAML configuration: %s : %v", service, err)
|
||||
}
|
||||
|
||||
bytes, err = yaml.Marshal(config.StringifyValues(m))
|
||||
bytes, err = yaml.Marshal(m)
|
||||
if err != nil {
|
||||
fmt.Errorf("Failed to marshal YAML configuration: %s : %v", service, err)
|
||||
return fmt.Errorf("Failed to marshal YAML configuration: %s : %v", service, err)
|
||||
}
|
||||
|
||||
err = p.Load(bytes)
|
||||
if err != nil {
|
||||
fmt.Errorf("Failed to load %s : %v", service, err)
|
||||
return fmt.Errorf("Failed to load %s : %v", service, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Reduce service configurations to just image and labels
|
||||
for serviceName, serviceConfig := range p.Configs {
|
||||
p.Configs[serviceName] = &project.ServiceConfig{
|
||||
for _, serviceName := range p.ServiceConfigs.Keys() {
|
||||
serviceConfig, _ := p.ServiceConfigs.Get(serviceName)
|
||||
p.ServiceConfigs.Add(serviceName, &composeConfig.ServiceConfig{
|
||||
Image: serviceConfig.Image,
|
||||
Labels: serviceConfig.Labels,
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
return p.Pull()
|
||||
}
|
||||
|
||||
func LoadServiceResource(name string, network bool, cfg *config.CloudConfig) ([]byte, error) {
|
||||
return util.LoadResource(name, network, cfg.Rancher.Repositories.ToArray())
|
||||
return p.Pull(context.Background())
|
||||
}
|
||||
|
||||
112
compose/reload.go
Normal file
112
compose/reload.go
Normal file
@@ -0,0 +1,112 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
yaml "github.com/cloudfoundry-incubator/candiedyaml"
|
||||
composeConfig "github.com/docker/libcompose/config"
|
||||
"github.com/docker/libcompose/project"
|
||||
"github.com/rancher/os/config"
|
||||
"github.com/rancher/os/docker"
|
||||
"github.com/rancher/os/log"
|
||||
"github.com/rancher/os/util/network"
|
||||
)
|
||||
|
||||
func LoadService(p *project.Project, cfg *config.CloudConfig, useNetwork bool, service string) error {
|
||||
bytes, err := network.LoadServiceResource(service, useNetwork, cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m := map[interface{}]interface{}{}
|
||||
if err = yaml.Unmarshal(bytes, &m); err != nil {
|
||||
return fmt.Errorf("Failed to parse YAML configuration for %s: %v", service, err)
|
||||
}
|
||||
|
||||
m = adjustContainerNames(m)
|
||||
|
||||
bytes, err = yaml.Marshal(m)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to marshal YAML configuration for %s: %v", service, err)
|
||||
}
|
||||
|
||||
if err = p.Load(bytes); err != nil {
|
||||
return fmt.Errorf("Failed to load %s: %v", service, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func LoadSpecialService(p *project.Project, cfg *config.CloudConfig, serviceName, serviceValue string) error {
|
||||
// Save config in case load fails
|
||||
previousConfig, ok := p.ServiceConfigs.Get(serviceName)
|
||||
|
||||
p.ServiceConfigs.Add(serviceName, &composeConfig.ServiceConfig{})
|
||||
|
||||
if err := LoadService(p, cfg, true, serviceValue); err != nil {
|
||||
// Rollback to previous config
|
||||
if ok {
|
||||
p.ServiceConfigs.Add(serviceName, previousConfig)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadConsoleService(cfg *config.CloudConfig, p *project.Project) error {
|
||||
if cfg.Rancher.Console == "" || cfg.Rancher.Console == "default" {
|
||||
return nil
|
||||
}
|
||||
return LoadSpecialService(p, cfg, "console", cfg.Rancher.Console)
|
||||
}
|
||||
|
||||
func loadEngineService(cfg *config.CloudConfig, p *project.Project) error {
|
||||
if cfg.Rancher.Docker.Engine == "" || cfg.Rancher.Docker.Engine == cfg.Rancher.Defaults.Docker.Engine {
|
||||
return nil
|
||||
}
|
||||
return LoadSpecialService(p, cfg, "docker", cfg.Rancher.Docker.Engine)
|
||||
}
|
||||
|
||||
func projectReload(p *project.Project, useNetwork *bool, loadConsole bool, environmentLookup *docker.ConfigEnvironment, authLookup *docker.ConfigAuthLookup) func() error {
|
||||
enabled := map[interface{}]interface{}{}
|
||||
return func() error {
|
||||
cfg := config.LoadConfig()
|
||||
|
||||
environmentLookup.SetConfig(cfg)
|
||||
authLookup.SetConfig(cfg)
|
||||
|
||||
enabled = addServices(p, enabled, cfg.Rancher.Services)
|
||||
|
||||
for service, serviceEnabled := range cfg.Rancher.ServicesInclude {
|
||||
if _, ok := enabled[service]; ok || !serviceEnabled {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := LoadService(p, cfg, *useNetwork, service); err != nil {
|
||||
if err != network.ErrNoNetwork {
|
||||
log.Error(err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
enabled[service] = service
|
||||
}
|
||||
|
||||
if !*useNetwork {
|
||||
return nil
|
||||
}
|
||||
|
||||
if loadConsole {
|
||||
if err := loadConsoleService(cfg, p); err != nil {
|
||||
log.Errorf("Failed to load console: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := loadEngineService(cfg, p); err != nil {
|
||||
log.Errorf("Failed to load engine: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
9
config/cloudinit/.travis.yml
Normal file
9
config/cloudinit/.travis.yml
Normal file
@@ -0,0 +1,9 @@
|
||||
language: go
|
||||
matrix:
|
||||
include:
|
||||
- go: 1.5
|
||||
env: GO15VENDOREXPERIMENT=1
|
||||
- go: 1.6
|
||||
|
||||
script:
|
||||
- ./test
|
||||
38
config/cloudinit/Documentation/cloud-config-deprecated.md
Normal file
38
config/cloudinit/Documentation/cloud-config-deprecated.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# Deprecated Cloud-Config Features
|
||||
|
||||
## Retrieving SSH Authorized Keys
|
||||
|
||||
### From a GitHub User
|
||||
|
||||
Using the `coreos-ssh-import-github` field, we can import public SSH keys from a GitHub user to use as authorized keys to a server.
|
||||
|
||||
```yaml
|
||||
#cloud-config
|
||||
|
||||
users:
|
||||
- name: elroy
|
||||
coreos-ssh-import-github: elroy
|
||||
```
|
||||
|
||||
### From an HTTP Endpoint
|
||||
|
||||
We can also pull public SSH keys from any HTTP endpoint which matches [GitHub's API response format](https://developer.github.com/v3/users/keys/#list-public-keys-for-a-user).
|
||||
For example, if you have an installation of GitHub Enterprise, you can provide a complete URL with an authentication token:
|
||||
|
||||
```yaml
|
||||
#cloud-config
|
||||
|
||||
users:
|
||||
- name: elroy
|
||||
coreos-ssh-import-url: https://github-enterprise.example.com/api/v3/users/elroy/keys?access_token=<TOKEN>
|
||||
```
|
||||
|
||||
You can also specify any URL whose response matches the JSON format for public keys:
|
||||
|
||||
```yaml
|
||||
#cloud-config
|
||||
|
||||
users:
|
||||
- name: elroy
|
||||
coreos-ssh-import-url: https://example.com/public-keys
|
||||
```
|
||||
26
config/cloudinit/Documentation/cloud-config-locations.md
Normal file
26
config/cloudinit/Documentation/cloud-config-locations.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Cloud-Config Locations
|
||||
|
||||
On every boot, coreos-cloudinit looks for a config file to configure your host. Here is a list of locations which are used by the Cloud-Config utility, depending on your CoreOS platform:
|
||||
|
||||
| Location | Description |
|
||||
| --- | --- |
|
||||
| `/media/configvirtfs/openstack/latest/user_data` | `/media/configvirtfs` mount point with [config-2](/os/docs/latest/config-drive.html#contents-and-format) label. It should contain a `openstack/latest/user_data` relative path. Usually used by cloud providers or in VM installations. |
|
||||
| `/media/configdrive/openstack/latest/user_data` | FAT or ISO9660 filesystem with [config-2](/os/docs/latest/config-drive.html#qemu-virtfs) label and `/media/configdrive/` mount point. It should also contain a `openstack/latest/user_data` relative path. Usually used in installations which are configured by USB Flash sticks or CDROM media. |
|
||||
| Kernel command line: `cloud-config-url=http://example.com/user_data`. | You can find this string using this command `cat /proc/cmdline`. Usually used in [PXE](/os/docs/latest/booting-with-pxe.html) or [iPXE](/os/docs/latest/booting-with-ipxe.html) boots. |
|
||||
| `/var/lib/coreos-install/user_data` | When you install CoreOS manually using the [coreos-install](/os/docs/latest/installing-to-disk.html) tool. Usually used in bare metal installations. |
|
||||
| `/usr/share/oem/cloud-config.yml` | Path for OEM images. |
|
||||
| `/var/lib/coreos-vagrant/vagrantfile-user-data`| Vagrant OEM scripts automatically store Cloud-Config into this path. |
|
||||
| `/var/lib/waagent/CustomData`| Azure platform uses OEM path for first Cloud-Config initialization and then `/var/lib/waagent/CustomData` to apply your settings. |
|
||||
| `http://169.254.169.254/metadata/v1/user-data` `http://169.254.169.254/2009-04-04/user-data` `https://metadata.packet.net/userdata`|DigitalOcean, EC2 and Packet cloud providers correspondingly use these URLs to download Cloud-Config.|
|
||||
| `/usr/share/oem/bin/vmtoolsd --cmd "info-get guestinfo.coreos.config.data"` | Cloud-Config provided by [VMware Guestinfo][VMware Guestinfo] |
|
||||
| `/usr/share/oem/bin/vmtoolsd --cmd "info-get guestinfo.coreos.config.url"` | Cloud-Config URL provided by [VMware Guestinfo][VMware Guestinfo] |
|
||||
|
||||
[VMware Guestinfo]: vmware-guestinfo.md
|
||||
|
||||
You can also run the `coreos-cloudinit` tool manually and provide a path to your custom Cloud-Config file:
|
||||
|
||||
```sh
|
||||
sudo coreos-cloudinit --from-file=/home/core/cloud-config.yaml
|
||||
```
|
||||
|
||||
This command will apply your custom cloud-config.
|
||||
37
config/cloudinit/Documentation/cloud-config-oem.md
Normal file
37
config/cloudinit/Documentation/cloud-config-oem.md
Normal file
@@ -0,0 +1,37 @@
|
||||
## OEM configuration
|
||||
|
||||
The `coreos.oem.*` parameters follow the [os-release spec][os-release], but have been repurposed as a way for coreos-cloudinit to know about the OEM partition on this machine. Customizing this section is only needed when generating a new OEM of CoreOS from the SDK. The fields include:
|
||||
|
||||
- **id**: Lowercase string identifying the OEM
|
||||
- **name**: Human-friendly string representing the OEM
|
||||
- **version-id**: Lowercase string identifying the version of the OEM
|
||||
- **home-url**: Link to the homepage of the provider or OEM
|
||||
- **bug-report-url**: Link to a place to file bug reports about this OEM
|
||||
|
||||
coreos-cloudinit renders these fields to `/etc/oem-release`.
|
||||
If no **id** field is provided, coreos-cloudinit will ignore this section.
|
||||
|
||||
For example, the following cloud-config document...
|
||||
|
||||
```yaml
|
||||
#cloud-config
|
||||
coreos:
|
||||
oem:
|
||||
id: "rackspace"
|
||||
name: "Rackspace Cloud Servers"
|
||||
version-id: "168.0.0"
|
||||
home-url: "https://www.rackspace.com/cloud/servers/"
|
||||
bug-report-url: "https://github.com/coreos/coreos-overlay"
|
||||
```
|
||||
|
||||
...would be rendered to the following `/etc/oem-release`:
|
||||
|
||||
```yaml
|
||||
ID=rackspace
|
||||
NAME="Rackspace Cloud Servers"
|
||||
VERSION_ID=168.0.0
|
||||
HOME_URL="https://www.rackspace.com/cloud/servers/"
|
||||
BUG_REPORT_URL="https://github.com/coreos/coreos-overlay"
|
||||
```
|
||||
|
||||
[os-release]: http://www.freedesktop.org/software/systemd/man/os-release.html
|
||||
485
config/cloudinit/Documentation/cloud-config.md
Normal file
485
config/cloudinit/Documentation/cloud-config.md
Normal file
@@ -0,0 +1,485 @@
|
||||
# Using Cloud-Config
|
||||
|
||||
CoreOS allows you to declaratively customize various OS-level items, such as network configuration, user accounts, and systemd units. This document describes the full list of items we can configure. The `coreos-cloudinit` program uses these files as it configures the OS after startup or during runtime.
|
||||
|
||||
Your cloud-config is processed during each boot. Invalid cloud-config won't be processed but will be logged in the journal. You can validate your cloud-config with the [CoreOS online validator](https://coreos.com/validate/) or by running `coreos-cloudinit -validate`. In addition to these two validation methods you can debug `coreos-cloudinit` system output through the `journalctl` tool:
|
||||
|
||||
```sh
|
||||
journalctl --identifier=coreos-cloudinit
|
||||
```
|
||||
|
||||
It will show `coreos-cloudinit` run output which was triggered by system boot.
|
||||
|
||||
## Configuration File
|
||||
|
||||
The file used by this system initialization program is called a "cloud-config" file. It is inspired by the [cloud-init][cloud-init] project's [cloud-config][cloud-config] file, which is "the defacto multi-distribution package that handles early initialization of a cloud instance" ([cloud-init docs][cloud-init-docs]). Because the cloud-init project includes tools which aren't used by CoreOS, only the relevant subset of its configuration items will be implemented in our cloud-config file. In addition to those, we added a few CoreOS-specific items, such as etcd configuration, OEM definition, and systemd units.
|
||||
|
||||
We've designed our implementation to allow the same cloud-config file to work across all of our supported platforms.
|
||||
|
||||
[cloud-init]: https://launchpad.net/cloud-init
|
||||
[cloud-init-docs]: http://cloudinit.readthedocs.org/en/latest/index.html
|
||||
[cloud-config]: http://cloudinit.readthedocs.org/en/latest/topics/format.html#cloud-config-data
|
||||
|
||||
### File Format
|
||||
|
||||
The cloud-config file uses the [YAML][yaml] file format, which uses whitespace and new-lines to delimit lists, associative arrays, and values.
|
||||
|
||||
A cloud-config file must contain a header: either `#cloud-config` for processing as cloud-config (suggested) or `#!` for processing as a shell script (advanced). If cloud-config has the `#cloud-config` header, it should followed by an associative array which has zero or more of the following keys:
|
||||
|
||||
- `coreos`
|
||||
- `ssh_authorized_keys`
|
||||
- `hostname`
|
||||
- `users`
|
||||
- `write_files`
|
||||
- `manage_etc_hosts`
|
||||
|
||||
The expected values for these keys are defined in the rest of this document.
|
||||
|
||||
If cloud-config header starts on `#!` then coreos-cloudinit will recognize it as shell script which is interpreted by bash and run it as transient systemd service.
|
||||
|
||||
[yaml]: https://en.wikipedia.org/wiki/YAML
|
||||
|
||||
### Providing Cloud-Config with Config-Drive
|
||||
|
||||
CoreOS tries to conform to each platform's native method to provide user data. Each cloud provider tends to be unique, but this complexity has been abstracted by CoreOS. You can view each platform's instructions on their documentation pages. The most universal way to provide cloud-config is [via config-drive](https://github.com/rancher/os/config/cloudinit/blob/master/Documentation/config-drive.md), which attaches a read-only device to the machine, that contains your cloud-config file.
|
||||
|
||||
## Configuration Parameters
|
||||
|
||||
### coreos
|
||||
|
||||
#### etcd (deprecated. see etcd2)
|
||||
|
||||
The `coreos.etcd.*` parameters will be translated to a partial systemd unit acting as an etcd configuration file.
|
||||
If the platform environment supports the templating feature of coreos-cloudinit it is possible to automate etcd configuration with the `$private_ipv4` and `$public_ipv4` fields. For example, the following cloud-config document...
|
||||
|
||||
```yaml
|
||||
#cloud-config
|
||||
|
||||
coreos:
|
||||
etcd:
|
||||
name: "node001"
|
||||
# generate a new token for each unique cluster from https://discovery.etcd.io/new
|
||||
discovery: "https://discovery.etcd.io/<token>"
|
||||
# multi-region and multi-cloud deployments need to use $public_ipv4
|
||||
addr: "$public_ipv4:4001"
|
||||
peer-addr: "$private_ipv4:7001"
|
||||
```
|
||||
|
||||
...will generate a systemd unit drop-in for etcd.service with the following contents:
|
||||
|
||||
```yaml
|
||||
[Service]
|
||||
Environment="ETCD_NAME=node001"
|
||||
Environment="ETCD_DISCOVERY=https://discovery.etcd.io/<token>"
|
||||
Environment="ETCD_ADDR=203.0.113.29:4001"
|
||||
Environment="ETCD_PEER_ADDR=192.0.2.13:7001"
|
||||
```
|
||||
|
||||
For more information about the available configuration parameters, see the [etcd documentation][etcd-config].
|
||||
|
||||
_Note: The `$private_ipv4` and `$public_ipv4` substitution variables referenced in other documents are only supported on Amazon EC2, Google Compute Engine, OpenStack, Rackspace, DigitalOcean, and Vagrant._
|
||||
|
||||
[etcd-config]: https://github.com/coreos/etcd/blob/release-0.4/Documentation/configuration.md
|
||||
|
||||
#### etcd2
|
||||
|
||||
The `coreos.etcd2.*` parameters will be translated to a partial systemd unit acting as an etcd configuration file.
|
||||
If the platform environment supports the templating feature of coreos-cloudinit it is possible to automate etcd configuration with the `$private_ipv4` and `$public_ipv4` fields. When generating a [discovery token](https://discovery.etcd.io/new?size=3), set the `size` parameter, since etcd uses this to determine if all members have joined the cluster. After the cluster is bootstrapped, it can grow or shrink from this configured size.
|
||||
|
||||
For example, the following cloud-config document...
|
||||
|
||||
```yaml
|
||||
#cloud-config
|
||||
|
||||
coreos:
|
||||
etcd2:
|
||||
# generate a new token for each unique cluster from https://discovery.etcd.io/new?size=3
|
||||
discovery: "https://discovery.etcd.io/<token>"
|
||||
# multi-region and multi-cloud deployments need to use $public_ipv4
|
||||
advertise-client-urls: "http://$public_ipv4:2379"
|
||||
initial-advertise-peer-urls: "http://$private_ipv4:2380"
|
||||
# listen on both the official ports and the legacy ports
|
||||
# legacy ports can be omitted if your application doesn't depend on them
|
||||
listen-client-urls: "http://0.0.0.0:2379,http://0.0.0.0:4001"
|
||||
listen-peer-urls: "http://$private_ipv4:2380,http://$private_ipv4:7001"
|
||||
```
|
||||
|
||||
...will generate a systemd unit drop-in for etcd2.service with the following contents:
|
||||
|
||||
```yaml
|
||||
[Service]
|
||||
Environment="ETCD_DISCOVERY=https://discovery.etcd.io/<token>"
|
||||
Environment="ETCD_ADVERTISE_CLIENT_URLS=http://203.0.113.29:2379"
|
||||
Environment="ETCD_INITIAL_ADVERTISE_PEER_URLS=http://192.0.2.13:2380"
|
||||
Environment="ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379,http://0.0.0.0:4001"
|
||||
Environment="ETCD_LISTEN_PEER_URLS=http://192.0.2.13:2380,http://192.0.2.13:7001"
|
||||
```
|
||||
|
||||
For more information about the available configuration parameters, see the [etcd2 documentation][etcd2-config].
|
||||
|
||||
_Note: The `$private_ipv4` and `$public_ipv4` substitution variables referenced in other documents are only supported on Amazon EC2, Google Compute Engine, OpenStack, Rackspace, DigitalOcean, and Vagrant._
|
||||
|
||||
[etcd2-config]: https://github.com/coreos/etcd/blob/v2.3.2/Documentation/configuration.md
|
||||
|
||||
#### fleet
|
||||
|
||||
The `coreos.fleet.*` parameters work very similarly to `coreos.etcd2.*`, and allow for the configuration of fleet through environment variables. For example, the following cloud-config document...
|
||||
|
||||
```yaml
|
||||
#cloud-config
|
||||
|
||||
coreos:
|
||||
fleet:
|
||||
public-ip: "$public_ipv4"
|
||||
metadata: "region=us-west"
|
||||
```
|
||||
|
||||
...will generate a systemd unit drop-in like this:
|
||||
|
||||
```yaml
|
||||
[Service]
|
||||
Environment="FLEET_PUBLIC_IP=203.0.113.29"
|
||||
Environment="FLEET_METADATA=region=us-west"
|
||||
```
|
||||
|
||||
List of fleet configuration parameters:
|
||||
|
||||
- **agent_ttl**: An Agent will be considered dead if it exceeds this amount of time to communicate with the Registry
|
||||
- **engine_reconcile_interval**: Interval in seconds at which the engine should reconcile the cluster schedule in etcd
|
||||
- **etcd_cafile**: Path to CA file used for TLS communication with etcd
|
||||
- **etcd_certfile**: Provide TLS configuration when SSL certificate authentication is enabled in etcd endpoints
|
||||
- **etcd_keyfile**: Path to private key file used for TLS communication with etcd
|
||||
- **etcd_key_prefix**: etcd prefix path to be used for fleet keys
|
||||
- **etcd_request_timeout**: Amount of time in seconds to allow a single etcd request before considering it failed
|
||||
- **etcd_servers**: Comma separated list of etcd endpoints
|
||||
- **etcd_username**: Username for Basic Authentication to etcd endpoints
|
||||
- **etcd_password**: Password for Basic Authentication to etcd endpoints
|
||||
- **metadata**: Comma separated key/value pairs that are published with the local to the fleet registry
|
||||
- **public_ip**: IP accessible by other nodes for inter-host communication
|
||||
- **verbosity**: Enable debug logging by setting this to an integer value greater than zero
|
||||
|
||||
For more information on fleet configuration, see the [fleet documentation][fleet-config].
|
||||
|
||||
[fleet-config]: https://github.com/coreos/fleet/blob/master/Documentation/deployment-and-configuration.md#configuration
|
||||
|
||||
#### flannel
|
||||
|
||||
The `coreos.flannel.*` parameters also work very similarly to `coreos.etcd2.*`
|
||||
and `coreos.fleet.*`. They can be used to set environment variables for
|
||||
flanneld. For example, the following cloud-config...
|
||||
|
||||
```yaml
|
||||
#cloud-config
|
||||
|
||||
coreos:
|
||||
flannel:
|
||||
etcd_prefix: "/coreos.com/network2"
|
||||
```
|
||||
|
||||
...will generate a systemd unit drop-in like so:
|
||||
|
||||
```
|
||||
[Service]
|
||||
Environment="FLANNELD_ETCD_PREFIX=/coreos.com/network2"
|
||||
```
|
||||
|
||||
List of flannel configuration parameters:
|
||||
|
||||
- **etcd_endpoints**: Comma separated list of etcd endpoints
|
||||
- **etcd_cafile**: Path to CA file used for TLS communication with etcd
|
||||
- **etcd_certfile**: Path to certificate file used for TLS communication with etcd
|
||||
- **etcd_keyfile**: Path to private key file used for TLS communication with etcd
|
||||
- **etcd_prefix**: etcd prefix path to be used for flannel keys
|
||||
- **etcd_username**: Username for Basic Authentication to etcd endpoints
|
||||
- **etcd_password**: Password for Basic Authentication to etcd endpoints
|
||||
- **ip_masq**: Install IP masquerade rules for traffic outside of flannel subnet
|
||||
- **subnet_file**: Path to flannel subnet file to write out
|
||||
- **interface**: Interface (name or IP) that should be used for inter-host communication
|
||||
- **public_ip**: IP accessible by other nodes for inter-host communication
|
||||
|
||||
For more information on flannel configuration, see the [flannel documentation][flannel-readme].
|
||||
|
||||
[flannel-readme]: https://github.com/coreos/flannel/blob/master/README.md
|
||||
|
||||
#### locksmith
|
||||
|
||||
The `coreos.locksmith.*` parameters can be used to set environment variables
|
||||
for locksmith. For example, the following cloud-config...
|
||||
|
||||
```yaml
|
||||
#cloud-config
|
||||
|
||||
coreos:
|
||||
locksmith:
|
||||
endpoint: "http://example.com:2379"
|
||||
```
|
||||
|
||||
...will generate a systemd unit drop-in like so:
|
||||
|
||||
```
|
||||
[Service]
|
||||
Environment="LOCKSMITHD_ENDPOINT=http://example.com:2379"
|
||||
```
|
||||
|
||||
List of locksmith configuration parameters:
|
||||
|
||||
- **endpoint**: Comma separated list of etcd endpoints
|
||||
- **etcd_cafile**: Path to CA file used for TLS communication with etcd
|
||||
- **etcd_certfile**: Path to certificate file used for TLS communication with etcd
|
||||
- **etcd_keyfile**: Path to private key file used for TLS communication with etcd
|
||||
- **group**: Name of the reboot group in which this instance belongs
|
||||
- **window_start**: Start time of the reboot window
|
||||
- **window_length**: Duration of the reboot window
|
||||
- **etcd_username**: Username for Basic Authentication to etcd endpoints
|
||||
- **etcd_password**: Password for Basic Authentication to etcd endpoints
|
||||
|
||||
For the complete list of locksmith configuration parameters, see the [locksmith documentation][locksmith-readme].
|
||||
|
||||
[locksmith-readme]: https://github.com/coreos/locksmith/blob/master/README.md
|
||||
|
||||
#### update
|
||||
|
||||
The `coreos.update.*` parameters manipulate settings related to how CoreOS instances are updated.
|
||||
|
||||
These fields will be written out to and replace `/etc/coreos/update.conf`. If only one of the parameters is given it will only overwrite the given field.
|
||||
The `reboot-strategy` parameter also affects the behaviour of [locksmith](https://github.com/coreos/locksmith).
|
||||
|
||||
- **reboot-strategy**: One of "reboot", "etcd-lock", "best-effort" or "off" for controlling when reboots are issued after an update is performed.
|
||||
- _reboot_: Reboot immediately after an update is applied.
|
||||
- _etcd-lock_: Reboot after first taking a distributed lock in etcd, this guarantees that only one host will reboot concurrently and that the cluster will remain available during the update.
|
||||
- _best-effort_ - If etcd is running, "etcd-lock", otherwise simply "reboot".
|
||||
- _off_ - Disable rebooting after updates are applied (not recommended).
|
||||
- **server**: The location of the [CoreUpdate][coreupdate] server which will be queried for updates. Also known as the [omaha][omaha-docs] server endpoint.
|
||||
- **group**: signifies the channel which should be used for automatic updates. This value defaults to the version of the image initially downloaded. (one of "master", "alpha", "beta", "stable")
|
||||
|
||||
[coreupdate]: https://coreos.com/products/coreupdate
|
||||
[omaha-docs]: https://coreos.com/docs/coreupdate/custom-apps/coreupdate-protocol/
|
||||
|
||||
*Note: cloudinit will only manipulate the locksmith unit file in the systemd runtime directory (`/run/systemd/system/locksmithd.service`). If any manual modifications are made to an overriding unit configuration file (e.g. `/etc/systemd/system/locksmithd.service`), cloudinit will no longer be able to control the locksmith service unit.*
|
||||
|
||||
##### Example
|
||||
|
||||
```yaml
|
||||
#cloud-config
|
||||
coreos:
|
||||
update:
|
||||
reboot-strategy: "etcd-lock"
|
||||
```
|
||||
|
||||
#### units
|
||||
|
||||
The `coreos.units.*` parameters define a list of arbitrary systemd units to start after booting. This feature is intended to help you start essential services required to mount storage and configure networking in order to join the CoreOS cluster. It is not intended to be a Chef/Puppet replacement.
|
||||
|
||||
Each item is an object with the following fields:
|
||||
|
||||
- **name**: String representing unit's name. Required.
|
||||
- **runtime**: Boolean indicating whether or not to persist the unit across reboots. This is analogous to the `--runtime` argument to `systemctl enable`. The default value is false.
|
||||
- **enable**: Boolean indicating whether or not to handle the [Install] section of the unit file. This is similar to running `systemctl enable <name>`. The default value is false.
|
||||
- **content**: Plaintext string representing entire unit file. If no value is provided, the unit is assumed to exist already.
|
||||
- **command**: Command to execute on unit: start, stop, reload, restart, try-restart, reload-or-restart, reload-or-try-restart. The default behavior is to not execute any commands.
|
||||
- **mask**: Whether to mask the unit file by symlinking it to `/dev/null` (analogous to `systemctl mask <name>`). Note that unlike `systemctl mask`, **this will destructively remove any existing unit file** located at `/etc/systemd/system/<unit>`, to ensure that the mask succeeds. The default value is false.
|
||||
- **drop-ins**: A list of unit drop-ins with the following fields:
|
||||
- **name**: String representing unit's name. Required.
|
||||
- **content**: Plaintext string representing entire file. Required.
|
||||
|
||||
|
||||
**NOTE:** The command field is ignored for all network, netdev, and link units. The systemd-networkd.service unit will be restarted in their place.
|
||||
|
||||
##### Examples
|
||||
|
||||
Write a unit to disk, automatically starting it.
|
||||
|
||||
```yaml
|
||||
#cloud-config
|
||||
|
||||
coreos:
|
||||
units:
|
||||
- name: "docker-redis.service"
|
||||
command: "start"
|
||||
content: |
|
||||
[Unit]
|
||||
Description=Redis container
|
||||
Author=Me
|
||||
After=docker.service
|
||||
|
||||
[Service]
|
||||
Restart=always
|
||||
ExecStart=/usr/bin/docker start -a redis_server
|
||||
ExecStop=/usr/bin/docker stop -t 2 redis_server
|
||||
```
|
||||
|
||||
Add the DOCKER_OPTS environment variable to docker.service.
|
||||
|
||||
```yaml
|
||||
#cloud-config
|
||||
|
||||
coreos:
|
||||
units:
|
||||
- name: "docker.service"
|
||||
drop-ins:
|
||||
- name: "50-insecure-registry.conf"
|
||||
content: |
|
||||
[Service]
|
||||
Environment=DOCKER_OPTS='--insecure-registry="10.0.1.0/24"'
|
||||
```
|
||||
|
||||
Start the built-in `etcd2` and `fleet` services:
|
||||
|
||||
```yaml
|
||||
#cloud-config
|
||||
|
||||
coreos:
|
||||
units:
|
||||
- name: "etcd2.service"
|
||||
command: "start"
|
||||
- name: "fleet.service"
|
||||
command: "start"
|
||||
```
|
||||
|
||||
### ssh_authorized_keys
|
||||
|
||||
The `ssh_authorized_keys` parameter adds public SSH keys which will be authorized for the `core` user.
|
||||
|
||||
The keys will be named "coreos-cloudinit" by default.
|
||||
Override this by using the `--ssh-key-name` flag when calling `coreos-cloudinit`.
|
||||
|
||||
```yaml
|
||||
#cloud-config
|
||||
|
||||
ssh_authorized_keys:
|
||||
- "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC0g+ZTxC7weoIJLUafOgrm+h..."
|
||||
```
|
||||
|
||||
### hostname
|
||||
|
||||
The `hostname` parameter defines the system's hostname.
|
||||
This is the local part of a fully-qualified domain name (i.e. `foo` in `foo.example.com`).
|
||||
|
||||
```yaml
|
||||
#cloud-config
|
||||
|
||||
hostname: "coreos1"
|
||||
```
|
||||
|
||||
### users
|
||||
|
||||
The `users` parameter adds or modifies the specified list of users. Each user is an object which consists of the following fields. Each field is optional and of type string unless otherwise noted.
|
||||
All but the `passwd` and `ssh-authorized-keys` fields will be ignored if the user already exists.
|
||||
|
||||
- **name**: Required. Login name of user
|
||||
- **gecos**: GECOS comment of user
|
||||
- **passwd**: Hash of the password to use for this user
|
||||
- **homedir**: User's home directory. Defaults to /home/\<name\>
|
||||
- **no-create-home**: Boolean. Skip home directory creation.
|
||||
- **primary-group**: Default group for the user. Defaults to a new group created named after the user.
|
||||
- **groups**: Add user to these additional groups
|
||||
- **no-user-group**: Boolean. Skip default group creation.
|
||||
- **ssh-authorized-keys**: List of public SSH keys to authorize for this user
|
||||
- **coreos-ssh-import-github** [DEPRECATED]: Authorize SSH keys from GitHub user
|
||||
- **coreos-ssh-import-github-users** [DEPRECATED]: Authorize SSH keys from a list of GitHub users
|
||||
- **coreos-ssh-import-url** [DEPRECATED]: Authorize SSH keys imported from a url endpoint.
|
||||
- **system**: Create the user as a system user. No home directory will be created.
|
||||
- **no-log-init**: Boolean. Skip initialization of lastlog and faillog databases.
|
||||
- **shell**: User's login shell.
|
||||
|
||||
The following fields are not yet implemented:
|
||||
|
||||
- **inactive**: Deactivate the user upon creation
|
||||
- **lock-passwd**: Boolean. Disable password login for user
|
||||
- **sudo**: Entry to add to /etc/sudoers for user. By default, no sudo access is authorized.
|
||||
- **selinux-user**: Corresponding SELinux user
|
||||
- **ssh-import-id**: Import SSH keys by ID from Launchpad.
|
||||
|
||||
```yaml
|
||||
#cloud-config
|
||||
|
||||
users:
|
||||
- name: "elroy"
|
||||
passwd: "$6$5s2u6/jR$un0AvWnqilcgaNB3Mkxd5yYv6mTlWfOoCYHZmfi3LDKVltj.E8XNKEcwWm..."
|
||||
groups:
|
||||
- "sudo"
|
||||
- "docker"
|
||||
ssh-authorized-keys:
|
||||
- "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC0g+ZTxC7weoIJLUafOgrm+h..."
|
||||
```
|
||||
|
||||
#### Generating a password hash
|
||||
|
||||
If you choose to use a password instead of an SSH key, generating a safe hash is extremely important to the security of your system. Simplified hashes like md5crypt are trivial to crack on modern GPU hardware. Here are a few ways to generate secure hashes:
|
||||
|
||||
```
|
||||
# On Debian/Ubuntu (via the package "whois")
|
||||
mkpasswd --method=SHA-512 --rounds=4096
|
||||
|
||||
# OpenSSL (note: this will only make md5crypt. While better than plantext it should not be considered fully secure)
|
||||
openssl passwd -1
|
||||
|
||||
# Python (change password and salt values)
|
||||
python -c "import crypt, getpass, pwd; print crypt.crypt('password', '\$6\$SALT\$')"
|
||||
|
||||
# Perl (change password and salt values)
|
||||
perl -e 'print crypt("password","\$6\$SALT\$") . "\n"'
|
||||
```
|
||||
|
||||
Using a higher number of rounds will help create more secure passwords, but given enough time, password hashes can be reversed. On most RPM based distributions there is a tool called mkpasswd available in the `expect` package, but this does not handle "rounds" nor advanced hashing algorithms.
|
||||
|
||||
### write_files
|
||||
|
||||
The `write_files` directive defines a set of files to create on the local filesystem.
|
||||
Each item in the list may have the following keys:
|
||||
|
||||
- **path**: Absolute location on disk where contents should be written
|
||||
- **content**: Data to write at the provided `path`
|
||||
- **permissions**: Integer representing file permissions, typically in octal notation (i.e. 0644)
|
||||
- **owner**: User and group that should own the file written to disk. This is equivalent to the `<user>:<group>` argument to `chown <user>:<group> <path>`.
|
||||
- **encoding**: Optional. The encoding of the data in content. If not specified this defaults to the yaml document encoding (usually utf-8). Supported encoding types are:
|
||||
- **b64, base64**: Base64 encoded content
|
||||
- **gz, gzip**: gzip encoded content, for use with the !!binary tag
|
||||
- **gz+b64, gz+base64, gzip+b64, gzip+base64**: Base64 encoded gzip content
|
||||
|
||||
|
||||
```yaml
|
||||
#cloud-config
|
||||
write_files:
|
||||
- path: "/etc/resolv.conf"
|
||||
permissions: "0644"
|
||||
owner: "root"
|
||||
content: |
|
||||
nameserver 8.8.8.8
|
||||
- path: "/etc/motd"
|
||||
permissions: "0644"
|
||||
owner: "root"
|
||||
content: |
|
||||
Good news, everyone!
|
||||
- path: "/tmp/like_this"
|
||||
permissions: "0644"
|
||||
owner: "root"
|
||||
encoding: "gzip"
|
||||
content: !!binary |
|
||||
H4sIAKgdh1QAAwtITM5WyK1USMqvUCjPLMlQSMssS1VIya9KzVPIySwszS9SyCpNLwYARQFQ5CcAAAA=
|
||||
- path: "/tmp/or_like_this"
|
||||
permissions: "0644"
|
||||
owner: "root"
|
||||
encoding: "gzip+base64"
|
||||
content: |
|
||||
H4sIAKgdh1QAAwtITM5WyK1USMqvUCjPLMlQSMssS1VIya9KzVPIySwszS9SyCpNLwYARQFQ5CcAAAA=
|
||||
- path: "/tmp/todolist"
|
||||
permissions: "0644"
|
||||
owner: "root"
|
||||
encoding: "base64"
|
||||
content: |
|
||||
UGFjayBteSBib3ggd2l0aCBmaXZlIGRvemVuIGxpcXVvciBqdWdz
|
||||
```
|
||||
|
||||
### manage_etc_hosts
|
||||
|
||||
The `manage_etc_hosts` parameter configures the contents of the `/etc/hosts` file, which is used for local name resolution.
|
||||
Currently, the only supported value is "localhost" which will cause your system's hostname
|
||||
to resolve to "127.0.0.1". This is helpful when the host does not have DNS
|
||||
infrastructure in place to resolve its own hostname, for example, when using Vagrant.
|
||||
|
||||
```yaml
|
||||
#cloud-config
|
||||
|
||||
manage_etc_hosts: "localhost"
|
||||
```
|
||||
40
config/cloudinit/Documentation/config-drive.md
Normal file
40
config/cloudinit/Documentation/config-drive.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Distribution via Config Drive
|
||||
|
||||
CoreOS supports providing configuration data via [config drive][config-drive]
|
||||
disk images. Currently only providing a single script or cloud config file is
|
||||
supported.
|
||||
|
||||
[config-drive]: http://docs.openstack.org/user-guide/cli_config_drive.html
|
||||
|
||||
## Contents and Format
|
||||
|
||||
The image should be a single FAT or ISO9660 file system with the label
|
||||
`config-2` and the configuration data should be located at
|
||||
`openstack/latest/user_data`.
|
||||
|
||||
For example, to wrap up a config named `user_data` in a config drive image:
|
||||
|
||||
```sh
|
||||
mkdir -p /tmp/new-drive/openstack/latest
|
||||
cp user_data /tmp/new-drive/openstack/latest/user_data
|
||||
mkisofs -R -V config-2 -o configdrive.iso /tmp/new-drive
|
||||
rm -r /tmp/new-drive
|
||||
```
|
||||
|
||||
If on OS X, replace the `mkisofs` invocation with:
|
||||
|
||||
```sh
|
||||
hdiutil makehybrid -iso -joliet -default-volume-name config-2 -o configdrive.iso /tmp/new-drive
|
||||
```
|
||||
|
||||
## QEMU virtfs
|
||||
|
||||
One exception to the above, when using QEMU it is possible to skip creating an
|
||||
image and use a plain directory containing the same contents:
|
||||
|
||||
```sh
|
||||
qemu-system-x86_64 \
|
||||
-fsdev local,id=conf,security_model=none,readonly,path=/tmp/new-drive \
|
||||
-device virtio-9p-pci,fsdev=conf,mount_tag=config-2 \
|
||||
[usual qemu options here...]
|
||||
```
|
||||
27
config/cloudinit/Documentation/debian-interfaces.md
Normal file
27
config/cloudinit/Documentation/debian-interfaces.md
Normal file
@@ -0,0 +1,27 @@
|
||||
#Debian Interfaces#
|
||||
**WARNING**: This option is EXPERIMENTAL and may change or be removed at any
|
||||
point.
|
||||
There is basic support for converting from a Debian network configuration to
|
||||
networkd unit files. The -convert-netconf=debian option is used to activate
|
||||
this feature.
|
||||
|
||||
#convert-netconf#
|
||||
Default: ""
|
||||
Read the network config provided in cloud-drive and translate it from the
|
||||
specified format into networkd unit files (requires the -from-configdrive
|
||||
flag). Currently only supports "debian" which provides support for a small
|
||||
subset of the [Debian network configuration]
|
||||
(https://wiki.debian.org/NetworkConfiguration). These options include:
|
||||
|
||||
- interface config methods
|
||||
- static
|
||||
- address/netmask
|
||||
- gateway
|
||||
- hwaddress
|
||||
- dns-nameservers
|
||||
- dhcp
|
||||
- hwaddress
|
||||
- manual
|
||||
- loopback
|
||||
- vlan_raw_device
|
||||
- bond-slaves
|
||||
36
config/cloudinit/Documentation/vmware-guestinfo.md
Normal file
36
config/cloudinit/Documentation/vmware-guestinfo.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# VMWare Guestinfo Interface
|
||||
|
||||
## Cloud-Config VMWare Guestinfo Variables
|
||||
|
||||
coreos-cloudinit accepts configuration from the VMware RPC API's *guestinfo*
|
||||
facility. This datasource can be enabled with the `--from-vmware-guestinfo`
|
||||
flag to coreos-cloudinit.
|
||||
|
||||
The following guestinfo variables are recognized and processed by cloudinit
|
||||
when passed from the hypervisor to the virtual machine at boot time. Note that
|
||||
property names are prefixed with `guestinfo.` in the VMX, e.g., `guestinfo.hostname`.
|
||||
|
||||
| guestinfo variable | type |
|
||||
|:--------------------------------------|:--------------------------------|
|
||||
| `hostname` | `hostname` |
|
||||
| `interface.<n>.name` | `string` |
|
||||
| `interface.<n>.mac` | `MAC address` |
|
||||
| `interface.<n>.dhcp` | `{"yes", "no"}` |
|
||||
| `interface.<n>.role` | `{"public", "private"}` |
|
||||
| `interface.<n>.ip.<m>.address` | `CIDR IP address` |
|
||||
| `interface.<n>.route.<l>.gateway` | `IP address` |
|
||||
| `interface.<n>.route.<l>.destination` | `CIDR IP address` |
|
||||
| `dns.server.<x>` | `IP address` |
|
||||
| `dns.domain.<y>` | `DNS search domain` |
|
||||
| `coreos.config.data` | `string` |
|
||||
| `coreos.config.data.encoding` | `{"", "base64", "gzip+base64"}` |
|
||||
| `coreos.config.url` | `URL` |
|
||||
|
||||
Note: "n", "m", "l", "x" and "y" are 0-indexed, incrementing integers. The
|
||||
identifier for an `interface` does not correspond to anything outside of this
|
||||
configuration; it serves only to distinguish between multiple `interface`s.
|
||||
|
||||
The guide to [booting on VMWare][bootvmware] is the starting point for more
|
||||
information about configuring and running CoreOS on VMWare.
|
||||
|
||||
[bootvmware]: https://github.com/coreos/docs/blob/master/os/booting-on-vmware.md
|
||||
2
config/cloudinit/MAINTAINERS
Normal file
2
config/cloudinit/MAINTAINERS
Normal file
@@ -0,0 +1,2 @@
|
||||
Alex Crawford <alex.crawford@coreos.com> (@crawford)
|
||||
Jonathan Boulle <jonathan.boulle@coreos.com> (@jonboulle)
|
||||
90
config/cloudinit/README.md
Normal file
90
config/cloudinit/README.md
Normal file
@@ -0,0 +1,90 @@
|
||||
**NOTE**: This project has been superseded by [Ignition][ignition] and is no longer under active development. Please direct all development efforts to Ignition.
|
||||
|
||||
[ignition]: https://github.com/coreos/ignition
|
||||
|
||||
# coreos-cloudinit [](https://travis-ci.org/coreos/coreos-cloudinit)
|
||||
|
||||
coreos-cloudinit enables a user to customize CoreOS machines by providing either a cloud-config document or an executable script through user-data.
|
||||
|
||||
## Configuration with cloud-config
|
||||
|
||||
A subset of the [official cloud-config spec][official-cloud-config] is implemented by coreos-cloudinit.
|
||||
Additionally, several [CoreOS-specific options][custom-cloud-config] have been implemented to support interacting with unit files, bootstrapping etcd clusters, and more.
|
||||
All supported cloud-config parameters are [documented here][all-cloud-config].
|
||||
|
||||
[official-cloud-config]: http://cloudinit.readthedocs.org/en/latest/topics/format.html#cloud-config-data
|
||||
[custom-cloud-config]: https://github.com/rancher/os/config/cloudinit/blob/master/Documentation/cloud-config.md#coreos-parameters
|
||||
[all-cloud-config]: https://github.com/rancher/os/config/cloudinit/tree/master/Documentation/cloud-config.md
|
||||
|
||||
The following is an example cloud-config document:
|
||||
|
||||
```
|
||||
#cloud-config
|
||||
|
||||
coreos:
|
||||
units:
|
||||
- name: etcd.service
|
||||
command: start
|
||||
|
||||
users:
|
||||
- name: core
|
||||
passwd: $1$allJZawX$00S5T756I5PGdQga5qhqv1
|
||||
|
||||
write_files:
|
||||
- path: /etc/resolv.conf
|
||||
content: |
|
||||
nameserver 192.0.2.2
|
||||
nameserver 192.0.2.3
|
||||
```
|
||||
|
||||
## Executing a Script
|
||||
|
||||
coreos-cloudinit supports executing user-data as a script instead of parsing it as a cloud-config document.
|
||||
Make sure the first line of your user-data is a shebang and coreos-cloudinit will attempt to execute it:
|
||||
|
||||
```
|
||||
#!/bin/bash
|
||||
|
||||
echo 'Hello, world!'
|
||||
```
|
||||
|
||||
## user-data Field Substitution
|
||||
|
||||
coreos-cloudinit will replace the following set of tokens in your user-data with system-generated values.
|
||||
|
||||
| Token | Description |
|
||||
| ------------- | ----------- |
|
||||
| $public_ipv4 | Public IPv4 address of machine |
|
||||
| $private_ipv4 | Private IPv4 address of machine |
|
||||
|
||||
These values are determined by CoreOS based on the given provider on which your machine is running.
|
||||
Read more about provider-specific functionality in the [CoreOS OEM documentation][oem-doc].
|
||||
|
||||
[oem-doc]: https://coreos.com/docs/sdk-distributors/distributors/notes-for-distributors/
|
||||
|
||||
For example, submitting the following user-data...
|
||||
|
||||
```
|
||||
#cloud-config
|
||||
coreos:
|
||||
etcd:
|
||||
addr: $public_ipv4:4001
|
||||
peer-addr: $private_ipv4:7001
|
||||
```
|
||||
|
||||
...will result in this cloud-config document being executed:
|
||||
|
||||
```
|
||||
#cloud-config
|
||||
coreos:
|
||||
etcd:
|
||||
addr: 203.0.113.29:4001
|
||||
peer-addr: 192.0.2.13:7001
|
||||
```
|
||||
|
||||
## Bugs
|
||||
|
||||
Please use the [CoreOS issue tracker][bugs] to report all bugs, issues, and feature requests.
|
||||
|
||||
[bugs]: https://github.com/coreos/bugs/issues/new?labels=component/cloud-init
|
||||
|
||||
17
config/cloudinit/build
Executable file
17
config/cloudinit/build
Executable file
@@ -0,0 +1,17 @@
|
||||
#!/bin/bash -e
|
||||
|
||||
NAME="coreos-cloudinit"
|
||||
ORG_PATH="github.com/coreos"
|
||||
REPO_PATH="${ORG_PATH}/${NAME}"
|
||||
VERSION=$(git describe --dirty --tags)
|
||||
GLDFLAGS="-X main.version=\"${VERSION}\""
|
||||
|
||||
if [ ! -h gopath/src/${REPO_PATH} ]; then
|
||||
mkdir -p gopath/src/${ORG_PATH}
|
||||
ln -s ../../../.. gopath/src/${REPO_PATH} || exit 255
|
||||
fi
|
||||
|
||||
export GOBIN=${PWD}/bin
|
||||
export GOPATH=${PWD}/gopath
|
||||
|
||||
go build -ldflags "${GLDFLAGS}" -o ${GOBIN}/${NAME} ${REPO_PATH}
|
||||
170
config/cloudinit/config/config.go
Normal file
170
config/cloudinit/config/config.go
Normal file
@@ -0,0 +1,170 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/coreos/yaml"
|
||||
)
|
||||
|
||||
// CloudConfig encapsulates the entire cloud-config configuration file and maps
|
||||
// directly to YAML. Fields that cannot be set in the cloud-config (fields
|
||||
// used for internal use) have the YAML tag '-' so that they aren't marshalled.
|
||||
type CloudConfig struct {
|
||||
SSHAuthorizedKeys []string `yaml:"ssh_authorized_keys"`
|
||||
CoreOS CoreOS `yaml:"coreos"`
|
||||
WriteFiles []File `yaml:"write_files"`
|
||||
Hostname string `yaml:"hostname"`
|
||||
Users []User `yaml:"users"`
|
||||
ManageEtcHosts EtcHosts `yaml:"manage_etc_hosts"`
|
||||
}
|
||||
|
||||
type CoreOS struct {
|
||||
Etcd Etcd `yaml:"etcd"`
|
||||
Etcd2 Etcd2 `yaml:"etcd2"`
|
||||
Flannel Flannel `yaml:"flannel"`
|
||||
Fleet Fleet `yaml:"fleet"`
|
||||
Locksmith Locksmith `yaml:"locksmith"`
|
||||
OEM OEM `yaml:"oem"`
|
||||
Update Update `yaml:"update"`
|
||||
Units []Unit `yaml:"units"`
|
||||
}
|
||||
|
||||
func IsCloudConfig(userdata string) bool {
|
||||
header := strings.SplitN(userdata, "\n", 2)[0]
|
||||
|
||||
// Trim trailing whitespaces
|
||||
header = strings.TrimRightFunc(header, unicode.IsSpace)
|
||||
|
||||
return (header == "#cloud-config")
|
||||
}
|
||||
|
||||
// NewCloudConfig instantiates a new CloudConfig from the given contents (a
|
||||
// string of YAML), returning any error encountered. It will ignore unknown
|
||||
// fields but log encountering them.
|
||||
func NewCloudConfig(contents string) (*CloudConfig, error) {
|
||||
yaml.UnmarshalMappingKeyTransform = func(nameIn string) (nameOut string) {
|
||||
return strings.Replace(nameIn, "-", "_", -1)
|
||||
}
|
||||
var cfg CloudConfig
|
||||
err := yaml.Unmarshal([]byte(contents), &cfg)
|
||||
return &cfg, err
|
||||
}
|
||||
|
||||
// Decode decodes the content of cloud config. Currently only WriteFiles section
|
||||
// supports several types of encoding and all of them are supported. After
|
||||
// decode operation, Encoding type is unset.
|
||||
func (cc *CloudConfig) Decode() error {
|
||||
for i, file := range cc.WriteFiles {
|
||||
content, err := DecodeContent(file.Content, file.Encoding)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cc.WriteFiles[i].Content = string(content)
|
||||
cc.WriteFiles[i].Encoding = ""
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
func (cc CloudConfig) String() string {
|
||||
bytes, err := yaml.Marshal(cc)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
stringified := string(bytes)
|
||||
stringified = fmt.Sprintf("#cloud-config\n%s", stringified)
|
||||
|
||||
return stringified
|
||||
}
|
||||
|
||||
// IsZero returns whether or not the parameter is the zero value for its type.
|
||||
// If the parameter is a struct, only the exported fields are considered.
|
||||
func IsZero(c interface{}) bool {
|
||||
return isZero(reflect.ValueOf(c))
|
||||
}
|
||||
|
||||
type ErrorValid struct {
|
||||
Value string
|
||||
Valid string
|
||||
Field string
|
||||
}
|
||||
|
||||
func (e ErrorValid) Error() string {
|
||||
return fmt.Sprintf("invalid value %q for option %q (valid options: %q)", e.Value, e.Field, e.Valid)
|
||||
}
|
||||
|
||||
// AssertStructValid checks the fields in the structure and makes sure that
|
||||
// they contain valid values as specified by the 'valid' flag. Empty fields are
|
||||
// implicitly valid.
|
||||
func AssertStructValid(c interface{}) error {
|
||||
ct := reflect.TypeOf(c)
|
||||
cv := reflect.ValueOf(c)
|
||||
for i := 0; i < ct.NumField(); i++ {
|
||||
ft := ct.Field(i)
|
||||
if !isFieldExported(ft) {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := AssertValid(cv.Field(i), ft.Tag.Get("valid")); err != nil {
|
||||
err.Field = ft.Name
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AssertValid checks to make sure that the given value is in the list of
|
||||
// valid values. Zero values are implicitly valid.
|
||||
func AssertValid(value reflect.Value, valid string) *ErrorValid {
|
||||
if valid == "" || isZero(value) {
|
||||
return nil
|
||||
}
|
||||
|
||||
vs := fmt.Sprintf("%v", value.Interface())
|
||||
if m, _ := regexp.MatchString(valid, vs); m {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &ErrorValid{
|
||||
Value: vs,
|
||||
Valid: valid,
|
||||
}
|
||||
}
|
||||
|
||||
func isZero(v reflect.Value) bool {
|
||||
switch v.Kind() {
|
||||
case reflect.Struct:
|
||||
vt := v.Type()
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
if isFieldExported(vt.Field(i)) && !isZero(v.Field(i)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
default:
|
||||
return v.Interface() == reflect.Zero(v.Type()).Interface()
|
||||
}
|
||||
}
|
||||
|
||||
func isFieldExported(f reflect.StructField) bool {
|
||||
return f.PkgPath == ""
|
||||
}
|
||||
547
config/cloudinit/config/config_test.go
Normal file
547
config/cloudinit/config/config_test.go
Normal file
@@ -0,0 +1,547 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewCloudConfig(t *testing.T) {
|
||||
tests := []struct {
|
||||
contents string
|
||||
|
||||
config CloudConfig
|
||||
}{
|
||||
{},
|
||||
{
|
||||
contents: "#cloud-config\nwrite_files:\n - path: underscore",
|
||||
config: CloudConfig{WriteFiles: []File{{Path: "underscore"}}},
|
||||
},
|
||||
{
|
||||
contents: "#cloud-config\nwrite-files:\n - path: hyphen",
|
||||
config: CloudConfig{WriteFiles: []File{{Path: "hyphen"}}},
|
||||
},
|
||||
{
|
||||
contents: "#cloud-config\ncoreos:\n update:\n reboot-strategy: off",
|
||||
config: CloudConfig{CoreOS: CoreOS{Update: Update{RebootStrategy: "off"}}},
|
||||
},
|
||||
{
|
||||
contents: "#cloud-config\ncoreos:\n update:\n reboot-strategy: false",
|
||||
config: CloudConfig{CoreOS: CoreOS{Update: Update{RebootStrategy: "false"}}},
|
||||
},
|
||||
{
|
||||
contents: "#cloud-config\nwrite_files:\n - permissions: 0744",
|
||||
config: CloudConfig{WriteFiles: []File{{RawFilePermissions: "0744"}}},
|
||||
},
|
||||
{
|
||||
contents: "#cloud-config\nwrite_files:\n - permissions: 744",
|
||||
config: CloudConfig{WriteFiles: []File{{RawFilePermissions: "744"}}},
|
||||
},
|
||||
{
|
||||
contents: "#cloud-config\nwrite_files:\n - permissions: '0744'",
|
||||
config: CloudConfig{WriteFiles: []File{{RawFilePermissions: "0744"}}},
|
||||
},
|
||||
{
|
||||
contents: "#cloud-config\nwrite_files:\n - permissions: '744'",
|
||||
config: CloudConfig{WriteFiles: []File{{RawFilePermissions: "744"}}},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
config, err := NewCloudConfig(tt.contents)
|
||||
if err != nil {
|
||||
t.Errorf("bad error (test case #%d): want %v, got %s", i, nil, err)
|
||||
}
|
||||
if !reflect.DeepEqual(&tt.config, config) {
|
||||
t.Errorf("bad config (test case #%d): want %#v, got %#v", i, tt.config, config)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewCloudConfigDecode(t *testing.T) {
|
||||
// //all of these decode to "bar"
|
||||
contentTests := map[string]string{
|
||||
"base64": "YmFy",
|
||||
"b64": "YmFy",
|
||||
// theoretically gz+gzip are supported but they break yaml
|
||||
// "gz": "\x1f\x8b\x08\x08w\x14\x87T\x02\xffok\x00KJ,\x02\x00\xaa\x8c\xffv\x03\x00\x00\x00",
|
||||
// "gzip": "\x1f\x8b\x08\x08w\x14\x87T\x02\xffok\x00KJ,\x02\x00\xaa\x8c\xffv\x03\x00\x00\x00",
|
||||
"gz+base64": "H4sIABMVh1QAA0tKLAIAqoz/dgMAAAA=",
|
||||
"gzip+base64": "H4sIABMVh1QAA0tKLAIAqoz/dgMAAAA=",
|
||||
"gz+b64": "H4sIABMVh1QAA0tKLAIAqoz/dgMAAAA=",
|
||||
"gzip+b64": "H4sIABMVh1QAA0tKLAIAqoz/dgMAAAA=",
|
||||
}
|
||||
|
||||
type testCase struct {
|
||||
contents string
|
||||
config CloudConfig
|
||||
}
|
||||
|
||||
var decodingTests []testCase
|
||||
for name, content := range contentTests {
|
||||
decodingTests = append(decodingTests, testCase{
|
||||
contents: fmt.Sprintf("#cloud-config\nwrite_files:\n - encoding: %q\n content: |\n %s", name, content),
|
||||
config: CloudConfig{WriteFiles: []File{{Content: "bar"}}},
|
||||
})
|
||||
}
|
||||
|
||||
for i, tt := range decodingTests {
|
||||
config, err := NewCloudConfig(tt.contents)
|
||||
if err != nil {
|
||||
t.Errorf("bad error (test case #%d): want %v, got %s", i, nil, err)
|
||||
}
|
||||
|
||||
if err := config.Decode(); err != nil {
|
||||
t.Errorf("bad error (test case #%d): want %v, got %s", i, nil, err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(&tt.config, config) {
|
||||
t.Errorf("bad config (test case #%d): want %#v, got %#v", i, tt.config, config)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestIsZero(t *testing.T) {
|
||||
tests := []struct {
|
||||
c interface{}
|
||||
|
||||
empty bool
|
||||
}{
|
||||
{struct{}{}, true},
|
||||
{struct{ a, b string }{}, true},
|
||||
{struct{ A, b string }{}, true},
|
||||
{struct{ A, B string }{}, true},
|
||||
{struct{ A string }{A: "hello"}, false},
|
||||
{struct{ A int }{}, true},
|
||||
{struct{ A int }{A: 1}, false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
if empty := IsZero(tt.c); tt.empty != empty {
|
||||
t.Errorf("bad result (%q): want %t, got %t", tt.c, tt.empty, empty)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssertStructValid(t *testing.T) {
|
||||
tests := []struct {
|
||||
c interface{}
|
||||
|
||||
err error
|
||||
}{
|
||||
{struct{}{}, nil},
|
||||
{struct {
|
||||
A, b string `valid:"^1|2$"`
|
||||
}{}, nil},
|
||||
{struct {
|
||||
A, b string `valid:"^1|2$"`
|
||||
}{A: "1", b: "2"}, nil},
|
||||
{struct {
|
||||
A, b string `valid:"^1|2$"`
|
||||
}{A: "1", b: "hello"}, nil},
|
||||
{struct {
|
||||
A, b string `valid:"^1|2$"`
|
||||
}{A: "hello", b: "2"}, &ErrorValid{Value: "hello", Field: "A", Valid: "^1|2$"}},
|
||||
{struct {
|
||||
A, b int `valid:"^1|2$"`
|
||||
}{}, nil},
|
||||
{struct {
|
||||
A, b int `valid:"^1|2$"`
|
||||
}{A: 1, b: 2}, nil},
|
||||
{struct {
|
||||
A, b int `valid:"^1|2$"`
|
||||
}{A: 1, b: 9}, nil},
|
||||
{struct {
|
||||
A, b int `valid:"^1|2$"`
|
||||
}{A: 9, b: 2}, &ErrorValid{Value: "9", Field: "A", Valid: "^1|2$"}},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
if err := AssertStructValid(tt.c); !reflect.DeepEqual(tt.err, err) {
|
||||
t.Errorf("bad result (%q): want %q, got %q", tt.c, tt.err, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigCompile(t *testing.T) {
|
||||
tests := []interface{}{
|
||||
Etcd{},
|
||||
File{},
|
||||
Flannel{},
|
||||
Fleet{},
|
||||
Locksmith{},
|
||||
OEM{},
|
||||
Unit{},
|
||||
Update{},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
ttt := reflect.TypeOf(tt)
|
||||
for i := 0; i < ttt.NumField(); i++ {
|
||||
ft := ttt.Field(i)
|
||||
if !isFieldExported(ft) {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, err := regexp.Compile(ft.Tag.Get("valid")); err != nil {
|
||||
t.Errorf("bad regexp(%s.%s): want %v, got %s", ttt.Name(), ft.Name, nil, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCloudConfigUnknownKeys(t *testing.T) {
|
||||
contents := `
|
||||
coreos:
|
||||
etcd:
|
||||
discovery: "https://discovery.etcd.io/827c73219eeb2fa5530027c37bf18877"
|
||||
coreos_unknown:
|
||||
foo: "bar"
|
||||
section_unknown:
|
||||
dunno:
|
||||
something
|
||||
bare_unknown:
|
||||
bar
|
||||
write_files:
|
||||
- content: fun
|
||||
path: /var/party
|
||||
file_unknown: nofun
|
||||
users:
|
||||
- name: fry
|
||||
passwd: somehash
|
||||
user_unknown: philip
|
||||
hostname:
|
||||
foo
|
||||
`
|
||||
cfg, err := NewCloudConfig(contents)
|
||||
if err != nil {
|
||||
t.Fatalf("error instantiating CloudConfig with unknown keys: %v", err)
|
||||
}
|
||||
if cfg.Hostname != "foo" {
|
||||
t.Fatalf("hostname not correctly set when invalid keys are present")
|
||||
}
|
||||
if cfg.CoreOS.Etcd.Discovery != "https://discovery.etcd.io/827c73219eeb2fa5530027c37bf18877" {
|
||||
t.Fatalf("etcd section not correctly set when invalid keys are present")
|
||||
}
|
||||
if len(cfg.WriteFiles) < 1 || cfg.WriteFiles[0].Content != "fun" || cfg.WriteFiles[0].Path != "/var/party" {
|
||||
t.Fatalf("write_files section not correctly set when invalid keys are present")
|
||||
}
|
||||
if len(cfg.Users) < 1 || cfg.Users[0].Name != "fry" || cfg.Users[0].PasswordHash != "somehash" {
|
||||
t.Fatalf("users section not correctly set when invalid keys are present")
|
||||
}
|
||||
}
|
||||
|
||||
// Assert that the parsing of a cloud config file "generally works"
|
||||
func TestCloudConfigEmpty(t *testing.T) {
|
||||
cfg, err := NewCloudConfig("")
|
||||
if err != nil {
|
||||
t.Fatalf("Encountered unexpected error :%v", err)
|
||||
}
|
||||
|
||||
keys := cfg.SSHAuthorizedKeys
|
||||
if len(keys) != 0 {
|
||||
t.Error("Parsed incorrect number of SSH keys")
|
||||
}
|
||||
|
||||
if len(cfg.WriteFiles) != 0 {
|
||||
t.Error("Expected zero WriteFiles")
|
||||
}
|
||||
|
||||
if cfg.Hostname != "" {
|
||||
t.Errorf("Expected hostname to be empty, got '%s'", cfg.Hostname)
|
||||
}
|
||||
}
|
||||
|
||||
// Assert that the parsing of a cloud config file "generally works"
|
||||
func TestCloudConfig(t *testing.T) {
|
||||
contents := `
|
||||
coreos:
|
||||
etcd:
|
||||
discovery: "https://discovery.etcd.io/827c73219eeb2fa5530027c37bf18877"
|
||||
update:
|
||||
reboot_strategy: reboot
|
||||
units:
|
||||
- name: 50-eth0.network
|
||||
runtime: yes
|
||||
content: '[Match]
|
||||
|
||||
Name=eth47
|
||||
|
||||
|
||||
[Network]
|
||||
|
||||
Address=10.209.171.177/19
|
||||
|
||||
'
|
||||
oem:
|
||||
id: rackspace
|
||||
name: Rackspace Cloud Servers
|
||||
version_id: 168.0.0
|
||||
home_url: https://www.rackspace.com/cloud/servers/
|
||||
bug_report_url: https://github.com/coreos/coreos-overlay
|
||||
ssh_authorized_keys:
|
||||
- foobar
|
||||
- foobaz
|
||||
write_files:
|
||||
- content: |
|
||||
penny
|
||||
elroy
|
||||
path: /etc/dogepack.conf
|
||||
permissions: '0644'
|
||||
owner: root:dogepack
|
||||
hostname: trontastic
|
||||
`
|
||||
cfg, err := NewCloudConfig(contents)
|
||||
if err != nil {
|
||||
t.Fatalf("Encountered unexpected error :%v", err)
|
||||
}
|
||||
|
||||
keys := cfg.SSHAuthorizedKeys
|
||||
if len(keys) != 2 {
|
||||
t.Error("Parsed incorrect number of SSH keys")
|
||||
} else if keys[0] != "foobar" {
|
||||
t.Error("Expected first SSH key to be 'foobar'")
|
||||
} else if keys[1] != "foobaz" {
|
||||
t.Error("Expected first SSH key to be 'foobaz'")
|
||||
}
|
||||
|
||||
if len(cfg.WriteFiles) != 1 {
|
||||
t.Error("Failed to parse correct number of write_files")
|
||||
} else {
|
||||
wf := cfg.WriteFiles[0]
|
||||
if wf.Content != "penny\nelroy\n" {
|
||||
t.Errorf("WriteFile has incorrect contents '%s'", wf.Content)
|
||||
}
|
||||
if wf.Encoding != "" {
|
||||
t.Errorf("WriteFile has incorrect encoding %s", wf.Encoding)
|
||||
}
|
||||
if wf.RawFilePermissions != "0644" {
|
||||
t.Errorf("WriteFile has incorrect permissions %s", wf.RawFilePermissions)
|
||||
}
|
||||
if wf.Path != "/etc/dogepack.conf" {
|
||||
t.Errorf("WriteFile has incorrect path %s", wf.Path)
|
||||
}
|
||||
if wf.Owner != "root:dogepack" {
|
||||
t.Errorf("WriteFile has incorrect owner %s", wf.Owner)
|
||||
}
|
||||
}
|
||||
|
||||
if len(cfg.CoreOS.Units) != 1 {
|
||||
t.Error("Failed to parse correct number of units")
|
||||
} else {
|
||||
u := cfg.CoreOS.Units[0]
|
||||
expect := `[Match]
|
||||
Name=eth47
|
||||
|
||||
[Network]
|
||||
Address=10.209.171.177/19
|
||||
`
|
||||
if u.Content != expect {
|
||||
t.Errorf("Unit has incorrect contents '%s'.\nExpected '%s'.", u.Content, expect)
|
||||
}
|
||||
if u.Runtime != true {
|
||||
t.Errorf("Unit has incorrect runtime value")
|
||||
}
|
||||
if u.Name != "50-eth0.network" {
|
||||
t.Errorf("Unit has incorrect name %s", u.Name)
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.CoreOS.OEM.ID != "rackspace" {
|
||||
t.Errorf("Failed parsing coreos.oem. Expected ID 'rackspace', got %q.", cfg.CoreOS.OEM.ID)
|
||||
}
|
||||
|
||||
if cfg.Hostname != "trontastic" {
|
||||
t.Errorf("Failed to parse hostname")
|
||||
}
|
||||
if cfg.CoreOS.Update.RebootStrategy != "reboot" {
|
||||
t.Errorf("Failed to parse locksmith strategy")
|
||||
}
|
||||
}
|
||||
|
||||
// Assert that our interface conversion doesn't panic
|
||||
func TestCloudConfigKeysNotList(t *testing.T) {
|
||||
contents := `
|
||||
ssh_authorized_keys:
|
||||
- foo: bar
|
||||
`
|
||||
cfg, err := NewCloudConfig(contents)
|
||||
if err != nil {
|
||||
t.Fatalf("Encountered unexpected error: %v", err)
|
||||
}
|
||||
|
||||
keys := cfg.SSHAuthorizedKeys
|
||||
if len(keys) != 0 {
|
||||
t.Error("Parsed incorrect number of SSH keys")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCloudConfigSerializationHeader(t *testing.T) {
|
||||
cfg, _ := NewCloudConfig("")
|
||||
contents := cfg.String()
|
||||
header := strings.SplitN(contents, "\n", 2)[0]
|
||||
if header != "#cloud-config" {
|
||||
t.Fatalf("Serialized config did not have expected header")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCloudConfigUsers(t *testing.T) {
|
||||
contents := `
|
||||
users:
|
||||
- name: elroy
|
||||
passwd: somehash
|
||||
ssh_authorized_keys:
|
||||
- somekey
|
||||
gecos: arbitrary comment
|
||||
homedir: /home/place
|
||||
no_create_home: yes
|
||||
primary_group: things
|
||||
groups:
|
||||
- ping
|
||||
- pong
|
||||
no_user_group: true
|
||||
system: y
|
||||
no_log_init: True
|
||||
shell: /bin/sh
|
||||
`
|
||||
cfg, err := NewCloudConfig(contents)
|
||||
if err != nil {
|
||||
t.Fatalf("Encountered unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if len(cfg.Users) != 1 {
|
||||
t.Fatalf("Parsed %d users, expected 1", len(cfg.Users))
|
||||
}
|
||||
|
||||
user := cfg.Users[0]
|
||||
|
||||
if user.Name != "elroy" {
|
||||
t.Errorf("User name is %q, expected 'elroy'", user.Name)
|
||||
}
|
||||
|
||||
if user.PasswordHash != "somehash" {
|
||||
t.Errorf("User passwd is %q, expected 'somehash'", user.PasswordHash)
|
||||
}
|
||||
|
||||
if keys := user.SSHAuthorizedKeys; len(keys) != 1 {
|
||||
t.Errorf("Parsed %d ssh keys, expected 1", len(keys))
|
||||
} else {
|
||||
key := user.SSHAuthorizedKeys[0]
|
||||
if key != "somekey" {
|
||||
t.Errorf("User SSH key is %q, expected 'somekey'", key)
|
||||
}
|
||||
}
|
||||
|
||||
if user.GECOS != "arbitrary comment" {
|
||||
t.Errorf("Failed to parse gecos field, got %q", user.GECOS)
|
||||
}
|
||||
|
||||
if user.Homedir != "/home/place" {
|
||||
t.Errorf("Failed to parse homedir field, got %q", user.Homedir)
|
||||
}
|
||||
|
||||
if !user.NoCreateHome {
|
||||
t.Errorf("Failed to parse no_create_home field")
|
||||
}
|
||||
|
||||
if user.PrimaryGroup != "things" {
|
||||
t.Errorf("Failed to parse primary_group field, got %q", user.PrimaryGroup)
|
||||
}
|
||||
|
||||
if len(user.Groups) != 2 {
|
||||
t.Errorf("Failed to parse 2 goups, got %d", len(user.Groups))
|
||||
} else {
|
||||
if user.Groups[0] != "ping" {
|
||||
t.Errorf("First group was %q, not expected value 'ping'", user.Groups[0])
|
||||
}
|
||||
if user.Groups[1] != "pong" {
|
||||
t.Errorf("First group was %q, not expected value 'pong'", user.Groups[1])
|
||||
}
|
||||
}
|
||||
|
||||
if !user.NoUserGroup {
|
||||
t.Errorf("Failed to parse no_user_group field")
|
||||
}
|
||||
|
||||
if !user.System {
|
||||
t.Errorf("Failed to parse system field")
|
||||
}
|
||||
|
||||
if !user.NoLogInit {
|
||||
t.Errorf("Failed to parse no_log_init field")
|
||||
}
|
||||
|
||||
if user.Shell != "/bin/sh" {
|
||||
t.Errorf("Failed to parse shell field, got %q", user.Shell)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCloudConfigUsersGithubUser(t *testing.T) {
|
||||
|
||||
contents := `
|
||||
users:
|
||||
- name: elroy
|
||||
coreos_ssh_import_github: bcwaldon
|
||||
`
|
||||
cfg, err := NewCloudConfig(contents)
|
||||
if err != nil {
|
||||
t.Fatalf("Encountered unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if len(cfg.Users) != 1 {
|
||||
t.Fatalf("Parsed %d users, expected 1", len(cfg.Users))
|
||||
}
|
||||
|
||||
user := cfg.Users[0]
|
||||
|
||||
if user.Name != "elroy" {
|
||||
t.Errorf("User name is %q, expected 'elroy'", user.Name)
|
||||
}
|
||||
|
||||
if user.SSHImportGithubUser != "bcwaldon" {
|
||||
t.Errorf("github user is %q, expected 'bcwaldon'", user.SSHImportGithubUser)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCloudConfigUsersSSHImportURL(t *testing.T) {
|
||||
contents := `
|
||||
users:
|
||||
- name: elroy
|
||||
coreos_ssh_import_url: https://token:x-auth-token@github.enterprise.com/api/v3/polvi/keys
|
||||
`
|
||||
cfg, err := NewCloudConfig(contents)
|
||||
if err != nil {
|
||||
t.Fatalf("Encountered unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if len(cfg.Users) != 1 {
|
||||
t.Fatalf("Parsed %d users, expected 1", len(cfg.Users))
|
||||
}
|
||||
|
||||
user := cfg.Users[0]
|
||||
|
||||
if user.Name != "elroy" {
|
||||
t.Errorf("User name is %q, expected 'elroy'", user.Name)
|
||||
}
|
||||
|
||||
if user.SSHImportURL != "https://token:x-auth-token@github.enterprise.com/api/v3/polvi/keys" {
|
||||
t.Errorf("ssh import url is %q, expected 'https://token:x-auth-token@github.enterprise.com/api/v3/polvi/keys'", user.SSHImportURL)
|
||||
}
|
||||
}
|
||||
59
config/cloudinit/config/etcd2.go
Normal file
59
config/cloudinit/config/etcd2.go
Normal file
@@ -0,0 +1,59 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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.
|
||||
|
||||
package config
|
||||
|
||||
type Etcd2 struct {
|
||||
AdvertiseClientURLs string `yaml:"advertise_client_urls" env:"ETCD_ADVERTISE_CLIENT_URLS"`
|
||||
CAFile string `yaml:"ca_file" env:"ETCD_CA_FILE" deprecated:"ca_file obsoleted by trusted_ca_file and client_cert_auth"`
|
||||
CertFile string `yaml:"cert_file" env:"ETCD_CERT_FILE"`
|
||||
ClientCertAuth bool `yaml:"client_cert_auth" env:"ETCD_CLIENT_CERT_AUTH"`
|
||||
CorsOrigins string `yaml:"cors" env:"ETCD_CORS"`
|
||||
DataDir string `yaml:"data_dir" env:"ETCD_DATA_DIR"`
|
||||
Debug bool `yaml:"debug" env:"ETCD_DEBUG"`
|
||||
Discovery string `yaml:"discovery" env:"ETCD_DISCOVERY"`
|
||||
DiscoveryFallback string `yaml:"discovery_fallback" env:"ETCD_DISCOVERY_FALLBACK"`
|
||||
DiscoverySRV string `yaml:"discovery_srv" env:"ETCD_DISCOVERY_SRV"`
|
||||
DiscoveryProxy string `yaml:"discovery_proxy" env:"ETCD_DISCOVERY_PROXY"`
|
||||
ElectionTimeout int `yaml:"election_timeout" env:"ETCD_ELECTION_TIMEOUT"`
|
||||
EnablePprof bool `yaml:"enable_pprof" env:"ETCD_ENABLE_PPROF"`
|
||||
ForceNewCluster bool `yaml:"force_new_cluster" env:"ETCD_FORCE_NEW_CLUSTER"`
|
||||
HeartbeatInterval int `yaml:"heartbeat_interval" env:"ETCD_HEARTBEAT_INTERVAL"`
|
||||
InitialAdvertisePeerURLs string `yaml:"initial_advertise_peer_urls" env:"ETCD_INITIAL_ADVERTISE_PEER_URLS"`
|
||||
InitialCluster string `yaml:"initial_cluster" env:"ETCD_INITIAL_CLUSTER"`
|
||||
InitialClusterState string `yaml:"initial_cluster_state" env:"ETCD_INITIAL_CLUSTER_STATE"`
|
||||
InitialClusterToken string `yaml:"initial_cluster_token" env:"ETCD_INITIAL_CLUSTER_TOKEN"`
|
||||
KeyFile string `yaml:"key_file" env:"ETCD_KEY_FILE"`
|
||||
ListenClientURLs string `yaml:"listen_client_urls" env:"ETCD_LISTEN_CLIENT_URLS"`
|
||||
ListenPeerURLs string `yaml:"listen_peer_urls" env:"ETCD_LISTEN_PEER_URLS"`
|
||||
LogPackageLevels string `yaml:"log_package_levels" env:"ETCD_LOG_PACKAGE_LEVELS"`
|
||||
MaxSnapshots int `yaml:"max_snapshots" env:"ETCD_MAX_SNAPSHOTS"`
|
||||
MaxWALs int `yaml:"max_wals" env:"ETCD_MAX_WALS"`
|
||||
Name string `yaml:"name" env:"ETCD_NAME"`
|
||||
PeerCAFile string `yaml:"peer_ca_file" env:"ETCD_PEER_CA_FILE" deprecated:"peer_ca_file obsoleted peer_trusted_ca_file and peer_client_cert_auth"`
|
||||
PeerCertFile string `yaml:"peer_cert_file" env:"ETCD_PEER_CERT_FILE"`
|
||||
PeerKeyFile string `yaml:"peer_key_file" env:"ETCD_PEER_KEY_FILE"`
|
||||
PeerClientCertAuth bool `yaml:"peer_client_cert_auth" env:"ETCD_PEER_CLIENT_CERT_AUTH"`
|
||||
PeerTrustedCAFile string `yaml:"peer_trusted_ca_file" env:"ETCD_PEER_TRUSTED_CA_FILE"`
|
||||
Proxy string `yaml:"proxy" env:"ETCD_PROXY" valid:"^(on|off|readonly)$"`
|
||||
ProxyDialTimeout int `yaml:"proxy_dial_timeout" env:"ETCD_PROXY_DIAL_TIMEOUT"`
|
||||
ProxyFailureWait int `yaml:"proxy_failure_wait" env:"ETCD_PROXY_FAILURE_WAIT"`
|
||||
ProxyReadTimeout int `yaml:"proxy_read_timeout" env:"ETCD_PROXY_READ_TIMEOUT"`
|
||||
ProxyRefreshInterval int `yaml:"proxy_refresh_interval" env:"ETCD_PROXY_REFRESH_INTERVAL"`
|
||||
ProxyWriteTimeout int `yaml:"proxy_write_timeout" env:"ETCD_PROXY_WRITE_TIMEOUT"`
|
||||
SnapshotCount int `yaml:"snapshot_count" env:"ETCD_SNAPSHOT_COUNT"`
|
||||
StrictReconfigCheck bool `yaml:"strict_reconfig_check" env:"ETCD_STRICT_RECONFIG_CHECK"`
|
||||
TrustedCAFile string `yaml:"trusted_ca_file" env:"ETCD_TRUSTED_CA_FILE"`
|
||||
WalDir string `yaml:"wal_dir" env:"ETCD_WAL_DIR"`
|
||||
}
|
||||
29
config/cloudinit/config/flannel.go
Normal file
29
config/cloudinit/config/flannel.go
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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.
|
||||
|
||||
package config
|
||||
|
||||
type Flannel struct {
|
||||
EtcdEndpoints string `yaml:"etcd_endpoints" env:"FLANNELD_ETCD_ENDPOINTS"`
|
||||
EtcdCAFile string `yaml:"etcd_cafile" env:"FLANNELD_ETCD_CAFILE"`
|
||||
EtcdCertFile string `yaml:"etcd_certfile" env:"FLANNELD_ETCD_CERTFILE"`
|
||||
EtcdKeyFile string `yaml:"etcd_keyfile" env:"FLANNELD_ETCD_KEYFILE"`
|
||||
EtcdPrefix string `yaml:"etcd_prefix" env:"FLANNELD_ETCD_PREFIX"`
|
||||
EtcdUsername string `yaml:"etcd_username" env:"FLANNELD_ETCD_USERNAME"`
|
||||
EtcdPassword string `yaml:"etcd_password" env:"FLANNELD_ETCD_PASSWORD"`
|
||||
IPMasq string `yaml:"ip_masq" env:"FLANNELD_IP_MASQ"`
|
||||
SubnetFile string `yaml:"subnet_file" env:"FLANNELD_SUBNET_FILE"`
|
||||
Iface string `yaml:"interface" env:"FLANNELD_IFACE"`
|
||||
PublicIP string `yaml:"public_ip" env:"FLANNELD_PUBLIC_IP"`
|
||||
}
|
||||
35
config/cloudinit/config/fleet.go
Normal file
35
config/cloudinit/config/fleet.go
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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.
|
||||
|
||||
package config
|
||||
|
||||
type Fleet struct {
|
||||
AgentTTL string `yaml:"agent_ttl" env:"FLEET_AGENT_TTL"`
|
||||
AuthorizedKeysFile string `yaml:"authorized_keys_file" env:"FLEET_AUTHORIZED_KEYS_FILE"`
|
||||
DisableEngine bool `yaml:"disable_engine" env:"FLEET_DISABLE_ENGINE"`
|
||||
EngineReconcileInterval float64 `yaml:"engine_reconcile_interval" env:"FLEET_ENGINE_RECONCILE_INTERVAL"`
|
||||
EtcdCAFile string `yaml:"etcd_cafile" env:"FLEET_ETCD_CAFILE"`
|
||||
EtcdCertFile string `yaml:"etcd_certfile" env:"FLEET_ETCD_CERTFILE"`
|
||||
EtcdKeyFile string `yaml:"etcd_keyfile" env:"FLEET_ETCD_KEYFILE"`
|
||||
EtcdKeyPrefix string `yaml:"etcd_key_prefix" env:"FLEET_ETCD_KEY_PREFIX"`
|
||||
EtcdRequestTimeout float64 `yaml:"etcd_request_timeout" env:"FLEET_ETCD_REQUEST_TIMEOUT"`
|
||||
EtcdServers string `yaml:"etcd_servers" env:"FLEET_ETCD_SERVERS"`
|
||||
EtcdUsername string `yaml:"etcd_username" env:"FLEET_ETCD_USERNAME"`
|
||||
EtcdPassword string `yaml:"etcd_password" env:"FLEET_ETCD_PASSWORD"`
|
||||
Metadata string `yaml:"metadata" env:"FLEET_METADATA"`
|
||||
PublicIP string `yaml:"public_ip" env:"FLEET_PUBLIC_IP"`
|
||||
TokenLimit int `yaml:"token_limit" env:"FLEET_TOKEN_LIMIT"`
|
||||
Verbosity int `yaml:"verbosity" env:"FLEET_VERBOSITY"`
|
||||
VerifyUnits bool `yaml:"verify_units" env:"FLEET_VERIFY_UNITS"`
|
||||
}
|
||||
@@ -20,7 +20,10 @@ import (
|
||||
|
||||
func IsIgnitionConfig(userdata string) bool {
|
||||
var cfg struct {
|
||||
Version *int `json:"ignitionVersion" yaml:"ignition_version"`
|
||||
Version *int `json:"ignitionVersion"`
|
||||
Ignition struct {
|
||||
Version *string `json:"version"`
|
||||
} `json:"ignition"`
|
||||
}
|
||||
return (json.Unmarshal([]byte(userdata), &cfg) == nil && cfg.Version != nil)
|
||||
return (json.Unmarshal([]byte(userdata), &cfg) == nil && (cfg.Version != nil || cfg.Ignition.Version != nil))
|
||||
}
|
||||
27
config/cloudinit/config/locksmith.go
Normal file
27
config/cloudinit/config/locksmith.go
Normal file
@@ -0,0 +1,27 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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.
|
||||
|
||||
package config
|
||||
|
||||
type Locksmith struct {
|
||||
Endpoint string `yaml:"endpoint" env:"LOCKSMITHD_ENDPOINT"`
|
||||
EtcdCAFile string `yaml:"etcd_cafile" env:"LOCKSMITHD_ETCD_CAFILE"`
|
||||
EtcdCertFile string `yaml:"etcd_certfile" env:"LOCKSMITHD_ETCD_CERTFILE"`
|
||||
EtcdKeyFile string `yaml:"etcd_keyfile" env:"LOCKSMITHD_ETCD_KEYFILE"`
|
||||
EtcdUsername string `yaml:"etcd_username" env:"LOCKSMITHD_ETCD_USERNAME"`
|
||||
EtcdPassword string `yaml:"etcd_password" env:"LOCKSMITHD_ETCD_PASSWORD"`
|
||||
Group string `yaml:"group" env:"LOCKSMITHD_GROUP"`
|
||||
RebootWindowStart string `yaml:"window_start" env:"REBOOT_WINDOW_START" valid:"^((?i:sun|mon|tue|wed|thu|fri|sat|sun) )?0*([0-9]|1[0-9]|2[0-3]):0*([0-9]|[1-5][0-9])$"`
|
||||
RebootWindowLength string `yaml:"window_length" env:"REBOOT_WINDOW_LENGTH" valid:"^[-+]?([0-9]*(\\.[0-9]*)?[a-z]+)+$"`
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user