mirror of
https://github.com/replicatedhq/troubleshoot.git
synced 2026-08-27 00:37:20 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6e9df1773 | ||
|
|
755220cd4c | ||
|
|
7289134757 | ||
|
|
63362d32ee | ||
|
|
aeb89e57a7 | ||
|
|
8d1a0f6d24 | ||
|
|
0cfd431274 | ||
|
|
fe5dd3ee8f | ||
|
|
7eb1d5a5fb | ||
|
|
40cdc6805e | ||
|
|
3d7a255e32 | ||
|
|
77a2475bd2 | ||
|
|
7bfb54360c | ||
|
|
ca0b3c31c1 | ||
|
|
2b774e16d7 | ||
|
|
9d41d4a7be | ||
|
|
e248ab0f97 | ||
|
|
942234da80 | ||
|
|
0a2ed01a46 | ||
|
|
a818417e8c | ||
|
|
f9039a0fb5 | ||
|
|
3351c289ab | ||
|
|
6f0cf6550d | ||
|
|
7425f583fc | ||
|
|
c3af3b63d9 | ||
|
|
8fc7d12e19 | ||
|
|
877e43dd47 | ||
|
|
e18d273205 | ||
|
|
3d97d8e5a6 | ||
|
|
137b0cee14 | ||
|
|
007edd1181 | ||
|
|
3cedbe16a7 | ||
|
|
46cfa14598 | ||
|
|
4c72573936 | ||
|
|
347d462d7f | ||
|
|
d1f341b8ed | ||
|
|
186180612b | ||
|
|
bed930fa7c | ||
|
|
08b746d021 | ||
|
|
f9b55e2bff | ||
|
|
f4db86960b | ||
|
|
40eea877ad | ||
|
|
9e2dac4d55 | ||
|
|
5c4825f4d4 | ||
|
|
2252e61cbf | ||
|
|
7582e58220 | ||
|
|
c18453711e | ||
|
|
9603ccdc56 | ||
|
|
891ee93cb1 | ||
|
|
2cfa690838 | ||
|
|
9b9824bef5 | ||
|
|
60ef14a751 | ||
|
|
3980a7b903 | ||
|
|
655fa9b4a0 | ||
|
|
27c5e51920 | ||
|
|
177f2da16d | ||
|
|
d774ad1f5a | ||
|
|
9c5ae1f64e | ||
|
|
a237dba463 | ||
|
|
60ec620ba9 | ||
|
|
0c422aefbd | ||
|
|
c2ac654e08 | ||
|
|
ffc8a347c6 | ||
|
|
0a5e397d44 | ||
|
|
e03cf6b37f | ||
|
|
2ec15b69f6 | ||
|
|
39bd7778f5 | ||
|
|
59d50e7679 | ||
|
|
cb8dccbe7b | ||
|
|
fba0f97225 | ||
|
|
4d0eaf471f | ||
|
|
213d518136 | ||
|
|
c4ba3fe70e | ||
|
|
b524ac8ac4 | ||
|
|
7403de58bd | ||
|
|
9eadae6771 | ||
|
|
75a56d09b2 | ||
|
|
4a72e7d2a5 | ||
|
|
2c53fb4da6 | ||
|
|
6a360b635f | ||
|
|
913963a809 | ||
|
|
49c56f6cd7 | ||
|
|
baa943474d | ||
|
|
fa2c775abd | ||
|
|
ac65e4dc97 | ||
|
|
dfb0991fb2 | ||
|
|
101ab5d2fa | ||
|
|
5211cb0fe7 | ||
|
|
c7c21e88fb | ||
|
|
ea7756f8f2 | ||
|
|
739ee666af | ||
|
|
181df9c85b | ||
|
|
742ddc8c06 | ||
|
|
b4c97e377e | ||
|
|
7cb6d90a39 | ||
|
|
5d9f14fde5 | ||
|
|
14463642b0 | ||
|
|
45dd980012 | ||
|
|
e100e7c478 | ||
|
|
1345b200aa | ||
|
|
1cb0dab3c2 | ||
|
|
db3d27d38f | ||
|
|
e7daba9d0c | ||
|
|
ada35eb31c | ||
|
|
f2374cf113 | ||
|
|
e7f7e75df1 | ||
|
|
5a8561a31f | ||
|
|
0df678929a | ||
|
|
1cdfd96768 | ||
|
|
9666679ab4 | ||
|
|
f108c3ca57 | ||
|
|
491376b772 | ||
|
|
34724e7932 | ||
|
|
7e11498509 | ||
|
|
3e60fcda8e | ||
|
|
b32afc1d17 | ||
|
|
f048b426a5 | ||
|
|
26402a7b04 | ||
|
|
3d1d53ee9d | ||
|
|
9cbdb70d16 | ||
|
|
20f1b60f11 | ||
|
|
fafac18d29 | ||
|
|
072d2d7a36 | ||
|
|
7b36e6a1f8 | ||
|
|
cf03503216 | ||
|
|
111396eb39 | ||
|
|
372454651e | ||
|
|
5dece3eb75 | ||
|
|
e095a7838f | ||
|
|
08953d46d1 | ||
|
|
bc197761ea | ||
|
|
dc8b38d249 | ||
|
|
73609c4fef | ||
|
|
977fc438ea | ||
|
|
4d52760d35 | ||
|
|
6e34aa615e | ||
|
|
ca51e92878 | ||
|
|
c546577449 | ||
|
|
06750d478e | ||
|
|
6d0a57b16e | ||
|
|
8a29442a2a | ||
|
|
299497c0c0 | ||
|
|
050f5939c6 | ||
|
|
0e8bedc281 | ||
|
|
bb0515830d | ||
|
|
b903f1f1c4 | ||
|
|
f26c9b4136 | ||
|
|
eb795c98b6 | ||
|
|
a0b3b3f7dc | ||
|
|
67987a4432 | ||
|
|
1bdd3db8c5 | ||
|
|
a2b5edb551 | ||
|
|
880c7dc3ea | ||
|
|
922f7c8b23 | ||
|
|
afa08e5362 | ||
|
|
614aed52c9 | ||
|
|
72a50ee3f2 | ||
|
|
0c7fede7b6 | ||
|
|
639bf7a832 | ||
|
|
48287097d8 | ||
|
|
cb5ddf752f | ||
|
|
52e1a04f57 | ||
|
|
5b4b548aa0 | ||
|
|
f2a232d174 | ||
|
|
f4e675dae0 | ||
|
|
867df407ea | ||
|
|
e0fb748498 | ||
|
|
463783d2fa | ||
|
|
2ce78ac33a | ||
|
|
4cf0f5881d | ||
|
|
465a533640 | ||
|
|
10785987c5 | ||
|
|
91eb94baaa | ||
|
|
1b65d1a544 | ||
|
|
6e09aa641d | ||
|
|
7fcc951c9a | ||
|
|
123e2e1049 | ||
|
|
558f92caef | ||
|
|
0478a7a60f | ||
|
|
5b157381fe | ||
|
|
1181bc21d0 | ||
|
|
c7af0dc593 | ||
|
|
f30e925580 | ||
|
|
e23fb2ce59 | ||
|
|
fd3b32293c | ||
|
|
2977f8f0d3 | ||
|
|
bf7d658313 | ||
|
|
e2dbb623e3 | ||
|
|
6349ae8aee | ||
|
|
851c91b582 | ||
|
|
cf4d510413 | ||
|
|
e0f6cab5b3 | ||
|
|
cf7864cd97 | ||
|
|
8dcfa9886d | ||
|
|
6007f15253 | ||
|
|
ba65f92ca2 | ||
|
|
6a0fb2e19c | ||
|
|
6861660460 | ||
|
|
1ed6100ac8 | ||
|
|
39350b5722 | ||
|
|
d7b6aa2758 | ||
|
|
0d56371242 | ||
|
|
c95dc489a2 | ||
|
|
c119a16235 | ||
|
|
d730e6cad6 | ||
|
|
7e32de464a | ||
|
|
ae4c07027b | ||
|
|
eef54d0021 | ||
|
|
972fe2c210 | ||
|
|
4047977b35 | ||
|
|
3382d29e96 | ||
|
|
4b4eedf477 | ||
|
|
7381d5086c | ||
|
|
1ed8532663 | ||
|
|
3833955a58 | ||
|
|
6aa97a9a51 | ||
|
|
52bbc0f2bf | ||
|
|
8fbae89465 | ||
|
|
53e8ee5495 | ||
|
|
c018452b8a | ||
|
|
cb3925a0af | ||
|
|
900dd9e417 | ||
|
|
776934af2b | ||
|
|
f92d6f9cfe | ||
|
|
904afc0b2a | ||
|
|
e1bccd74b5 | ||
|
|
2f34561a39 | ||
|
|
a86f5cae7d | ||
|
|
ed6b3369b3 | ||
|
|
0d5f17de3c | ||
|
|
12fb05e5f9 | ||
|
|
646f7a6991 | ||
|
|
160fffe9ed | ||
|
|
c17be3fb73 | ||
|
|
35718432fe | ||
|
|
0dd24c702c | ||
|
|
2c3909f585 | ||
|
|
b8622a65ad | ||
|
|
f1ca1a5345 | ||
|
|
9357d5ac96 | ||
|
|
eef9ebe0e5 | ||
|
|
25a92dec56 | ||
|
|
39cf553a03 | ||
|
|
e5233dfcf5 | ||
|
|
1562a645de | ||
|
|
3f43697b57 | ||
|
|
67ec6f54bc | ||
|
|
c43da65afe | ||
|
|
dd2c2f84e6 | ||
|
|
45decfcb7d | ||
|
|
7d7e3c2664 | ||
|
|
822ada99a2 | ||
|
|
30f21ac71b | ||
|
|
ce815844cd | ||
|
|
0a6c9836e0 | ||
|
|
e21813535c | ||
|
|
44993a5d0d | ||
|
|
a2f4041a1b | ||
|
|
744523cc09 | ||
|
|
477cde7228 | ||
|
|
ae63ccba8d | ||
|
|
5c5e30a9a5 | ||
|
|
ac2a0484e2 | ||
|
|
bc91151883 | ||
|
|
de8feb167e | ||
|
|
0a6ef1c2c3 | ||
|
|
4a89067912 | ||
|
|
1e17c5c4c2 | ||
|
|
32cca1215a | ||
|
|
052d10fec7 | ||
|
|
7a0c6e5383 | ||
|
|
1dc03db254 | ||
|
|
fe414af556 | ||
|
|
f8dca39899 | ||
|
|
19aef8a02f | ||
|
|
4b6606e323 | ||
|
|
559e18d996 | ||
|
|
bf4d26acd2 | ||
|
|
f3b599c19a | ||
|
|
810b3cbf78 | ||
|
|
81789181ed | ||
|
|
256c68feca | ||
|
|
b01e6ef578 | ||
|
|
62afc87af8 | ||
|
|
17bff4b2bd | ||
|
|
5f2525b663 | ||
|
|
afa0bc56d4 | ||
|
|
c1f9b3906d | ||
|
|
0a19d35073 | ||
|
|
7c4135d563 | ||
|
|
4b78c430ca | ||
|
|
09d16ff185 | ||
|
|
bdf843f84d | ||
|
|
47f7d98907 | ||
|
|
a1e0813d55 | ||
|
|
d6acd6d906 | ||
|
|
ce53dbcd29 | ||
|
|
3b10d9c9e1 | ||
|
|
7589b4fed0 | ||
|
|
a459120516 | ||
|
|
b02338762e | ||
|
|
76419237d4 | ||
|
|
87b4c12274 | ||
|
|
7647c039e9 | ||
|
|
c26824a619 | ||
|
|
617e0d55de | ||
|
|
b418334a46 | ||
|
|
b78d7d04d9 | ||
|
|
989d5f7dbd | ||
|
|
8aca23199c | ||
|
|
fe4db40b43 | ||
|
|
5eef177965 | ||
|
|
6498c34da5 | ||
|
|
b0a005796c | ||
|
|
1311064d21 | ||
|
|
450d7570eb | ||
|
|
40af0f8a9c | ||
|
|
529fe8cdbf | ||
|
|
105a718bbb | ||
|
|
c2b53036f9 | ||
|
|
ba22fe9f22 | ||
|
|
8df8a5c792 | ||
|
|
0bcd5183f5 | ||
|
|
e2c0c722ae | ||
|
|
9984fe2caa | ||
|
|
f2e4127111 | ||
|
|
f25149f45c | ||
|
|
2a06cb25e4 | ||
|
|
86bc366ec3 | ||
|
|
10a34c2e58 | ||
|
|
df68f7445f | ||
|
|
c7fdec0291 | ||
|
|
e8dba3cad9 | ||
|
|
3e2d90ee9b | ||
|
|
b05eb6bf75 | ||
|
|
2e16424b44 | ||
|
|
f19ac98055 | ||
|
|
55fbd92673 | ||
|
|
2f2edb5356 | ||
|
|
0ec1c43afd | ||
|
|
b43ef17b8f | ||
|
|
858d00ce41 | ||
|
|
c81576f25f | ||
|
|
1e8e20a295 | ||
|
|
4cdfa5b112 | ||
|
|
8ef720ba9e | ||
|
|
7d2e0d0aa3 | ||
|
|
88685ae5f7 | ||
|
|
364a63533a | ||
|
|
6749f339e8 | ||
|
|
2bf19eaddf | ||
|
|
fd6af65f7e | ||
|
|
9e669b3f13 | ||
|
|
4e74fd209e | ||
|
|
a00a1892d9 | ||
|
|
474a874f82 | ||
|
|
463ba62ffa | ||
|
|
46194fccb7 | ||
|
|
badedf34ee | ||
|
|
af2f49cb07 | ||
|
|
88a6d3f715 | ||
|
|
5a1321da02 | ||
|
|
014a716949 | ||
|
|
ad4893bec5 | ||
|
|
989093394c | ||
|
|
f1868d0ba8 | ||
|
|
3cbdb41c8f | ||
|
|
d2f6594c0c | ||
|
|
56408fab01 | ||
|
|
f0d9418e21 | ||
|
|
1ba3840ad9 | ||
|
|
a3d667298e | ||
|
|
5cf4ae2157 | ||
|
|
dede187841 | ||
|
|
7dc6e22d8d | ||
|
|
f3af73b6e3 | ||
|
|
4e047b2372 | ||
|
|
7f190e3cce | ||
|
|
2436a0c163 | ||
|
|
883abacaf8 | ||
|
|
7186b75f7e | ||
|
|
e16eabd531 | ||
|
|
3ff91a3b92 | ||
|
|
2020049cb7 | ||
|
|
d94954e6dd | ||
|
|
06817c1682 | ||
|
|
0554c2ac0f | ||
|
|
1087eb65d2 | ||
|
|
09dc6b5862 | ||
|
|
3d301f4307 | ||
|
|
446b1d5552 | ||
|
|
63c8422e63 | ||
|
|
616f6e73a5 | ||
|
|
213b52477f | ||
|
|
e9ba73d87f | ||
|
|
6e86cdc803 | ||
|
|
c023a81966 | ||
|
|
7d0bb1a5d7 | ||
|
|
3cf679260e | ||
|
|
53bd30755d | ||
|
|
7fe7216b29 | ||
|
|
2da1f518d3 | ||
|
|
8a30f2ddce | ||
|
|
0ea4e1d52f | ||
|
|
74c9ff2432 | ||
|
|
bfa50c9643 | ||
|
|
74a7e5f19c | ||
|
|
e3c954f460 | ||
|
|
16d3ff372f | ||
|
|
437357f099 | ||
|
|
955e08c35a | ||
|
|
58261cdaf2 | ||
|
|
545313aa06 | ||
|
|
1ceab64b6f | ||
|
|
eb0f692aa0 | ||
|
|
2ae1b9b678 | ||
|
|
2f2501ac3b | ||
|
|
a57894962d | ||
|
|
3f48574071 | ||
|
|
00bbfc0414 | ||
|
|
c4d4af6890 | ||
|
|
4149e4d6ed | ||
|
|
b2cb3bf43b | ||
|
|
e232cc0f34 | ||
|
|
f68e0dbae0 | ||
|
|
0126fb7d7a | ||
|
|
d2bc571a18 | ||
|
|
1f0842484c | ||
|
|
8c31054331 | ||
|
|
4b2e7e153e | ||
|
|
2289e00558 | ||
|
|
949f0f2213 | ||
|
|
703219119e | ||
|
|
5522b0623c | ||
|
|
8b1ae0194e | ||
|
|
a0ce85ae1e | ||
|
|
b1e251ed03 | ||
|
|
f7e4103d9b | ||
|
|
442c97f138 | ||
|
|
6862f0f6c1 | ||
|
|
6ad9103f86 | ||
|
|
016d6e4f18 | ||
|
|
97db83b270 | ||
|
|
20e82be101 | ||
|
|
4e08ce4d93 | ||
|
|
e8dc5ae1b8 | ||
|
|
0e667e9685 | ||
|
|
99f5e2d896 | ||
|
|
407b80daf5 | ||
|
|
c84dc6de45 | ||
|
|
a686e47fa2 | ||
|
|
56e58c0849 | ||
|
|
3b8e53bf90 | ||
|
|
cc9d3aedec | ||
|
|
a70843cd69 | ||
|
|
08c52770a9 | ||
|
|
52e385ffce | ||
|
|
d63321bc43 | ||
|
|
37ae457aa1 | ||
|
|
4b1852c3e1 | ||
|
|
dc80733397 | ||
|
|
78e328be86 | ||
|
|
ea43d4d9b6 | ||
|
|
8ce751f989 | ||
|
|
f55b9b6ae4 | ||
|
|
086f85845a | ||
|
|
eb90b6d935 | ||
|
|
13df9a0f87 | ||
|
|
89b4979cbc | ||
|
|
6e874483b6 | ||
|
|
afd2ee95ca | ||
|
|
e66d12dab2 | ||
|
|
d98172eb9f | ||
|
|
b070b4009c | ||
|
|
890cbb1378 | ||
|
|
0f1f8b511e | ||
|
|
19fb95e99b | ||
|
|
a22e2e25df | ||
|
|
8d991e1f9e | ||
|
|
91e856d6d5 | ||
|
|
65f957db81 | ||
|
|
a561254756 | ||
|
|
53626bb025 | ||
|
|
54d8d7585d | ||
|
|
4021a6c176 | ||
|
|
8c9734c19c | ||
|
|
b6ac6e4702 | ||
|
|
f256bdb653 | ||
|
|
1c3496e2f7 | ||
|
|
f5d737f2c5 | ||
|
|
619c4550bc | ||
|
|
6f42f85705 | ||
|
|
661d7fcfc4 | ||
|
|
47e715a350 | ||
|
|
b9c3ad2635 | ||
|
|
2787cf385c | ||
|
|
f03916cfd2 | ||
|
|
252b29fb0a | ||
|
|
553718eada | ||
|
|
171c440f27 | ||
|
|
869a9eafd3 | ||
|
|
6c61040f74 | ||
|
|
bde882047f | ||
|
|
0d84c62c78 | ||
|
|
f5183c62d6 | ||
|
|
5a2c153026 | ||
|
|
669cb2a145 | ||
|
|
fb2f028fb5 | ||
|
|
d37ace6e51 | ||
|
|
b6e92e1bdf | ||
|
|
27369acbf4 | ||
|
|
22d397cc7b | ||
|
|
428db221c3 | ||
|
|
f6a5d40bfd | ||
|
|
8bc627d20f | ||
|
|
7c95363afe | ||
|
|
f7aebd0745 | ||
|
|
523332268b | ||
|
|
ca6f118c62 | ||
|
|
769d616ede | ||
|
|
620343e3a1 | ||
|
|
994978ccd8 | ||
|
|
0f31088bd1 | ||
|
|
4b9680be8b | ||
|
|
20033b12d5 | ||
|
|
f795f909b3 | ||
|
|
b3828b175d | ||
|
|
fa84d3fbd3 | ||
|
|
f377b7c1a8 | ||
|
|
6d3321cb43 | ||
|
|
15b100e0ee | ||
|
|
cd84cfa629 | ||
|
|
94ccf284b8 | ||
|
|
2f092ae4a1 | ||
|
|
56150c3345 | ||
|
|
8f594e8764 | ||
|
|
7752a33268 | ||
|
|
94e27eb5e1 | ||
|
|
33580e9611 | ||
|
|
09bc81e144 | ||
|
|
7626f7d7d7 | ||
|
|
37f75c1a3f | ||
|
|
8f8b8b2c78 | ||
|
|
b1e1dbb42a | ||
|
|
1605b56090 | ||
|
|
a58913d1f6 | ||
|
|
eb84fc64e1 | ||
|
|
087505f8e5 | ||
|
|
8b54c28b4b | ||
|
|
7be40ddf16 | ||
|
|
de9b832357 | ||
|
|
fc65ceb5b4 | ||
|
|
d016e3269c | ||
|
|
079765b558 | ||
|
|
45ccd7be5b | ||
|
|
2bc9938a2a | ||
|
|
5050f46b1e | ||
|
|
222a93162d | ||
|
|
f06cd9e4a2 | ||
|
|
7dc93ddade | ||
|
|
a7c4f04a63 | ||
|
|
527fe16bcc | ||
|
|
406e1e6d1a | ||
|
|
cbaf96691e | ||
|
|
4dfb02e7f7 | ||
|
|
e19a5dee38 | ||
|
|
2c495b8a7a | ||
|
|
c3646bacd9 | ||
|
|
3844a3dbd3 | ||
|
|
544c832d6e | ||
|
|
38aef191ab | ||
|
|
545ce24c04 | ||
|
|
c262320cea | ||
|
|
d1c45f1456 | ||
|
|
c4f841d420 | ||
|
|
75ab7bb917 | ||
|
|
9692d5a457 | ||
|
|
2775a6393e | ||
|
|
b0c46c5ac8 | ||
|
|
e9ad961c0e | ||
|
|
bd71222715 | ||
|
|
818f8b26dd | ||
|
|
9dbf282b5b | ||
|
|
374856798f | ||
|
|
d754b3bb9f | ||
|
|
225ebfbcbe | ||
|
|
821a132980 | ||
|
|
4e3a627b21 | ||
|
|
206e5c01d7 | ||
|
|
e54ad171d8 | ||
|
|
8f78827002 | ||
|
|
77eba55ee8 | ||
|
|
562a565f1f | ||
|
|
db8533e69c | ||
|
|
f37bbdb14c | ||
|
|
35808423b2 | ||
|
|
86b745d6c2 | ||
|
|
c773ebd894 | ||
|
|
2ba2849a38 | ||
|
|
a1c5247c8b | ||
|
|
47ff99bfe2 | ||
|
|
6d19794dc5 | ||
|
|
0c3cc7afce | ||
|
|
5798a4aa11 | ||
|
|
ae64a4d591 | ||
|
|
983aaaacea | ||
|
|
e74101070d | ||
|
|
d9625477d2 | ||
|
|
1c66eed2cd | ||
|
|
1dda832933 | ||
|
|
d019f87284 | ||
|
|
983481da07 | ||
|
|
7462e1cdee | ||
|
|
6563290de9 | ||
|
|
ff627dcf83 | ||
|
|
2158dee58d | ||
|
|
f6a56a9def | ||
|
|
63a81314bb | ||
|
|
1d352298e1 | ||
|
|
26d8211a08 | ||
|
|
6c212e24f0 | ||
|
|
c9b01f981d | ||
|
|
4fca3ff4fc | ||
|
|
d81cff0d67 | ||
|
|
62d12d8fff | ||
|
|
8126cb3d3d | ||
|
|
6277101574 | ||
|
|
346c751bdf | ||
|
|
812aa9bc30 | ||
|
|
5a7b155093 | ||
|
|
c6ecab39cc | ||
|
|
648564eb4d | ||
|
|
1b9a220e2d | ||
|
|
45c7d95ebe | ||
|
|
42288a4ff3 | ||
|
|
2eb6518792 | ||
|
|
78d2364199 | ||
|
|
1f9e478a78 | ||
|
|
0d3c58eccd | ||
|
|
1a117220cb | ||
|
|
1c79403934 | ||
|
|
d1ab5a6991 | ||
|
|
c91a8ee60d | ||
|
|
899d5d7672 | ||
|
|
b466188679 | ||
|
|
f15579a3b2 | ||
|
|
1424faacab | ||
|
|
e94b152733 | ||
|
|
717ba13614 | ||
|
|
6765e6c030 | ||
|
|
eea2315c52 | ||
|
|
1e834430c2 | ||
|
|
729caa4a7b | ||
|
|
130aa88f3a | ||
|
|
f8870e5fc0 | ||
|
|
4472f10ae7 | ||
|
|
10b2941c57 | ||
|
|
879c3a67d7 | ||
|
|
299f3e0da7 | ||
|
|
3982e2023b | ||
|
|
8eddab69ce | ||
|
|
58b642426b | ||
|
|
6fe9e2e7e6 | ||
|
|
ecfe34f24c | ||
|
|
190eeef657 | ||
|
|
db48de2494 | ||
|
|
5d280aee02 | ||
|
|
f67cd31e80 | ||
|
|
8f87fd0f1e | ||
|
|
46a114478b | ||
|
|
f8c9cbf381 | ||
|
|
26f3540d98 | ||
|
|
2208b7672e | ||
|
|
b24032519e | ||
|
|
1ce5e7b70b | ||
|
|
052c932e05 | ||
|
|
5f60b1f0cb | ||
|
|
0379274bb8 | ||
|
|
ff97a4eabf | ||
|
|
033524a83a | ||
|
|
64675883eb | ||
|
|
0a04256d62 | ||
|
|
30f4d5e3f8 | ||
|
|
0fcf473f85 | ||
|
|
f9b9dea1c6 | ||
|
|
1928f00b0f | ||
|
|
d6b5ae9b7c | ||
|
|
3c8e4de10d | ||
|
|
600a67510b | ||
|
|
55f2ed44bf | ||
|
|
0993f6efd4 | ||
|
|
89250b0a87 | ||
|
|
46cebad731 | ||
|
|
8e1cb615a5 | ||
|
|
777d907f58 | ||
|
|
0aafbb79a6 | ||
|
|
47c686f352 | ||
|
|
0ad7a87e4d | ||
|
|
4a31134d3a | ||
|
|
2b97e524e3 | ||
|
|
3955dab9bc | ||
|
|
9de6b3fffd | ||
|
|
90b7ac986d | ||
|
|
9a59d1b018 | ||
|
|
af9defc753 | ||
|
|
806b2fbb41 | ||
|
|
8ca92b42c4 | ||
|
|
1ef9368e7b | ||
|
|
b22120acd8 | ||
|
|
518f826fc2 | ||
|
|
6a64146012 | ||
|
|
a0529b09cd | ||
|
|
f5fe442349 | ||
|
|
e2748c7b24 | ||
|
|
c962dc59f0 | ||
|
|
8ba2c576c3 | ||
|
|
fd47d23ac0 | ||
|
|
59a2281290 | ||
|
|
1465af2c09 | ||
|
|
7f82ec1f09 | ||
|
|
4338b22505 | ||
|
|
1757af58eb | ||
|
|
d8bae5dc8b | ||
|
|
ca9106e7c9 | ||
|
|
57094dbb44 | ||
|
|
7ac973647d | ||
|
|
94f812c7bd | ||
|
|
e5f0e09236 | ||
|
|
1ee662944f | ||
|
|
7ab1734cb6 | ||
|
|
c61acdc5de | ||
|
|
98f926b46c | ||
|
|
d0a0d925bb | ||
|
|
fd257b5618 | ||
|
|
f35f5e4d57 | ||
|
|
71e66c7402 | ||
|
|
3afe32cd1e | ||
|
|
714667d641 | ||
|
|
029282451e | ||
|
|
68c76867c7 | ||
|
|
e13cb35c82 | ||
|
|
9c4fa8ac14 | ||
|
|
1d3831f6d1 | ||
|
|
5812414093 | ||
|
|
94e468ade3 | ||
|
|
3dc78d2ce8 | ||
|
|
c4dd568267 | ||
|
|
00fa236525 | ||
|
|
ec24967e50 | ||
|
|
430fdaa9e4 | ||
|
|
58ed4c8fc2 | ||
|
|
353569d59a | ||
|
|
cd43fd6f56 | ||
|
|
ed8453ce6b | ||
|
|
5744d608a4 | ||
|
|
0e3bcd29e4 | ||
|
|
2a3e4e7d75 | ||
|
|
67b25e319b | ||
|
|
b0bbaa4e6b | ||
|
|
8a1e129dbe | ||
|
|
b926ff6fa2 | ||
|
|
fb5f1522ea | ||
|
|
6e7715a145 | ||
|
|
c0415dd100 | ||
|
|
1d031d6897 | ||
|
|
b19ac90512 | ||
|
|
13fb6abcaf | ||
|
|
218b9c006d | ||
|
|
0b493a533b | ||
|
|
5830eb34a0 | ||
|
|
8a1bb8a422 | ||
|
|
14ace5294f | ||
|
|
25ec38bdf5 | ||
|
|
223a4a6e0a | ||
|
|
3b6ab41e1b | ||
|
|
60ca35a5a6 | ||
|
|
36e3bf294d | ||
|
|
3ea71c1d5a | ||
|
|
26baaacee8 | ||
|
|
6f868877d8 | ||
|
|
075c0f6f16 | ||
|
|
2455f87ee1 | ||
|
|
a4c482c74c | ||
|
|
642c1d7e5b | ||
|
|
e88e3a4694 | ||
|
|
e23f799bed | ||
|
|
25e884c43a | ||
|
|
481aab7820 | ||
|
|
0b7aff6597 | ||
|
|
4153b9ac44 | ||
|
|
48750ba1e5 | ||
|
|
086d5569b3 | ||
|
|
996a55bf90 | ||
|
|
8125d5534b | ||
|
|
09deffb507 | ||
|
|
a8bdda483d | ||
|
|
c3e2748f3c | ||
|
|
0c7c8c3927 | ||
|
|
9a1bb9fa7b | ||
|
|
143f24b1e6 | ||
|
|
45d537744f | ||
|
|
1855c42b16 | ||
|
|
2fb3a17783 | ||
|
|
b195b34fbe | ||
|
|
1c6fe9f262 |
@@ -1,39 +0,0 @@
|
||||
steps:
|
||||
- label: build
|
||||
commands:
|
||||
- make
|
||||
plugins:
|
||||
- docker#v3.2.0:
|
||||
image: replicated/gitops-builder:buildkite-go12-node10
|
||||
always-pull: true
|
||||
mount-checkout: true
|
||||
workdir: /go/src/github.com/replicatedhq/troubleshoot
|
||||
|
||||
- wait
|
||||
|
||||
- commands:
|
||||
- make snapshot-release
|
||||
branches: "master"
|
||||
plugins:
|
||||
- docker#v3.2.0:
|
||||
image: replicated/gitops-builder:buildkite-go12-node10
|
||||
always-pull: true
|
||||
mount-checkout: true
|
||||
workdir: /go/src/github.com/replicatedhq/troubleshoot
|
||||
volumes:
|
||||
- "/var/run/docker.sock:/var/run/docker.sock"
|
||||
- "/var/lib/buildkite-agent/.docker/config.json:/home/builder/.docker/config.json"
|
||||
|
||||
- label: release
|
||||
commands:
|
||||
- if [ -z "$BUILDKITE_TAG" ]; then echo "Not a tag, not releasing"; else git tag -f "$BUILDKITE_TAG" && make release; fi
|
||||
plugins:
|
||||
- docker#v3.2.0:
|
||||
always-pull: true
|
||||
image: replicated/gitops-builder:buildkite-go12-node10
|
||||
workdir: /go/src/github.com/replicatedhq/troubleshoot
|
||||
volumes:
|
||||
- "/var/run/docker.sock:/var/run/docker.sock"
|
||||
- "/var/lib/buildkite-agent/.docker/config.json:/home/builder/.docker/config.json"
|
||||
environment:
|
||||
- GITHUB_TOKEN_TROUBLESHOOT
|
||||
@@ -0,0 +1,115 @@
|
||||
#-------------------------------------------------------------------------------------------------------------
|
||||
# Modified from Codespaces default container image: https://github.com/microsoft/vscode-dev-containers/blob/main/containers/codespaces-linux/history/1.6.3.md
|
||||
# - Remove PHP, Ruby, Dotnet, Java, powershell, rust dependencies
|
||||
# - Remove fish shell
|
||||
# - Remove Oryx
|
||||
# - Remove git-lfs
|
||||
# - Change shell to zsh
|
||||
#
|
||||
# TODO (dans): find a better way to pull in library script dynamically from vscode repo
|
||||
# TODO (dans): AWS CLI - make a common script in the dev-containers repo
|
||||
# TODO (dans): Gcloud CLI - make a common script in the dev-containers repo
|
||||
# TODO (dans): add gcommands alias
|
||||
# TODO (dans): terraform
|
||||
#-------------------------------------------------------------------------------------------------------------
|
||||
FROM mcr.microsoft.com/oryx/build:vso-focal-20210902.1 as replicated
|
||||
|
||||
ARG USERNAME=codespace
|
||||
ARG USER_UID=1000
|
||||
ARG USER_GID=$USER_UID
|
||||
ARG HOMEDIR=/home/$USERNAME
|
||||
|
||||
ARG GO_VERSION="latest"
|
||||
|
||||
# Default to bash shell (other shells available at /usr/bin/fish and /usr/bin/zsh)
|
||||
ENV SHELL=/bin/bash \
|
||||
ORYX_ENV_TYPE=vsonline-present \
|
||||
NODE_ROOT="${HOMEDIR}/.nodejs" \
|
||||
PYTHON_ROOT="${HOMEDIR}/.python" \
|
||||
HUGO_ROOT="${HOMEDIR}/.hugo" \
|
||||
NVM_SYMLINK_CURRENT=true \
|
||||
NVM_DIR="/home/${USERNAME}/.nvm" \
|
||||
NVS_HOME="/home/${USERNAME}/.nvs" \
|
||||
NPM_GLOBAL="/home/${USERNAME}/.npm-global" \
|
||||
KREW_HOME="/home/${USERNAME}/.krew/bin" \
|
||||
PIPX_HOME="/usr/local/py-utils" \
|
||||
PIPX_BIN_DIR="/usr/local/py-utils/bin" \
|
||||
GOROOT="/usr/local/go" \
|
||||
GOPATH="/go"
|
||||
|
||||
ENV PATH="${PATH}:${KREW_HOME}:${NVM_DIR}/current/bin:${NPM_GLOBAL}/bin:${ORIGINAL_PATH}:${GOROOT}/bin:${GOPATH}/bin:${PIPX_BIN_DIR}:/opt/conda/condabin:${NODE_ROOT}/current/bin:${PYTHON_ROOT}/current/bin:${HUGO_ROOT}/current/bin:${ORYX_PATHS}"
|
||||
|
||||
COPY library-scripts/* first-run-notice.txt /tmp/scripts/
|
||||
COPY ./config/* /etc/replicated/
|
||||
COPY ./lifecycle-scripts/* /var/lib/replicated/scripts/
|
||||
|
||||
# Install needed utilities and setup non-root user. Use a separate RUN statement to add your own dependencies.
|
||||
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
||||
# Restore man command
|
||||
&& yes | unminimize 2>&1 \
|
||||
# Run common script and setup user
|
||||
&& bash /tmp/scripts/common-debian.sh "true" "${USERNAME}" "${USER_UID}" "${USER_GID}" "true" "true" "true" \
|
||||
&& bash /tmp/scripts/setup-user.sh "${USERNAME}" "${PATH}" \
|
||||
# Change owner of opt contents since Oryx can dynamically install and will run as "codespace"
|
||||
&& chown ${USERNAME} /opt/* \
|
||||
&& chsh -s /bin/bash ${USERNAME} \
|
||||
# Verify expected build and debug tools are present
|
||||
&& apt-get -y install build-essential cmake python3-dev \
|
||||
# Install tools and shells not in common script
|
||||
&& apt-get install -yq vim vim-doc xtail software-properties-common libsecret-1-dev \
|
||||
# Install additional tools (useful for 'puppeteer' project)
|
||||
&& apt-get install -y --no-install-recommends libnss3 libnspr4 libatk-bridge2.0-0 libatk1.0-0 libx11-6 libpangocairo-1.0-0 \
|
||||
libx11-xcb1 libcups2 libxcomposite1 libxdamage1 libxfixes3 libpango-1.0-0 libgbm1 libgtk-3-0 \
|
||||
&& bash /tmp/scripts/sshd-debian.sh \
|
||||
&& bash /tmp/scripts/github-debian.sh \
|
||||
&& bash /tmp/scripts/azcli-debian.sh \
|
||||
# Install Moby CLI and Engine
|
||||
&& /bin/bash /tmp/scripts/docker-debian.sh "true" "/var/run/docker-host.sock" "/var/run/docker.sock" "${USERNAME}" "true" \
|
||||
# && bash /tmp/scripts/docker-in-docker-debian.sh "true" "${USERNAME}" "true" \
|
||||
&& bash /tmp/scripts/kubectl-helm-debian.sh \
|
||||
# Build latest git from source
|
||||
&& bash /tmp/scripts/git-from-src-debian.sh "latest" \
|
||||
# Clean up
|
||||
&& apt-get autoremove -y && apt-get clean -y \
|
||||
# Move first run notice to right spot
|
||||
&& mkdir -p /usr/local/etc/vscode-dev-containers/ \
|
||||
&& mv -f /tmp/scripts/first-run-notice.txt /usr/local/etc/vscode-dev-containers/
|
||||
|
||||
# Install Python
|
||||
RUN bash /tmp/scripts/python-debian.sh "none" "/opt/python/latest" "${PIPX_HOME}" "${USERNAME}" "true" \
|
||||
&& apt-get clean -y
|
||||
|
||||
# Setup Node.js, install NVM and NVS
|
||||
RUN bash /tmp/scripts/node-debian.sh "${NVM_DIR}" "none" "${USERNAME}" \
|
||||
&& (cd ${NVM_DIR} && git remote get-url origin && echo $(git log -n 1 --pretty=format:%H -- .)) > ${NVM_DIR}/.git-remote-and-commit \
|
||||
# Install nvs (alternate cross-platform Node.js version-management tool)
|
||||
&& sudo -u ${USERNAME} git clone -c advice.detachedHead=false --depth 1 https://github.com/jasongin/nvs ${NVS_HOME} 2>&1 \
|
||||
&& (cd ${NVS_HOME} && git remote get-url origin && echo $(git log -n 1 --pretty=format:%H -- .)) > ${NVS_HOME}/.git-remote-and-commit \
|
||||
&& sudo -u ${USERNAME} bash ${NVS_HOME}/nvs.sh install \
|
||||
&& rm ${NVS_HOME}/cache/* \
|
||||
# Set npm global location
|
||||
&& sudo -u ${USERNAME} npm config set prefix ${NPM_GLOBAL} \
|
||||
&& npm config -g set prefix ${NPM_GLOBAL} \
|
||||
# Clean up
|
||||
&& rm -rf ${NVM_DIR}/.git ${NVS_HOME}/.git
|
||||
|
||||
# Install Go
|
||||
RUN bash /tmp/scripts/go-debian.sh "${GO_VERSION}" "${GOROOT}" "${GOPATH}" "${USERNAME}"
|
||||
|
||||
# Install Replicated Tools
|
||||
RUN bash /tmp/scripts/replicated-debian.sh \
|
||||
&& rm -rf /tmp/scripts \
|
||||
&& apt-get clean -y
|
||||
|
||||
# Userspace
|
||||
ENV SHELL=/bin/zsh
|
||||
USER ${USERNAME}
|
||||
COPY --chown=${USERNAME}:root library-scripts/replicated-userspace.sh /tmp/scripts/
|
||||
RUN bash /usr/local/share/docker-init.sh \
|
||||
&& bash /tmp/scripts/replicated-userspace.sh \
|
||||
&& rm -rf /tmp/scripts/scripts
|
||||
|
||||
# Fire Docker/Moby script if needed along with Oryx's benv
|
||||
ENTRYPOINT [ "/usr/local/share/docker-init.sh", "/usr/local/share/ssh-init.sh", "benv" ]
|
||||
CMD [ "sleep", "infinity" ]
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# Replicated KOTS Codespace Container
|
||||
|
||||
Most of the code here is borrowed from this [Microsoft repo of base images](https://github.com/microsoft/vscode-dev-containers), except for replicated specific things.
|
||||
|
||||
## Notes
|
||||
* k3d *DOES NOT* work with DinD. You have to use the docker with docker install instead.
|
||||
* Might be faster to install kubectl plugins on the `$PATH` in the `Dockerfile` instead of downloading them `onCreate.sh`.
|
||||
@@ -0,0 +1,10 @@
|
||||
apiVersion: k3d.io/v1alpha3
|
||||
kind: Simple
|
||||
name: replicated
|
||||
servers: 1
|
||||
image: rancher/k3s:v1.21.4-k3s1 # v1.21.3-k3s1 default is broken
|
||||
registries:
|
||||
create:
|
||||
name: k3d-replicated-registry.localhost
|
||||
host: "0.0.0.0"
|
||||
hostPort: "5000"
|
||||
@@ -0,0 +1,63 @@
|
||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
|
||||
// https://github.com/microsoft/vscode-dev-containers/tree/v0.162.0/containers/javascript-node
|
||||
{
|
||||
"name": "Replicated Codeserver",
|
||||
"build": {
|
||||
"dockerfile": "Dockerfile",
|
||||
"args": {
|
||||
"GO_VERSION": "1.17",
|
||||
}
|
||||
},
|
||||
|
||||
// Set *default* container specific settings.json values on container create.
|
||||
"settings": {
|
||||
"terminal.integrated.shell.linux": "/usr/bin/zsh",
|
||||
"go.toolsManagement.checkForUpdates": "local",
|
||||
"go.useLanguageServer": true,
|
||||
"go.gopath": "/go",
|
||||
"go.goroot": "/usr/local/go",
|
||||
"python.pythonPath": "/opt/python/latest/bin/python",
|
||||
"python.linting.enabled": true,
|
||||
"python.linting.pylintEnabled": true,
|
||||
"python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8",
|
||||
"python.formatting.blackPath": "/usr/local/py-utils/bin/black",
|
||||
"python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf",
|
||||
"python.linting.banditPath": "/usr/local/py-utils/bin/bandit",
|
||||
"python.linting.flake8Path": "/usr/local/py-utils/bin/flake8",
|
||||
"python.linting.mypyPath": "/usr/local/py-utils/bin/mypy",
|
||||
"python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle",
|
||||
"python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle",
|
||||
"python.linting.pylintPath": "/usr/local/py-utils/bin/pylint",
|
||||
"lldb.executable": "/usr/bin/lldb",
|
||||
"files.watcherExclude": {
|
||||
"**/target/**": true
|
||||
}
|
||||
},
|
||||
"remoteUser": "codespace",
|
||||
"overrideCommand": false,
|
||||
"runArgs": [
|
||||
"--privileged",
|
||||
"--init"
|
||||
],
|
||||
"mounts": [
|
||||
"source=/var/run/docker.sock,target=/var/run/docker-host.sock,type=bind",
|
||||
],
|
||||
// Add the IDs of extensions you want installed when the container is created.
|
||||
"extensions": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"GitHub.vscode-pull-request-github",
|
||||
"golang.go",
|
||||
"github.copilot",
|
||||
"lizebang.bash-extension-pack",
|
||||
"streetsidesoftware.code-spell-checker",
|
||||
],
|
||||
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
"postCreateCommand": "bash /var/lib/replicated/scripts/onCreate.sh",
|
||||
|
||||
// Use 'postStartCommand' to run commands after the container is created like starting minikube.
|
||||
"postStartCommand": "bash /var/lib/replicated/scripts/onStart.sh",
|
||||
|
||||
// Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
|
||||
// "remoteUser": "node"
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
👋 Welcome to your Replicated Codespace!
|
||||
|
||||
There's a local Kubernetes cluster set up for you.
|
||||
|
||||
Drivers Manual:
|
||||
* `k` alias is available for `kubectl` with auto-completion for your pleasure
|
||||
* This is a `zsh` terminal with Oh My Zsh installed. Just thought you should know.
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env bash
|
||||
#-------------------------------------------------------------------------------------------------------------
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
|
||||
#-------------------------------------------------------------------------------------------------------------
|
||||
#
|
||||
# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/azcli.md
|
||||
# Maintainer: The VS Code and Codespaces Teams
|
||||
#
|
||||
# Syntax: ./azcli-debian.sh
|
||||
|
||||
set -e
|
||||
|
||||
MICROSOFT_GPG_KEYS_URI="https://packages.microsoft.com/keys/microsoft.asc"
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get central common setting
|
||||
get_common_setting() {
|
||||
if [ "${common_settings_file_loaded}" != "true" ]; then
|
||||
curl -sfL "https://aka.ms/vscode-dev-containers/script-library/settings.env" 2>/dev/null -o /tmp/vsdc-settings.env || echo "Could not download settings file. Skipping."
|
||||
common_settings_file_loaded=true
|
||||
fi
|
||||
if [ -f "/tmp/vsdc-settings.env" ]; then
|
||||
local multi_line=""
|
||||
if [ "$2" = "true" ]; then multi_line="-z"; fi
|
||||
local result="$(grep ${multi_line} -oP "$1=\"?\K[^\"]+" /tmp/vsdc-settings.env | tr -d '\0')"
|
||||
if [ ! -z "${result}" ]; then declare -g $1="${result}"; fi
|
||||
fi
|
||||
echo "$1=${!1}"
|
||||
}
|
||||
|
||||
# Function to run apt-get if needed
|
||||
apt_get_update_if_needed()
|
||||
{
|
||||
if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then
|
||||
echo "Running apt-get update..."
|
||||
apt-get update
|
||||
else
|
||||
echo "Skipping apt-get update."
|
||||
fi
|
||||
}
|
||||
|
||||
# Checks if packages are installed and installs them if not
|
||||
check_packages() {
|
||||
if ! dpkg -s "$@" > /dev/null 2>&1; then
|
||||
apt_get_update_if_needed
|
||||
apt-get -y install --no-install-recommends "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Install dependencies
|
||||
check_packages apt-transport-https curl ca-certificates lsb-release gnupg2
|
||||
|
||||
# Import key safely (new 'signed-by' method rather than deprecated apt-key approach) and install
|
||||
. /etc/os-release
|
||||
get_common_setting MICROSOFT_GPG_KEYS_URI
|
||||
curl -sSL ${MICROSOFT_GPG_KEYS_URI} | gpg --dearmor > /usr/share/keyrings/microsoft-archive-keyring.gpg
|
||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/microsoft-archive-keyring.gpg] https://packages.microsoft.com/repos/azure-cli/ ${VERSION_CODENAME} main" > /etc/apt/sources.list.d/azure-cli.list
|
||||
apt-get update
|
||||
apt-get install -y azure-cli
|
||||
echo "Done!"
|
||||
@@ -0,0 +1,478 @@
|
||||
#!/usr/bin/env bash
|
||||
#-------------------------------------------------------------------------------------------------------------
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
|
||||
#-------------------------------------------------------------------------------------------------------------
|
||||
#
|
||||
# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/common.md
|
||||
# Maintainer: The VS Code and Codespaces Teams
|
||||
#
|
||||
# Syntax: ./common-debian.sh [install zsh flag] [username] [user UID] [user GID] [upgrade packages flag] [install Oh My Zsh! flag] [Add non-free packages]
|
||||
|
||||
set -e
|
||||
|
||||
INSTALL_ZSH=${1:-"true"}
|
||||
USERNAME=${2:-"automatic"}
|
||||
USER_UID=${3:-"automatic"}
|
||||
USER_GID=${4:-"automatic"}
|
||||
UPGRADE_PACKAGES=${5:-"true"}
|
||||
INSTALL_OH_MYS=${6:-"true"}
|
||||
ADD_NON_FREE_PACKAGES=${7:-"false"}
|
||||
SCRIPT_DIR="$(cd $(dirname "${BASH_SOURCE[0]}") && pwd)"
|
||||
MARKER_FILE="/usr/local/etc/vscode-dev-containers/common"
|
||||
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Ensure that login shells get the correct path if the user updated the PATH using ENV.
|
||||
rm -f /etc/profile.d/00-restore-env.sh
|
||||
echo "export PATH=${PATH//$(sh -lc 'echo $PATH')/\$PATH}" > /etc/profile.d/00-restore-env.sh
|
||||
chmod +x /etc/profile.d/00-restore-env.sh
|
||||
|
||||
# If in automatic mode, determine if a user already exists, if not use vscode
|
||||
if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then
|
||||
USERNAME=""
|
||||
POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)")
|
||||
for CURRENT_USER in ${POSSIBLE_USERS[@]}; do
|
||||
if id -u ${CURRENT_USER} > /dev/null 2>&1; then
|
||||
USERNAME=${CURRENT_USER}
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ "${USERNAME}" = "" ]; then
|
||||
USERNAME=vscode
|
||||
fi
|
||||
elif [ "${USERNAME}" = "none" ]; then
|
||||
USERNAME=root
|
||||
USER_UID=0
|
||||
USER_GID=0
|
||||
fi
|
||||
|
||||
# Load markers to see which steps have already run
|
||||
if [ -f "${MARKER_FILE}" ]; then
|
||||
echo "Marker file found:"
|
||||
cat "${MARKER_FILE}"
|
||||
source "${MARKER_FILE}"
|
||||
fi
|
||||
|
||||
# Ensure apt is in non-interactive to avoid prompts
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Function to call apt-get if needed
|
||||
apt_get_update_if_needed()
|
||||
{
|
||||
if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then
|
||||
echo "Running apt-get update..."
|
||||
apt-get update
|
||||
else
|
||||
echo "Skipping apt-get update."
|
||||
fi
|
||||
}
|
||||
|
||||
# Run install apt-utils to avoid debconf warning then verify presence of other common developer tools and dependencies
|
||||
if [ "${PACKAGES_ALREADY_INSTALLED}" != "true" ]; then
|
||||
|
||||
package_list="apt-utils \
|
||||
openssh-client \
|
||||
gnupg2 \
|
||||
iproute2 \
|
||||
procps \
|
||||
lsof \
|
||||
htop \
|
||||
net-tools \
|
||||
psmisc \
|
||||
curl \
|
||||
wget \
|
||||
rsync \
|
||||
ca-certificates \
|
||||
unzip \
|
||||
zip \
|
||||
nano \
|
||||
vim-tiny \
|
||||
less \
|
||||
jq \
|
||||
lsb-release \
|
||||
apt-transport-https \
|
||||
dialog \
|
||||
libc6 \
|
||||
libgcc1 \
|
||||
libkrb5-3 \
|
||||
libgssapi-krb5-2 \
|
||||
libicu[0-9][0-9] \
|
||||
liblttng-ust0 \
|
||||
libstdc++6 \
|
||||
zlib1g \
|
||||
locales \
|
||||
sudo \
|
||||
ncdu \
|
||||
man-db \
|
||||
strace \
|
||||
manpages \
|
||||
manpages-dev \
|
||||
init-system-helpers"
|
||||
|
||||
# Needed for adding manpages-posix and manpages-posix-dev which are non-free packages in Debian
|
||||
if [ "${ADD_NON_FREE_PACKAGES}" = "true" ]; then
|
||||
# Bring in variables from /etc/os-release like VERSION_CODENAME
|
||||
. /etc/os-release
|
||||
sed -i -E "s/deb http:\/\/(deb|httpredir)\.debian\.org\/debian ${VERSION_CODENAME} main/deb http:\/\/\1\.debian\.org\/debian ${VERSION_CODENAME} main contrib non-free/" /etc/apt/sources.list
|
||||
sed -i -E "s/deb-src http:\/\/(deb|httredir)\.debian\.org\/debian ${VERSION_CODENAME} main/deb http:\/\/\1\.debian\.org\/debian ${VERSION_CODENAME} main contrib non-free/" /etc/apt/sources.list
|
||||
sed -i -E "s/deb http:\/\/(deb|httpredir)\.debian\.org\/debian ${VERSION_CODENAME}-updates main/deb http:\/\/\1\.debian\.org\/debian ${VERSION_CODENAME}-updates main contrib non-free/" /etc/apt/sources.list
|
||||
sed -i -E "s/deb-src http:\/\/(deb|httpredir)\.debian\.org\/debian ${VERSION_CODENAME}-updates main/deb http:\/\/\1\.debian\.org\/debian ${VERSION_CODENAME}-updates main contrib non-free/" /etc/apt/sources.list
|
||||
sed -i "s/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}\/updates main/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}\/updates main contrib non-free/" /etc/apt/sources.list
|
||||
sed -i "s/deb-src http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}\/updates main/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}\/updates main contrib non-free/" /etc/apt/sources.list
|
||||
sed -i "s/deb http:\/\/deb\.debian\.org\/debian ${VERSION_CODENAME}-backports main/deb http:\/\/deb\.debian\.org\/debian ${VERSION_CODENAME}-backports main contrib non-free/" /etc/apt/sources.list
|
||||
sed -i "s/deb-src http:\/\/deb\.debian\.org\/debian ${VERSION_CODENAME}-backports main/deb http:\/\/deb\.debian\.org\/debian ${VERSION_CODENAME}-backports main contrib non-free/" /etc/apt/sources.list
|
||||
echo "Running apt-get update..."
|
||||
apt-get update
|
||||
package_list="${package_list} manpages-posix manpages-posix-dev"
|
||||
else
|
||||
apt_get_update_if_needed
|
||||
fi
|
||||
|
||||
# Install libssl1.1 if available
|
||||
if [[ ! -z $(apt-cache --names-only search ^libssl1.1$) ]]; then
|
||||
package_list="${package_list} libssl1.1"
|
||||
fi
|
||||
|
||||
# Install appropriate version of libssl1.0.x if available
|
||||
libssl_package=$(dpkg-query -f '${db:Status-Abbrev}\t${binary:Package}\n' -W 'libssl1\.0\.?' 2>&1 || echo '')
|
||||
if [ "$(echo "$LIlibssl_packageBSSL" | grep -o 'libssl1\.0\.[0-9]:' | uniq | sort | wc -l)" -eq 0 ]; then
|
||||
if [[ ! -z $(apt-cache --names-only search ^libssl1.0.2$) ]]; then
|
||||
# Debian 9
|
||||
package_list="${package_list} libssl1.0.2"
|
||||
elif [[ ! -z $(apt-cache --names-only search ^libssl1.0.0$) ]]; then
|
||||
# Ubuntu 18.04, 16.04, earlier
|
||||
package_list="${package_list} libssl1.0.0"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Packages to verify are installed: ${package_list}"
|
||||
apt-get -y install --no-install-recommends ${package_list} 2> >( grep -v 'debconf: delaying package configuration, since apt-utils is not installed' >&2 )
|
||||
|
||||
# Install git if not already installed (may be more recent than distro version)
|
||||
if ! type git > /dev/null 2>&1; then
|
||||
apt-get -y install --no-install-recommends git
|
||||
fi
|
||||
|
||||
PACKAGES_ALREADY_INSTALLED="true"
|
||||
fi
|
||||
|
||||
# Get to latest versions of all packages
|
||||
if [ "${UPGRADE_PACKAGES}" = "true" ]; then
|
||||
apt_get_update_if_needed
|
||||
apt-get -y upgrade --no-install-recommends
|
||||
apt-get autoremove -y
|
||||
fi
|
||||
|
||||
# Ensure at least the en_US.UTF-8 UTF-8 locale is available.
|
||||
# Common need for both applications and things like the agnoster ZSH theme.
|
||||
if [ "${LOCALE_ALREADY_SET}" != "true" ] && ! grep -o -E '^\s*en_US.UTF-8\s+UTF-8' /etc/locale.gen > /dev/null; then
|
||||
echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen
|
||||
locale-gen
|
||||
LOCALE_ALREADY_SET="true"
|
||||
fi
|
||||
|
||||
# Create or update a non-root user to match UID/GID.
|
||||
if id -u ${USERNAME} > /dev/null 2>&1; then
|
||||
# User exists, update if needed
|
||||
if [ "${USER_GID}" != "automatic" ] && [ "$USER_GID" != "$(id -G $USERNAME)" ]; then
|
||||
groupmod --gid $USER_GID $USERNAME
|
||||
usermod --gid $USER_GID $USERNAME
|
||||
fi
|
||||
if [ "${USER_UID}" != "automatic" ] && [ "$USER_UID" != "$(id -u $USERNAME)" ]; then
|
||||
usermod --uid $USER_UID $USERNAME
|
||||
fi
|
||||
else
|
||||
# Create user
|
||||
if [ "${USER_GID}" = "automatic" ]; then
|
||||
groupadd $USERNAME
|
||||
else
|
||||
groupadd --gid $USER_GID $USERNAME
|
||||
fi
|
||||
if [ "${USER_UID}" = "automatic" ]; then
|
||||
useradd -s /bin/bash --gid $USERNAME -m $USERNAME
|
||||
else
|
||||
useradd -s /bin/bash --uid $USER_UID --gid $USERNAME -m $USERNAME
|
||||
fi
|
||||
fi
|
||||
|
||||
# Add add sudo support for non-root user
|
||||
if [ "${USERNAME}" != "root" ] && [ "${EXISTING_NON_ROOT_USER}" != "${USERNAME}" ]; then
|
||||
echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME
|
||||
chmod 0440 /etc/sudoers.d/$USERNAME
|
||||
EXISTING_NON_ROOT_USER="${USERNAME}"
|
||||
fi
|
||||
|
||||
# ** Shell customization section **
|
||||
if [ "${USERNAME}" = "root" ]; then
|
||||
user_rc_path="/root"
|
||||
else
|
||||
user_rc_path="/home/${USERNAME}"
|
||||
fi
|
||||
|
||||
# Restore user .bashrc defaults from skeleton file if it doesn't exist or is empty
|
||||
if [ ! -f "${user_rc_path}/.bashrc" ] || [ ! -s "${user_rc_path}/.bashrc" ] ; then
|
||||
cp /etc/skel/.bashrc "${user_rc_path}/.bashrc"
|
||||
fi
|
||||
|
||||
# Restore user .profile defaults from skeleton file if it doesn't exist or is empty
|
||||
if [ ! -f "${user_rc_path}/.profile" ] || [ ! -s "${user_rc_path}/.profile" ] ; then
|
||||
cp /etc/skel/.profile "${user_rc_path}/.profile"
|
||||
fi
|
||||
|
||||
# .bashrc/.zshrc snippet
|
||||
rc_snippet="$(cat << 'EOF'
|
||||
|
||||
if [ -z "${USER}" ]; then export USER=$(whoami); fi
|
||||
if [[ "${PATH}" != *"$HOME/.local/bin"* ]]; then export PATH="${PATH}:$HOME/.local/bin"; fi
|
||||
|
||||
# Display optional first run image specific notice if configured and terminal is interactive
|
||||
if [ -t 1 ] && [[ "${TERM_PROGRAM}" = "vscode" || "${TERM_PROGRAM}" = "codespaces" ]] && [ ! -f "$HOME/.config/vscode-dev-containers/first-run-notice-already-displayed" ]; then
|
||||
if [ -f "/usr/local/etc/vscode-dev-containers/first-run-notice.txt" ]; then
|
||||
cat "/usr/local/etc/vscode-dev-containers/first-run-notice.txt"
|
||||
elif [ -f "/workspaces/.codespaces/shared/first-run-notice.txt" ]; then
|
||||
cat "/workspaces/.codespaces/shared/first-run-notice.txt"
|
||||
fi
|
||||
mkdir -p "$HOME/.config/vscode-dev-containers"
|
||||
# Mark first run notice as displayed after 10s to avoid problems with fast terminal refreshes hiding it
|
||||
((sleep 10s; touch "$HOME/.config/vscode-dev-containers/first-run-notice-already-displayed") &)
|
||||
fi
|
||||
|
||||
# Set the default git editor if not already set
|
||||
if [ -z "$(git config --get core.editor)" ] && [ -z "${GIT_EDITOR}" ]; then
|
||||
if [ "${TERM_PROGRAM}" = "vscode" ]; then
|
||||
if [[ -n $(command -v code-insiders) && -z $(command -v code) ]]; then
|
||||
export GIT_EDITOR="code-insiders --wait"
|
||||
else
|
||||
export GIT_EDITOR="code --wait"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
EOF
|
||||
)"
|
||||
|
||||
# code shim, it fallbacks to code-insiders if code is not available
|
||||
cat << 'EOF' > /usr/local/bin/code
|
||||
#!/bin/sh
|
||||
|
||||
get_in_path_except_current() {
|
||||
which -a "$1" | grep -A1 "$0" | grep -v "$0"
|
||||
}
|
||||
|
||||
code="$(get_in_path_except_current code)"
|
||||
|
||||
if [ -n "$code" ]; then
|
||||
exec "$code" "$@"
|
||||
elif [ "$(command -v code-insiders)" ]; then
|
||||
exec code-insiders "$@"
|
||||
else
|
||||
echo "code or code-insiders is not installed" >&2
|
||||
exit 127
|
||||
fi
|
||||
EOF
|
||||
chmod +x /usr/local/bin/code
|
||||
|
||||
# systemctl shim - tells people to use 'service' if systemd is not running
|
||||
cat << 'EOF' > /usr/local/bin/systemctl
|
||||
#!/bin/sh
|
||||
set -e
|
||||
if [ -d "/run/systemd/system" ]; then
|
||||
exec /bin/systemctl/systemctl "$@"
|
||||
else
|
||||
echo '\n"systemd" is not running in this container due to its overhead.\nUse the "service" command to start services intead. e.g.: \n\nservice --status-all'
|
||||
fi
|
||||
EOF
|
||||
chmod +x /usr/local/bin/systemctl
|
||||
|
||||
# Codespaces bash and OMZ themes - partly inspired by https://github.com/ohmyzsh/ohmyzsh/blob/master/themes/robbyrussell.zsh-theme
|
||||
codespaces_bash="$(cat \
|
||||
<<'EOF'
|
||||
|
||||
# Codespaces bash prompt theme
|
||||
__bash_prompt() {
|
||||
local userpart='`export XIT=$? \
|
||||
&& [ ! -z "${GITHUB_USER}" ] && echo -n "\[\033[0;32m\]@${GITHUB_USER} " || echo -n "\[\033[0;32m\]\u " \
|
||||
&& [ "$XIT" -ne "0" ] && echo -n "\[\033[1;31m\]➜" || echo -n "\[\033[0m\]➜"`'
|
||||
local gitbranch='`\
|
||||
export BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || git rev-parse --short HEAD 2>/dev/null); \
|
||||
if [ "${BRANCH}" != "" ]; then \
|
||||
echo -n "\[\033[0;36m\](\[\033[1;31m\]${BRANCH}" \
|
||||
&& if git ls-files --error-unmatch -m --directory --no-empty-directory -o --exclude-standard ":/*" > /dev/null 2>&1; then \
|
||||
echo -n " \[\033[1;33m\]✗"; \
|
||||
fi \
|
||||
&& echo -n "\[\033[0;36m\]) "; \
|
||||
fi`'
|
||||
local lightblue='\[\033[1;34m\]'
|
||||
local removecolor='\[\033[0m\]'
|
||||
PS1="${userpart} ${lightblue}\w ${gitbranch}${removecolor}\$ "
|
||||
unset -f __bash_prompt
|
||||
}
|
||||
__bash_prompt
|
||||
|
||||
EOF
|
||||
)"
|
||||
|
||||
codespaces_zsh="$(cat \
|
||||
<<'EOF'
|
||||
# Codespaces zsh prompt theme
|
||||
__zsh_prompt() {
|
||||
local prompt_username
|
||||
if [ ! -z "${GITHUB_USER}" ]; then
|
||||
prompt_username="@${GITHUB_USER}"
|
||||
else
|
||||
prompt_username="%n"
|
||||
fi
|
||||
PROMPT="%{$fg[green]%}${prompt_username} %(?:%{$reset_color%}➜ :%{$fg_bold[red]%}➜ )" # User/exit code arrow
|
||||
PROMPT+='%{$fg_bold[blue]%}%(5~|%-1~/…/%3~|%4~)%{$reset_color%} ' # cwd
|
||||
PROMPT+='$(git_prompt_info)%{$fg[white]%}$ %{$reset_color%}' # Git status
|
||||
unset -f __zsh_prompt
|
||||
}
|
||||
ZSH_THEME_GIT_PROMPT_PREFIX="%{$fg_bold[cyan]%}(%{$fg_bold[red]%}"
|
||||
ZSH_THEME_GIT_PROMPT_SUFFIX="%{$reset_color%} "
|
||||
ZSH_THEME_GIT_PROMPT_DIRTY=" %{$fg_bold[yellow]%}✗%{$fg_bold[cyan]%})"
|
||||
ZSH_THEME_GIT_PROMPT_CLEAN="%{$fg_bold[cyan]%})"
|
||||
__zsh_prompt
|
||||
|
||||
EOF
|
||||
)"
|
||||
|
||||
# Add notice that Oh My Bash! has been removed from images and how to provide information on how to install manually
|
||||
omb_readme="$(cat \
|
||||
<<'EOF'
|
||||
"Oh My Bash!" has been removed from this image in favor of a simple shell prompt. If you
|
||||
still wish to use it, remove "~/.oh-my-bash" and install it from: https://github.com/ohmybash/oh-my-bash
|
||||
You may also want to consider "Bash-it" as an alternative: https://github.com/bash-it/bash-it
|
||||
See here for infomation on adding it to your image or dotfiles: https://aka.ms/codespaces/omb-remove
|
||||
EOF
|
||||
)"
|
||||
omb_stub="$(cat \
|
||||
<<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
if [ -t 1 ]; then
|
||||
cat $HOME/.oh-my-bash/README.md
|
||||
fi
|
||||
EOF
|
||||
)"
|
||||
|
||||
# Add RC snippet and custom bash prompt
|
||||
if [ "${RC_SNIPPET_ALREADY_ADDED}" != "true" ]; then
|
||||
echo "${rc_snippet}" >> /etc/bash.bashrc
|
||||
echo "${codespaces_bash}" >> "${user_rc_path}/.bashrc"
|
||||
echo 'export PROMPT_DIRTRIM=4' >> "${user_rc_path}/.bashrc"
|
||||
if [ "${USERNAME}" != "root" ]; then
|
||||
echo "${codespaces_bash}" >> "/root/.bashrc"
|
||||
echo 'export PROMPT_DIRTRIM=4' >> "/root/.bashrc"
|
||||
fi
|
||||
chown ${USERNAME}:${USERNAME} "${user_rc_path}/.bashrc"
|
||||
RC_SNIPPET_ALREADY_ADDED="true"
|
||||
fi
|
||||
|
||||
# Add stub for Oh My Bash!
|
||||
if [ ! -d "${user_rc_path}/.oh-my-bash}" ] && [ "${INSTALL_OH_MYS}" = "true" ]; then
|
||||
mkdir -p "${user_rc_path}/.oh-my-bash" "/root/.oh-my-bash"
|
||||
echo "${omb_readme}" >> "${user_rc_path}/.oh-my-bash/README.md"
|
||||
echo "${omb_stub}" >> "${user_rc_path}/.oh-my-bash/oh-my-bash.sh"
|
||||
chmod +x "${user_rc_path}/.oh-my-bash/oh-my-bash.sh"
|
||||
if [ "${USERNAME}" != "root" ]; then
|
||||
echo "${omb_readme}" >> "/root/.oh-my-bash/README.md"
|
||||
echo "${omb_stub}" >> "/root/.oh-my-bash/oh-my-bash.sh"
|
||||
chmod +x "/root/.oh-my-bash/oh-my-bash.sh"
|
||||
fi
|
||||
chown -R "${USERNAME}:${USERNAME}" "${user_rc_path}/.oh-my-bash"
|
||||
fi
|
||||
|
||||
# Optionally install and configure zsh and Oh My Zsh!
|
||||
if [ "${INSTALL_ZSH}" = "true" ]; then
|
||||
if ! type zsh > /dev/null 2>&1; then
|
||||
apt_get_update_if_needed
|
||||
apt-get install -y zsh
|
||||
fi
|
||||
if [ "${ZSH_ALREADY_INSTALLED}" != "true" ]; then
|
||||
echo "${rc_snippet}" >> /etc/zsh/zshrc
|
||||
ZSH_ALREADY_INSTALLED="true"
|
||||
fi
|
||||
|
||||
# Adapted, simplified inline Oh My Zsh! install steps that adds, defaults to a codespaces theme.
|
||||
# See https://github.com/ohmyzsh/ohmyzsh/blob/master/tools/install.sh for official script.
|
||||
oh_my_install_dir="${user_rc_path}/.oh-my-zsh"
|
||||
if [ ! -d "${oh_my_install_dir}" ] && [ "${INSTALL_OH_MYS}" = "true" ]; then
|
||||
template_path="${oh_my_install_dir}/templates/zshrc.zsh-template"
|
||||
user_rc_file="${user_rc_path}/.zshrc"
|
||||
umask g-w,o-w
|
||||
mkdir -p ${oh_my_install_dir}
|
||||
git clone --depth=1 \
|
||||
-c core.eol=lf \
|
||||
-c core.autocrlf=false \
|
||||
-c fsck.zeroPaddedFilemode=ignore \
|
||||
-c fetch.fsck.zeroPaddedFilemode=ignore \
|
||||
-c receive.fsck.zeroPaddedFilemode=ignore \
|
||||
"https://github.com/ohmyzsh/ohmyzsh" "${oh_my_install_dir}" 2>&1
|
||||
echo -e "$(cat "${template_path}")\nDISABLE_AUTO_UPDATE=true\nDISABLE_UPDATE_PROMPT=true" > ${user_rc_file}
|
||||
sed -i -e 's/ZSH_THEME=.*/ZSH_THEME="codespaces"/g' ${user_rc_file}
|
||||
|
||||
mkdir -p ${oh_my_install_dir}/custom/themes
|
||||
echo "${codespaces_zsh}" > "${oh_my_install_dir}/custom/themes/codespaces.zsh-theme"
|
||||
# Shrink git while still enabling updates
|
||||
cd "${oh_my_install_dir}"
|
||||
git repack -a -d -f --depth=1 --window=1
|
||||
# Copy to non-root user if one is specified
|
||||
if [ "${USERNAME}" != "root" ]; then
|
||||
cp -rf "${user_rc_file}" "${oh_my_install_dir}" /root
|
||||
chown -R ${USERNAME}:${USERNAME} "${user_rc_path}"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Persist image metadata info, script if meta.env found in same directory
|
||||
meta_info_script="$(cat << 'EOF'
|
||||
#!/bin/sh
|
||||
. /usr/local/etc/vscode-dev-containers/meta.env
|
||||
|
||||
# Minimal output
|
||||
if [ "$1" = "version" ] || [ "$1" = "image-version" ]; then
|
||||
echo "${VERSION}"
|
||||
exit 0
|
||||
elif [ "$1" = "release" ]; then
|
||||
echo "${GIT_REPOSITORY_RELEASE}"
|
||||
exit 0
|
||||
elif [ "$1" = "content" ] || [ "$1" = "content-url" ] || [ "$1" = "contents" ] || [ "$1" = "contents-url" ]; then
|
||||
echo "${CONTENTS_URL}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
#Full output
|
||||
echo
|
||||
echo "Development container image information"
|
||||
echo
|
||||
if [ ! -z "${VERSION}" ]; then echo "- Image version: ${VERSION}"; fi
|
||||
if [ ! -z "${DEFINITION_ID}" ]; then echo "- Definition ID: ${DEFINITION_ID}"; fi
|
||||
if [ ! -z "${VARIANT}" ]; then echo "- Variant: ${VARIANT}"; fi
|
||||
if [ ! -z "${GIT_REPOSITORY}" ]; then echo "- Source code repository: ${GIT_REPOSITORY}"; fi
|
||||
if [ ! -z "${GIT_REPOSITORY_RELEASE}" ]; then echo "- Source code release/branch: ${GIT_REPOSITORY_RELEASE}"; fi
|
||||
if [ ! -z "${BUILD_TIMESTAMP}" ]; then echo "- Timestamp: ${BUILD_TIMESTAMP}"; fi
|
||||
if [ ! -z "${CONTENTS_URL}" ]; then echo && echo "More info: ${CONTENTS_URL}"; fi
|
||||
echo
|
||||
EOF
|
||||
)"
|
||||
if [ -f "${SCRIPT_DIR}/meta.env" ]; then
|
||||
mkdir -p /usr/local/etc/vscode-dev-containers/
|
||||
cp -f "${SCRIPT_DIR}/meta.env" /usr/local/etc/vscode-dev-containers/meta.env
|
||||
echo "${meta_info_script}" > /usr/local/bin/devcontainer-info
|
||||
chmod +x /usr/local/bin/devcontainer-info
|
||||
fi
|
||||
|
||||
# Write marker file
|
||||
mkdir -p "$(dirname "${MARKER_FILE}")"
|
||||
echo -e "\
|
||||
PACKAGES_ALREADY_INSTALLED=${PACKAGES_ALREADY_INSTALLED}\n\
|
||||
LOCALE_ALREADY_SET=${LOCALE_ALREADY_SET}\n\
|
||||
EXISTING_NON_ROOT_USER=${EXISTING_NON_ROOT_USER}\n\
|
||||
RC_SNIPPET_ALREADY_ADDED=${RC_SNIPPET_ALREADY_ADDED}\n\
|
||||
ZSH_ALREADY_INSTALLED=${ZSH_ALREADY_INSTALLED}" > "${MARKER_FILE}"
|
||||
|
||||
echo "Done!"
|
||||
@@ -0,0 +1,224 @@
|
||||
#!/usr/bin/env bash
|
||||
#-------------------------------------------------------------------------------------------------------------
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
|
||||
#-------------------------------------------------------------------------------------------------------------
|
||||
#
|
||||
# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/docker.md
|
||||
# Maintainer: The VS Code and Codespaces Teams
|
||||
#
|
||||
# Syntax: ./docker-debian.sh [enable non-root docker socket access flag] [source socket] [target socket] [non-root user] [use moby]
|
||||
|
||||
ENABLE_NONROOT_DOCKER=${1:-"true"}
|
||||
SOURCE_SOCKET=${2:-"/var/run/docker-host.sock"}
|
||||
TARGET_SOCKET=${3:-"/var/run/docker.sock"}
|
||||
USERNAME=${4:-"automatic"}
|
||||
USE_MOBY=${5:-"true"}
|
||||
MICROSOFT_GPG_KEYS_URI="https://packages.microsoft.com/keys/microsoft.asc"
|
||||
|
||||
set -e
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Determine the appropriate non-root user
|
||||
if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then
|
||||
USERNAME=""
|
||||
POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)")
|
||||
for CURRENT_USER in ${POSSIBLE_USERS[@]}; do
|
||||
if id -u ${CURRENT_USER} > /dev/null 2>&1; then
|
||||
USERNAME=${CURRENT_USER}
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ "${USERNAME}" = "" ]; then
|
||||
USERNAME=root
|
||||
fi
|
||||
elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then
|
||||
USERNAME=root
|
||||
fi
|
||||
|
||||
# Get central common setting
|
||||
get_common_setting() {
|
||||
if [ "${common_settings_file_loaded}" != "true" ]; then
|
||||
curl -sfL "https://aka.ms/vscode-dev-containers/script-library/settings.env" 2>/dev/null -o /tmp/vsdc-settings.env || echo "Could not download settings file. Skipping."
|
||||
common_settings_file_loaded=true
|
||||
fi
|
||||
if [ -f "/tmp/vsdc-settings.env" ]; then
|
||||
local multi_line=""
|
||||
if [ "$2" = "true" ]; then multi_line="-z"; fi
|
||||
local result="$(grep ${multi_line} -oP "$1=\"?\K[^\"]+" /tmp/vsdc-settings.env | tr -d '\0')"
|
||||
if [ ! -z "${result}" ]; then declare -g $1="${result}"; fi
|
||||
fi
|
||||
echo "$1=${!1}"
|
||||
}
|
||||
|
||||
# Function to run apt-get if needed
|
||||
apt_get_update_if_needed()
|
||||
{
|
||||
if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then
|
||||
echo "Running apt-get update..."
|
||||
apt-get update
|
||||
else
|
||||
echo "Skipping apt-get update."
|
||||
fi
|
||||
}
|
||||
|
||||
# Checks if packages are installed and installs them if not
|
||||
check_packages() {
|
||||
if ! dpkg -s "$@" > /dev/null 2>&1; then
|
||||
apt_get_update_if_needed
|
||||
apt-get -y install --no-install-recommends "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
# Ensure apt is in non-interactive to avoid prompts
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Install dependencies
|
||||
check_packages apt-transport-https curl ca-certificates gnupg2
|
||||
|
||||
# Install Docker / Moby CLI if not already installed
|
||||
if type docker > /dev/null 2>&1; then
|
||||
echo "Docker / Moby CLI already installed."
|
||||
else
|
||||
# Source /etc/os-release to get OS info
|
||||
. /etc/os-release
|
||||
if [ "${USE_MOBY}" = "true" ]; then
|
||||
# Import key safely (new 'signed-by' method rather than deprecated apt-key approach) and install
|
||||
get_common_setting MICROSOFT_GPG_KEYS_URI
|
||||
curl -sSL ${MICROSOFT_GPG_KEYS_URI} | gpg --dearmor > /usr/share/keyrings/microsoft-archive-keyring.gpg
|
||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/microsoft-archive-keyring.gpg] https://packages.microsoft.com/repos/microsoft-${ID}-${VERSION_CODENAME}-prod ${VERSION_CODENAME} main" > /etc/apt/sources.list.d/microsoft.list
|
||||
apt-get update
|
||||
apt-get -y install --no-install-recommends moby-cli moby-buildx moby-compose
|
||||
else
|
||||
# Import key safely (new 'signed-by' method rather than deprecated apt-key approach) and install
|
||||
curl -fsSL https://download.docker.com/linux/${ID}/gpg | gpg --dearmor > /usr/share/keyrings/docker-archive-keyring.gpg
|
||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/${ID} ${VERSION_CODENAME} stable" > /etc/apt/sources.list.d/docker.list
|
||||
apt-get update
|
||||
apt-get -y install --no-install-recommends docker-ce-cli
|
||||
fi
|
||||
fi
|
||||
|
||||
# Install Docker Compose if not already installed and is on a supported architecture
|
||||
if type docker-compose > /dev/null 2>&1; then
|
||||
echo "Docker Compose already installed."
|
||||
else
|
||||
TARGET_COMPOSE_ARCH="$(uname -m)"
|
||||
if [ "${TARGET_COMPOSE_ARCH}" = "amd64" ]; then
|
||||
TARGET_COMPOSE_ARCH="x86_64"
|
||||
fi
|
||||
if [ "${TARGET_COMPOSE_ARCH}" != "x86_64" ]; then
|
||||
# Use pip to get a version that runns on this architecture
|
||||
if ! dpkg -s python3-minimal python3-pip libffi-dev python3-venv pipx > /dev/null 2>&1; then
|
||||
apt_get_update_if_needed
|
||||
apt-get -y install python3-minimal python3-pip libffi-dev python3-venv pipx
|
||||
fi
|
||||
export PIPX_HOME=/usr/local/pipx
|
||||
mkdir -p ${PIPX_HOME}
|
||||
export PIPX_BIN_DIR=/usr/local/bin
|
||||
export PIP_CACHE_DIR=/tmp/pip-tmp/cache
|
||||
pipx install --system-site-packages --pip-args '--no-cache-dir --force-reinstall' docker-compose
|
||||
rm -rf /tmp/pip-tmp
|
||||
else
|
||||
LATEST_COMPOSE_VERSION=$(basename "$(curl -fsSL -o /dev/null -w "%{url_effective}" https://github.com/docker/compose/releases/latest)")
|
||||
curl -fsSL "https://github.com/docker/compose/releases/download/${LATEST_COMPOSE_VERSION}/docker-compose-$(uname -s)-${TARGET_COMPOSE_ARCH}" -o /usr/local/bin/docker-compose
|
||||
chmod +x /usr/local/bin/docker-compose
|
||||
fi
|
||||
fi
|
||||
|
||||
# If init file already exists, exit
|
||||
if [ -f "/usr/local/share/docker-init.sh" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# By default, make the source and target sockets the same
|
||||
if [ "${SOURCE_SOCKET}" != "${TARGET_SOCKET}" ]; then
|
||||
touch "${SOURCE_SOCKET}"
|
||||
ln -s "${SOURCE_SOCKET}" "${TARGET_SOCKET}"
|
||||
fi
|
||||
|
||||
# Add a stub if not adding non-root user access, user is root
|
||||
if [ "${ENABLE_NONROOT_DOCKER}" = "false" ] || [ "${USERNAME}" = "root" ]; then
|
||||
echo '/usr/bin/env bash -c "\$@"' > /usr/local/share/docker-init.sh
|
||||
chmod +x /usr/local/share/docker-init.sh
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# If enabling non-root access and specified user is found, setup socat and add script
|
||||
chown -h "${USERNAME}":root "${TARGET_SOCKET}"
|
||||
if ! dpkg -s socat > /dev/null 2>&1; then
|
||||
apt_get_update_if_needed
|
||||
apt-get -y install socat
|
||||
fi
|
||||
tee /usr/local/share/docker-init.sh > /dev/null \
|
||||
<< EOF
|
||||
#!/usr/bin/env bash
|
||||
#-------------------------------------------------------------------------------------------------------------
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
|
||||
#-------------------------------------------------------------------------------------------------------------
|
||||
|
||||
set -e
|
||||
|
||||
SOCAT_PATH_BASE=/tmp/vscr-docker-from-docker
|
||||
SOCAT_LOG=\${SOCAT_PATH_BASE}.log
|
||||
SOCAT_PID=\${SOCAT_PATH_BASE}.pid
|
||||
|
||||
# Wrapper function to only use sudo if not already root
|
||||
sudoIf()
|
||||
{
|
||||
if [ "\$(id -u)" -ne 0 ]; then
|
||||
sudo "\$@"
|
||||
else
|
||||
"\$@"
|
||||
fi
|
||||
}
|
||||
|
||||
# Log messages
|
||||
log()
|
||||
{
|
||||
echo -e "[\$(date)] \$@" | sudoIf tee -a \${SOCAT_LOG} > /dev/null
|
||||
}
|
||||
|
||||
echo -e "\n** \$(date) **" | sudoIf tee -a \${SOCAT_LOG} > /dev/null
|
||||
log "Ensuring ${USERNAME} has access to ${SOURCE_SOCKET} via ${TARGET_SOCKET}"
|
||||
|
||||
# If enabled, try to add a docker group with the right GID. If the group is root,
|
||||
# fall back on using socat to forward the docker socket to another unix socket so
|
||||
# that we can set permissions on it without affecting the host.
|
||||
if [ "${ENABLE_NONROOT_DOCKER}" = "true" ] && [ "${SOURCE_SOCKET}" != "${TARGET_SOCKET}" ] && [ "${USERNAME}" != "root" ] && [ "${USERNAME}" != "0" ]; then
|
||||
SOCKET_GID=\$(stat -c '%g' ${SOURCE_SOCKET})
|
||||
if [ "\${SOCKET_GID}" != "0" ]; then
|
||||
log "Adding user to group with GID \${SOCKET_GID}."
|
||||
if [ "\$(cat /etc/group | grep :\${SOCKET_GID}:)" = "" ]; then
|
||||
sudoIf groupadd --gid \${SOCKET_GID} docker-host
|
||||
fi
|
||||
# Add user to group if not already in it
|
||||
if [ "\$(id ${USERNAME} | grep -E "groups.*(=|,)\${SOCKET_GID}\(")" = "" ]; then
|
||||
sudoIf usermod -aG \${SOCKET_GID} ${USERNAME}
|
||||
fi
|
||||
else
|
||||
# Enable proxy if not already running
|
||||
if [ ! -f "\${SOCAT_PID}" ] || ! ps -p \$(cat \${SOCAT_PID}) > /dev/null; then
|
||||
log "Enabling socket proxy."
|
||||
log "Proxying ${SOURCE_SOCKET} to ${TARGET_SOCKET} for vscode"
|
||||
sudoIf rm -rf ${TARGET_SOCKET}
|
||||
(sudoIf socat UNIX-LISTEN:${TARGET_SOCKET},fork,mode=660,user=${USERNAME} UNIX-CONNECT:${SOURCE_SOCKET} 2>&1 | sudoIf tee -a \${SOCAT_LOG} > /dev/null & echo "\$!" | sudoIf tee \${SOCAT_PID} > /dev/null)
|
||||
else
|
||||
log "Socket proxy already running."
|
||||
fi
|
||||
fi
|
||||
log "Success"
|
||||
fi
|
||||
|
||||
# Execute whatever commands were passed in (if any). This allows us
|
||||
# to set this script to ENTRYPOINT while still executing the default CMD.
|
||||
set +e
|
||||
exec "\$@"
|
||||
EOF
|
||||
chmod +x /usr/local/share/docker-init.sh
|
||||
chown ${USERNAME}:root /usr/local/share/docker-init.sh
|
||||
echo "Done!"
|
||||
@@ -0,0 +1,237 @@
|
||||
#!/usr/bin/env bash
|
||||
#-------------------------------------------------------------------------------------------------------------
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
|
||||
#-------------------------------------------------------------------------------------------------------------
|
||||
#
|
||||
# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/docker-in-docker.md
|
||||
# Maintainer: The VS Code and Codespaces Teams
|
||||
#
|
||||
# Syntax: ./docker-in-docker-debian.sh [enable non-root docker access flag] [non-root user] [use moby]
|
||||
|
||||
ENABLE_NONROOT_DOCKER=${1:-"true"}
|
||||
USERNAME=${2:-"automatic"}
|
||||
USE_MOBY=${3:-"true"}
|
||||
MICROSOFT_GPG_KEYS_URI="https://packages.microsoft.com/keys/microsoft.asc"
|
||||
|
||||
set -e
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Determine the appropriate non-root user
|
||||
if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then
|
||||
USERNAME=""
|
||||
POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)")
|
||||
for CURRENT_USER in ${POSSIBLE_USERS[@]}; do
|
||||
if id -u ${CURRENT_USER} > /dev/null 2>&1; then
|
||||
USERNAME=${CURRENT_USER}
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ "${USERNAME}" = "" ]; then
|
||||
USERNAME=root
|
||||
fi
|
||||
elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then
|
||||
USERNAME=root
|
||||
fi
|
||||
|
||||
# Get central common setting
|
||||
get_common_setting() {
|
||||
if [ "${common_settings_file_loaded}" != "true" ]; then
|
||||
curl -sfL "https://aka.ms/vscode-dev-containers/script-library/settings.env" 2>/dev/null -o /tmp/vsdc-settings.env || echo "Could not download settings file. Skipping."
|
||||
common_settings_file_loaded=true
|
||||
fi
|
||||
if [ -f "/tmp/vsdc-settings.env" ]; then
|
||||
local multi_line=""
|
||||
if [ "$2" = "true" ]; then multi_line="-z"; fi
|
||||
local result="$(grep ${multi_line} -oP "$1=\"?\K[^\"]+" /tmp/vsdc-settings.env | tr -d '\0')"
|
||||
if [ ! -z "${result}" ]; then declare -g $1="${result}"; fi
|
||||
fi
|
||||
echo "$1=${!1}"
|
||||
}
|
||||
|
||||
# Function to run apt-get if needed
|
||||
apt_get_update_if_needed()
|
||||
{
|
||||
if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then
|
||||
echo "Running apt-get update..."
|
||||
apt-get update
|
||||
else
|
||||
echo "Skipping apt-get update."
|
||||
fi
|
||||
}
|
||||
|
||||
# Checks if packages are installed and installs them if not
|
||||
check_packages() {
|
||||
if ! dpkg -s "$@" > /dev/null 2>&1; then
|
||||
apt_get_update_if_needed
|
||||
apt-get -y install --no-install-recommends "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
# Ensure apt is in non-interactive to avoid prompts
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Install dependencies
|
||||
check_packages apt-transport-https curl ca-certificates lxc pigz iptables gnupg2
|
||||
|
||||
# Swap to legacy iptables for compatibility
|
||||
if type iptables-legacy > /dev/null 2>&1; then
|
||||
update-alternatives --set iptables /usr/sbin/iptables-legacy
|
||||
update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy
|
||||
fi
|
||||
|
||||
# Install Docker / Moby CLI if not already installed
|
||||
if type docker > /dev/null 2>&1 && type dockerd > /dev/null 2>&1; then
|
||||
echo "Docker / Moby CLI and Engine already installed."
|
||||
else
|
||||
# Source /etc/os-release to get OS info
|
||||
. /etc/os-release
|
||||
if [ "${USE_MOBY}" = "true" ]; then
|
||||
# Import key safely (new 'signed-by' method rather than deprecated apt-key approach) and install
|
||||
get_common_setting MICROSOFT_GPG_KEYS_URI
|
||||
curl -sSL ${MICROSOFT_GPG_KEYS_URI} | gpg --dearmor > /usr/share/keyrings/microsoft-archive-keyring.gpg
|
||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/microsoft-archive-keyring.gpg] https://packages.microsoft.com/repos/microsoft-${ID}-${VERSION_CODENAME}-prod ${VERSION_CODENAME} main" > /etc/apt/sources.list.d/microsoft.list
|
||||
apt-get update
|
||||
apt-get -y install --no-install-recommends moby-cli moby-buildx moby-compose moby-engine
|
||||
else
|
||||
# Import key safely (new 'signed-by' method rather than deprecated apt-key approach) and install
|
||||
curl -fsSL https://download.docker.com/linux/${ID}/gpg | gpg --dearmor > /usr/share/keyrings/docker-archive-keyring.gpg
|
||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/${ID} ${VERSION_CODENAME} stable" > /etc/apt/sources.list.d/docker.list
|
||||
apt-get update
|
||||
apt-get -y install --no-install-recommends docker-ce-cli docker-ce
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Finished installing docker / moby"
|
||||
|
||||
# Install Docker Compose if not already installed and is on a supported architecture
|
||||
if type docker-compose > /dev/null 2>&1; then
|
||||
echo "Docker Compose already installed."
|
||||
else
|
||||
TARGET_COMPOSE_ARCH="$(uname -m)"
|
||||
if [ "${TARGET_COMPOSE_ARCH}" = "amd64" ]; then
|
||||
TARGET_COMPOSE_ARCH="x86_64"
|
||||
fi
|
||||
if [ "${TARGET_COMPOSE_ARCH}" != "x86_64" ]; then
|
||||
# Use pip to get a version that runns on this architecture
|
||||
if ! dpkg -s python3-minimal python3-pip libffi-dev python3-venv > /dev/null 2>&1; then
|
||||
apt_get_update_if_needed
|
||||
apt-get -y install python3-minimal python3-pip libffi-dev python3-venv
|
||||
fi
|
||||
export PIPX_HOME=/usr/local/pipx
|
||||
mkdir -p ${PIPX_HOME}
|
||||
export PIPX_BIN_DIR=/usr/local/bin
|
||||
export PYTHONUSERBASE=/tmp/pip-tmp
|
||||
export PIP_CACHE_DIR=/tmp/pip-tmp/cache
|
||||
pipx_bin=pipx
|
||||
if ! type pipx > /dev/null 2>&1; then
|
||||
pip3 install --disable-pip-version-check --no-warn-script-location --no-cache-dir --user pipx
|
||||
pipx_bin=/tmp/pip-tmp/bin/pipx
|
||||
fi
|
||||
${pipx_bin} install --system-site-packages --pip-args '--no-cache-dir --force-reinstall' docker-compose
|
||||
rm -rf /tmp/pip-tmp
|
||||
else
|
||||
LATEST_COMPOSE_VERSION=$(basename "$(curl -fsSL -o /dev/null -w "%{url_effective}" https://github.com/docker/compose/releases/latest)")
|
||||
curl -fsSL "https://github.com/docker/compose/releases/download/${LATEST_COMPOSE_VERSION}/docker-compose-$(uname -s)-${TARGET_COMPOSE_ARCH}" -o /usr/local/bin/docker-compose
|
||||
chmod +x /usr/local/bin/docker-compose
|
||||
fi
|
||||
fi
|
||||
|
||||
# If init file already exists, exit
|
||||
if [ -f "/usr/local/share/docker-init.sh" ]; then
|
||||
echo "/usr/local/share/docker-init.sh already exists, so exiting."
|
||||
exit 0
|
||||
fi
|
||||
echo "docker-init doesnt exist..."
|
||||
|
||||
# Add user to the docker group
|
||||
if [ "${ENABLE_NONROOT_DOCKER}" = "true" ]; then
|
||||
if ! getent group docker > /dev/null 2>&1; then
|
||||
groupadd docker
|
||||
fi
|
||||
|
||||
usermod -aG docker ${USERNAME}
|
||||
fi
|
||||
|
||||
tee /usr/local/share/docker-init.sh > /dev/null \
|
||||
<< 'EOF'
|
||||
#!/usr/bin/env bash
|
||||
#-------------------------------------------------------------------------------------------------------------
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
|
||||
#-------------------------------------------------------------------------------------------------------------
|
||||
|
||||
sudoIf()
|
||||
{
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
sudo "$@"
|
||||
else
|
||||
"$@"
|
||||
fi
|
||||
}
|
||||
|
||||
# explicitly remove dockerd and containerd PID file to ensure that it can start properly if it was stopped uncleanly
|
||||
# ie: docker kill <ID>
|
||||
sudoIf find /run /var/run -iname 'docker*.pid' -delete || :
|
||||
sudoIf find /run /var/run -iname 'container*.pid' -delete || :
|
||||
|
||||
set -e
|
||||
|
||||
## Dind wrapper script from docker team
|
||||
# Maintained: https://github.com/moby/moby/blob/master/hack/dind
|
||||
|
||||
export container=docker
|
||||
|
||||
if [ -d /sys/kernel/security ] && ! sudoIf mountpoint -q /sys/kernel/security; then
|
||||
sudoIf mount -t securityfs none /sys/kernel/security || {
|
||||
echo >&2 'Could not mount /sys/kernel/security.'
|
||||
echo >&2 'AppArmor detection and --privileged mode might break.'
|
||||
}
|
||||
fi
|
||||
|
||||
# Mount /tmp (conditionally)
|
||||
if ! sudoIf mountpoint -q /tmp; then
|
||||
sudoIf mount -t tmpfs none /tmp
|
||||
fi
|
||||
|
||||
# cgroup v2: enable nesting
|
||||
if [ -f /sys/fs/cgroup/cgroup.controllers ]; then
|
||||
# move the init process (PID 1) from the root group to the /init group,
|
||||
# otherwise writing subtree_control fails with EBUSY.
|
||||
sudoIf mkdir -p /sys/fs/cgroup/init
|
||||
sudoIf echo 1 > /sys/fs/cgroup/init/cgroup.procs
|
||||
# enable controllers
|
||||
sudoIf sed -e 's/ / +/g' -e 's/^/+/' < /sys/fs/cgroup/cgroup.controllers \
|
||||
> /sys/fs/cgroup/cgroup.subtree_control
|
||||
fi
|
||||
## Dind wrapper over.
|
||||
|
||||
# Handle DNS
|
||||
set +e
|
||||
cat /etc/resolv.conf | grep -i 'internal.cloudapp.net'
|
||||
if [ $? -eq 0 ]
|
||||
then
|
||||
echo "Setting dockerd Azure DNS."
|
||||
CUSTOMDNS="--dns 168.63.129.16"
|
||||
else
|
||||
echo "Not setting dockerd DNS manually."
|
||||
CUSTOMDNS=""
|
||||
fi
|
||||
set -e
|
||||
|
||||
# Start docker/moby engine
|
||||
( sudoIf dockerd $CUSTOMDNS > /tmp/dockerd.log 2>&1 ) &
|
||||
|
||||
set +e
|
||||
|
||||
# Execute whatever commands were passed in (if any). This allows us
|
||||
# to set this script to ENTRYPOINT while still executing the default CMD.
|
||||
exec "$@"
|
||||
EOF
|
||||
|
||||
chmod +x /usr/local/share/docker-init.sh
|
||||
chown ${USERNAME}:root /usr/local/share/docker-init.sh
|
||||
@@ -0,0 +1,140 @@
|
||||
#!/usr/bin/env bash
|
||||
#-------------------------------------------------------------------------------------------------------------
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
|
||||
#-------------------------------------------------------------------------------------------------------------
|
||||
#
|
||||
# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/git-from-src.md
|
||||
# Maintainer: The VS Code and Codespaces Teams
|
||||
#
|
||||
# Syntax: ./git-from-src-debian.sh [version] [use PPA if available]
|
||||
|
||||
GIT_VERSION=${1:-"latest"}
|
||||
USE_PPA_IF_AVAILABLE=${2:-"false"}
|
||||
|
||||
GIT_CORE_PPA_ARCHIVE_GPG_KEY=E1DD270288B4E6030699E45FA1715D88E1DF1F24
|
||||
GPG_KEY_SERVERS="keyserver hkp://keyserver.ubuntu.com:80
|
||||
keyserver hkps://keys.openpgp.org
|
||||
keyserver hkp://keyserver.pgp.com"
|
||||
|
||||
set -e
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get central common setting
|
||||
get_common_setting() {
|
||||
if [ "${common_settings_file_loaded}" != "true" ]; then
|
||||
curl -sfL "https://aka.ms/vscode-dev-containers/script-library/settings.env" 2>/dev/null -o /tmp/vsdc-settings.env || echo "Could not download settings file. Skipping."
|
||||
common_settings_file_loaded=true
|
||||
fi
|
||||
if [ -f "/tmp/vsdc-settings.env" ]; then
|
||||
local multi_line=""
|
||||
if [ "$2" = "true" ]; then multi_line="-z"; fi
|
||||
local result="$(grep ${multi_line} -oP "$1=\"?\K[^\"]+" /tmp/vsdc-settings.env | tr -d '\0')"
|
||||
if [ ! -z "${result}" ]; then declare -g $1="${result}"; fi
|
||||
fi
|
||||
echo "$1=${!1}"
|
||||
}
|
||||
|
||||
# Import the specified key in a variable name passed in as
|
||||
receive_gpg_keys() {
|
||||
get_common_setting $1
|
||||
local keys=${!1}
|
||||
get_common_setting GPG_KEY_SERVERS true
|
||||
local keyring_args=""
|
||||
if [ ! -z "$2" ]; then
|
||||
mkdir -p "$(dirname \"$2\")"
|
||||
keyring_args="--no-default-keyring --keyring $2"
|
||||
fi
|
||||
|
||||
# Use a temporary locaiton for gpg keys to avoid polluting image
|
||||
export GNUPGHOME="/tmp/tmp-gnupg"
|
||||
mkdir -p ${GNUPGHOME}
|
||||
chmod 700 ${GNUPGHOME}
|
||||
echo -e "disable-ipv6\n${GPG_KEY_SERVERS}" > ${GNUPGHOME}/dirmngr.conf
|
||||
# GPG key download sometimes fails for some reason and retrying fixes it.
|
||||
local retry_count=0
|
||||
local gpg_ok="false"
|
||||
set +e
|
||||
until [ "${gpg_ok}" = "true" ] || [ "${retry_count}" -eq "5" ];
|
||||
do
|
||||
echo "(*) Downloading GPG key..."
|
||||
( echo "${keys}" | xargs -n 1 gpg -q ${keyring_args} --recv-keys) 2>&1 && gpg_ok="true"
|
||||
if [ "${gpg_ok}" != "true" ]; then
|
||||
echo "(*) Failed getting key, retring in 10s..."
|
||||
(( retry_count++ ))
|
||||
sleep 10s
|
||||
fi
|
||||
done
|
||||
set -e
|
||||
if [ "${gpg_ok}" = "false" ]; then
|
||||
echo "(!) Failed to install rvm."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to run apt-get if needed
|
||||
apt_get_update_if_needed()
|
||||
{
|
||||
if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then
|
||||
echo "Running apt-get update..."
|
||||
apt-get update
|
||||
else
|
||||
echo "Skipping apt-get update."
|
||||
fi
|
||||
}
|
||||
|
||||
# Checks if packages are installed and installs them if not
|
||||
check_packages() {
|
||||
if ! dpkg -s "$@" > /dev/null 2>&1; then
|
||||
apt_get_update_if_needed
|
||||
apt-get -y install --no-install-recommends "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Source /etc/os-release to get OS info
|
||||
. /etc/os-release
|
||||
# If ubuntu, PPAs allowed, and latest - install from there
|
||||
if ([ "${GIT_VERSION}" = "latest" ] || [ "${GIT_VERSION}" = "lts" ] || [ "${GIT_VERSION}" = "current" ]) && [ "${ID}" = "ubuntu" ] && [ "${USE_PPA_IF_AVAILABLE}" = "true" ]; then
|
||||
echo "Using PPA to install latest git..."
|
||||
check_packages apt-transport-https curl ca-certificates gnupg2
|
||||
receive_gpg_keys GIT_CORE_PPA_ARCHIVE_GPG_KEY /usr/share/keyrings/gitcoreppa-archive-keyring.gpg
|
||||
echo -e "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/gitcoreppa-archive-keyring.gpg] http://ppa.launchpad.net/git-core/ppa/ubuntu ${VERSION_CODENAME} main\ndeb-src [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/gitcoreppa-archive-keyring.gpg] http://ppa.launchpad.net/git-core/ppa/ubuntu ${VERSION_CODENAME} main" > /etc/apt/sources.list.d/git-core-ppa.list
|
||||
apt-get update
|
||||
apt-get -y install --no-install-recommends git
|
||||
rm -rf "/tmp/tmp-gnupg"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Install required packages to build if missing
|
||||
check_packages build-essential curl ca-certificates tar gettext libssl-dev zlib1g-dev libcurl?-openssl-dev libexpat1-dev
|
||||
|
||||
# Partial version matching
|
||||
if [ "$(echo "${GIT_VERSION}" | grep -o '\.' | wc -l)" != "2" ]; then
|
||||
requested_version="${GIT_VERSION}"
|
||||
version_list="$(curl -sSL -H "Accept: application/vnd.github.v3+json" "https://api.github.com/repos/git/git/tags" | grep -oP '"name":\s*"v\K[0-9]+\.[0-9]+\.[0-9]+"' | tr -d '"' | sort -rV )"
|
||||
if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "lts" ] || [ "${requested_version}" = "current" ]; then
|
||||
GIT_VERSION="$(echo "${version_list}" | head -n 1)"
|
||||
else
|
||||
set +e
|
||||
GIT_VERSION="$(echo "${version_list}" | grep -E -m 1 "^${requested_version//./\\.}([\\.\\s]|$)")"
|
||||
set -e
|
||||
fi
|
||||
if [ -z "${GIT_VERSION}" ] || ! echo "${version_list}" | grep "^${GIT_VERSION//./\\.}$" > /dev/null 2>&1; then
|
||||
echo "Invalid git version: ${requested_version}" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Downloading source for ${GIT_VERSION}..."
|
||||
curl -sL https://github.com/git/git/archive/v${GIT_VERSION}.tar.gz | tar -xzC /tmp 2>&1
|
||||
echo "Building..."
|
||||
cd /tmp/git-${GIT_VERSION}
|
||||
make -s prefix=/usr/local all && make -s prefix=/usr/local install 2>&1
|
||||
rm -rf /tmp/git-${GIT_VERSION}
|
||||
echo "Done!"
|
||||
@@ -0,0 +1,188 @@
|
||||
#!/usr/bin/env bash
|
||||
#-------------------------------------------------------------------------------------------------------------
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
|
||||
#-------------------------------------------------------------------------------------------------------------
|
||||
#
|
||||
# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/github.md
|
||||
# Maintainer: The VS Code and Codespaces Teams
|
||||
#
|
||||
# Syntax: ./github-debian.sh [version]
|
||||
|
||||
CLI_VERSION=${1:-"latest"}
|
||||
|
||||
GITHUB_CLI_ARCHIVE_GPG_KEY=C99B11DEB97541F0
|
||||
GPG_KEY_SERVERS="keyserver hkp://keyserver.ubuntu.com:80
|
||||
keyserver hkps://keys.openpgp.org
|
||||
keyserver hkp://keyserver.pgp.com"
|
||||
|
||||
set -e
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get central common setting
|
||||
get_common_setting() {
|
||||
if [ "${common_settings_file_loaded}" != "true" ]; then
|
||||
curl -sfL "https://aka.ms/vscode-dev-containers/script-library/settings.env" 2>/dev/null -o /tmp/vsdc-settings.env || echo "Could not download settings file. Skipping."
|
||||
common_settings_file_loaded=true
|
||||
fi
|
||||
if [ -f "/tmp/vsdc-settings.env" ]; then
|
||||
local multi_line=""
|
||||
if [ "$2" = "true" ]; then multi_line="-z"; fi
|
||||
local result="$(grep ${multi_line} -oP "$1=\"?\K[^\"]+" /tmp/vsdc-settings.env | tr -d '\0')"
|
||||
if [ ! -z "${result}" ]; then declare -g $1="${result}"; fi
|
||||
fi
|
||||
echo "$1=${!1}"
|
||||
}
|
||||
|
||||
# Import the specified key in a variable name passed in as
|
||||
receive_gpg_keys() {
|
||||
get_common_setting $1
|
||||
local keys=${!1}
|
||||
get_common_setting GPG_KEY_SERVERS true
|
||||
|
||||
# Use a temporary locaiton for gpg keys to avoid polluting image
|
||||
export GNUPGHOME="/tmp/tmp-gnupg"
|
||||
mkdir -p ${GNUPGHOME}
|
||||
chmod 700 ${GNUPGHOME}
|
||||
echo -e "disable-ipv6\n${GPG_KEY_SERVERS}" > ${GNUPGHOME}/dirmngr.conf
|
||||
# GPG key download sometimes fails for some reason and retrying fixes it.
|
||||
local retry_count=0
|
||||
local gpg_ok="false"
|
||||
set +e
|
||||
until [ "${gpg_ok}" = "true" ] || [ "${retry_count}" -eq "5" ];
|
||||
do
|
||||
echo "(*) Downloading GPG key..."
|
||||
( echo "${keys}" | xargs -n 1 gpg --recv-keys) 2>&1 && gpg_ok="true"
|
||||
if [ "${gpg_ok}" != "true" ]; then
|
||||
echo "(*) Failed getting key, retring in 10s..."
|
||||
(( retry_count++ ))
|
||||
sleep 10s
|
||||
fi
|
||||
done
|
||||
set -e
|
||||
if [ "${gpg_ok}" = "false" ]; then
|
||||
echo "(!) Failed to install rvm."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Figure out correct version of a three part version number is not passed
|
||||
find_version_from_git_tags() {
|
||||
local variable_name=$1
|
||||
local requested_version=${!variable_name}
|
||||
if [ "${requested_version}" = "none" ]; then return; fi
|
||||
local repository=$2
|
||||
local prefix=${3:-"tags/v"}
|
||||
local separator=${4:-"."}
|
||||
local last_part_optional=${5:-"false"}
|
||||
if [ "$(echo "${requested_version}" | grep -o "." | wc -l)" != "2" ]; then
|
||||
local escaped_separator=${separator//./\\.}
|
||||
local last_part
|
||||
if [ "${last_part_optional}" = "true" ]; then
|
||||
last_part="(${escaped_separator}[0-9]+)?"
|
||||
else
|
||||
last_part="${escaped_separator}[0-9]+"
|
||||
fi
|
||||
local regex="${prefix}\\K[0-9]+${escaped_separator}[0-9]+${last_part}$"
|
||||
local version_list="$(git ls-remote --tags ${repository} | grep -oP "${regex}" | tr -d ' ' | tr "${separator}" "." | sort -rV)"
|
||||
if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "lts" ]; then
|
||||
declare -g ${variable_name}="$(echo "${version_list}" | head -n 1)"
|
||||
else
|
||||
set +e
|
||||
declare -g ${variable_name}="$(echo "${version_list}" | grep -E -m 1 "^${requested_version//./\\.}([\\.\\s]|$)")"
|
||||
set -e
|
||||
fi
|
||||
fi
|
||||
if [ -z "${!variable_name}" ] || ! echo "${version_list}" | grep "^${!variable_name//./\\.}$" > /dev/null 2>&1; then
|
||||
echo -e "Invalid ${variable_name} value: ${requested_version}\nValid values:\n${version_list}" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "${variable_name}=${!variable_name}"
|
||||
}
|
||||
|
||||
# Import the specified key in a variable name passed in as
|
||||
receive_gpg_keys() {
|
||||
get_common_setting $1
|
||||
local keys=${!1}
|
||||
get_common_setting GPG_KEY_SERVERS true
|
||||
local keyring_args=""
|
||||
if [ ! -z "$2" ]; then
|
||||
keyring_args="--no-default-keyring --keyring $2"
|
||||
fi
|
||||
|
||||
# Use a temporary locaiton for gpg keys to avoid polluting image
|
||||
export GNUPGHOME="/tmp/tmp-gnupg"
|
||||
mkdir -p ${GNUPGHOME}
|
||||
chmod 700 ${GNUPGHOME}
|
||||
echo -e "disable-ipv6\n${GPG_KEY_SERVERS}" > ${GNUPGHOME}/dirmngr.conf
|
||||
# GPG key download sometimes fails for some reason and retrying fixes it.
|
||||
local retry_count=0
|
||||
local gpg_ok="false"
|
||||
set +e
|
||||
until [ "${gpg_ok}" = "true" ] || [ "${retry_count}" -eq "5" ];
|
||||
do
|
||||
echo "(*) Downloading GPG key..."
|
||||
( echo "${keys}" | xargs -n 1 gpg -q ${keyring_args} --recv-keys) 2>&1 && gpg_ok="true"
|
||||
if [ "${gpg_ok}" != "true" ]; then
|
||||
echo "(*) Failed getting key, retring in 10s..."
|
||||
(( retry_count++ ))
|
||||
sleep 10s
|
||||
fi
|
||||
done
|
||||
set -e
|
||||
if [ "${gpg_ok}" = "false" ]; then
|
||||
echo "(!) Failed to install rvm."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to run apt-get if needed
|
||||
apt_get_update_if_needed()
|
||||
{
|
||||
if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then
|
||||
echo "Running apt-get update..."
|
||||
apt-get update
|
||||
else
|
||||
echo "Skipping apt-get update."
|
||||
fi
|
||||
}
|
||||
|
||||
# Checks if packages are installed and installs them if not
|
||||
check_packages() {
|
||||
if ! dpkg -s "$@" > /dev/null 2>&1; then
|
||||
apt_get_update_if_needed
|
||||
apt-get -y install --no-install-recommends "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Install curl, apt-transport-https, curl, gpg, or dirmngr, git if missing
|
||||
check_packages curl ca-certificates apt-transport-https dirmngr gnupg2
|
||||
if ! type git > /dev/null 2>&1; then
|
||||
apt_get_update_if_needed
|
||||
apt-get -y install --no-install-recommends git
|
||||
fi
|
||||
|
||||
# Soft version matching
|
||||
if [ "${CLI_VERSION}" != "latest" ] && [ "${CLI_VERSION}" != "lts" ] && [ "${CLI_VERSION}" != "stable" ]; then
|
||||
find_version_from_git_tags CLI_VERSION "https://github.com/cli/cli"
|
||||
version_suffix="=${CLI_VERSION}"
|
||||
else
|
||||
version_suffix=""
|
||||
fi
|
||||
|
||||
# Install the GitHub CLI
|
||||
echo "Downloading github CLI..."
|
||||
# Import key safely (new method rather than deprecated apt-key approach) and install
|
||||
. /etc/os-release
|
||||
receive_gpg_keys GITHUB_CLI_ARCHIVE_GPG_KEY /usr/share/keyrings/githubcli-archive-keyring.gpg
|
||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages ${VERSION_CODENAME} main" > /etc/apt/sources.list.d/github-cli.list
|
||||
apt-get update
|
||||
apt-get -y install "gh${version_suffix}"
|
||||
rm -rf "/tmp/gh/gnupg"
|
||||
echo "Done!"
|
||||
@@ -0,0 +1,201 @@
|
||||
#!/usr/bin/env bash
|
||||
#-------------------------------------------------------------------------------------------------------------
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
|
||||
#-------------------------------------------------------------------------------------------------------------
|
||||
#
|
||||
# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/go.md
|
||||
# Maintainer: The VS Code and Codespaces Teams
|
||||
#
|
||||
# Syntax: ./go-debian.sh [Go version] [GOROOT] [GOPATH] [non-root user] [Add GOPATH, GOROOT to rc files flag] [Install tools flag]
|
||||
|
||||
TARGET_GO_VERSION=${1:-"latest"}
|
||||
TARGET_GOROOT=${2:-"/usr/local/go"}
|
||||
TARGET_GOPATH=${3:-"/go"}
|
||||
USERNAME=${4:-"automatic"}
|
||||
UPDATE_RC=${5:-"true"}
|
||||
INSTALL_GO_TOOLS=${6:-"true"}
|
||||
|
||||
set -e
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Ensure that login shells get the correct path if the user updated the PATH using ENV.
|
||||
rm -f /etc/profile.d/00-restore-env.sh
|
||||
echo "export PATH=${PATH//$(sh -lc 'echo $PATH')/\$PATH}" > /etc/profile.d/00-restore-env.sh
|
||||
chmod +x /etc/profile.d/00-restore-env.sh
|
||||
|
||||
# Determine the appropriate non-root user
|
||||
if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then
|
||||
USERNAME=""
|
||||
POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)")
|
||||
for CURRENT_USER in ${POSSIBLE_USERS[@]}; do
|
||||
if id -u ${CURRENT_USER} > /dev/null 2>&1; then
|
||||
USERNAME=${CURRENT_USER}
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ "${USERNAME}" = "" ]; then
|
||||
USERNAME=root
|
||||
fi
|
||||
elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then
|
||||
USERNAME=root
|
||||
fi
|
||||
|
||||
updaterc() {
|
||||
if [ "${UPDATE_RC}" = "true" ]; then
|
||||
echo "Updating /etc/bash.bashrc and /etc/zsh/zshrc..."
|
||||
echo -e "$1" >> /etc/bash.bashrc
|
||||
if [ -f "/etc/zsh/zshrc" ]; then
|
||||
echo -e "$1" >> /etc/zsh/zshrc
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Figure out correct version of a three part version number is not passed
|
||||
find_version_from_git_tags() {
|
||||
local variable_name=$1
|
||||
local requested_version=${!variable_name}
|
||||
if [ "${requested_version}" = "none" ]; then return; fi
|
||||
local repository=$2
|
||||
local prefix=${3:-"tags/v"}
|
||||
local separator=${4:-"."}
|
||||
local last_part_optional=${5:-"false"}
|
||||
if [ "$(echo "${requested_version}" | grep -o "." | wc -l)" != "2" ]; then
|
||||
local escaped_separator=${separator//./\\.}
|
||||
local last_part
|
||||
if [ "${last_part_optional}" = "true" ]; then
|
||||
last_part="(${escaped_separator}[0-9]+)?"
|
||||
else
|
||||
last_part="${escaped_separator}[0-9]+"
|
||||
fi
|
||||
local regex="${prefix}\\K[0-9]+${escaped_separator}[0-9]+${last_part}$"
|
||||
local version_list="$(git ls-remote --tags ${repository} | grep -oP "${regex}" | tr -d ' ' | tr "${separator}" "." | sort -rV)"
|
||||
if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "lts" ]; then
|
||||
declare -g ${variable_name}="$(echo "${version_list}" | head -n 1)"
|
||||
else
|
||||
set +e
|
||||
declare -g ${variable_name}="$(echo "${version_list}" | grep -E -m 1 "^${requested_version//./\\.}([\\.\\s]|$)")"
|
||||
set -e
|
||||
fi
|
||||
fi
|
||||
if [ -z "${!variable_name}" ] || ! echo "${version_list}" | grep "^${!variable_name//./\\.}$" > /dev/null 2>&1; then
|
||||
echo -e "Invalid ${variable_name} value: ${requested_version}\nValid values:\n${version_list}" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "${variable_name}=${!variable_name}"
|
||||
}
|
||||
|
||||
# Function to run apt-get if needed
|
||||
apt_get_update_if_needed()
|
||||
{
|
||||
if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then
|
||||
echo "Running apt-get update..."
|
||||
apt-get update
|
||||
else
|
||||
echo "Skipping apt-get update."
|
||||
fi
|
||||
}
|
||||
|
||||
# Checks if packages are installed and installs them if not
|
||||
check_packages() {
|
||||
if ! dpkg -s "$@" > /dev/null 2>&1; then
|
||||
apt_get_update_if_needed
|
||||
apt-get -y install --no-install-recommends "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Install curl, tar, git, other dependencies if missing
|
||||
check_packages curl ca-certificates tar g++ gcc libc6-dev make pkg-config
|
||||
if ! type git > /dev/null 2>&1; then
|
||||
apt_get_update_if_needed
|
||||
apt-get -y install --no-install-recommends git
|
||||
fi
|
||||
|
||||
# Get closest match for version number specified
|
||||
find_version_from_git_tags TARGET_GO_VERSION "https://go.googlesource.com/go" "tags/go" "." "true"
|
||||
|
||||
architecture="$(uname -m)"
|
||||
case $architecture in
|
||||
x86_64) architecture="amd64";;
|
||||
aarch64 | armv8*) architecture="arm64";;
|
||||
aarch32 | armv7* | armvhf*) architecture="armv6l";;
|
||||
i?86) architecture="386";;
|
||||
*) echo "(!) Architecture $architecture unsupported"; exit 1 ;;
|
||||
esac
|
||||
|
||||
# Install Go
|
||||
GO_INSTALL_SCRIPT="$(cat <<EOF
|
||||
set -e
|
||||
echo "Downloading Go ${TARGET_GO_VERSION}..."
|
||||
curl -sSL -o /tmp/go.tar.gz "https://golang.org/dl/go${TARGET_GO_VERSION}.linux-${architecture}.tar.gz"
|
||||
echo "Extracting Go ${TARGET_GO_VERSION}..."
|
||||
tar -xzf /tmp/go.tar.gz -C "${TARGET_GOROOT}" --strip-components=1
|
||||
rm -f /tmp/go.tar.gz
|
||||
EOF
|
||||
)"
|
||||
if [ "${TARGET_GO_VERSION}" != "none" ] && ! type go > /dev/null 2>&1; then
|
||||
mkdir -p "${TARGET_GOROOT}" "${TARGET_GOPATH}"
|
||||
chown -R ${USERNAME} "${TARGET_GOROOT}" "${TARGET_GOPATH}"
|
||||
su ${USERNAME} -c "${GO_INSTALL_SCRIPT}"
|
||||
else
|
||||
echo "Go already installed. Skipping."
|
||||
fi
|
||||
|
||||
# Install Go tools that are isImportant && !replacedByGopls based on
|
||||
# https://github.com/golang/vscode-go/blob/0ff533d408e4eb8ea54ce84d6efa8b2524d62873/src/goToolsInformation.ts
|
||||
# Exception `dlv-dap` is a copy of github.com/go-delve/delve/cmd/dlv built from the master.
|
||||
GO_TOOLS="\
|
||||
golang.org/x/tools/gopls@latest \
|
||||
honnef.co/go/tools/cmd/staticcheck@latest \
|
||||
golang.org/x/lint/golint@latest \
|
||||
github.com/mgechev/revive@latest \
|
||||
github.com/uudashr/gopkgs/v2/cmd/gopkgs@latest \
|
||||
github.com/ramya-rao-a/go-outline@latest \
|
||||
github.com/go-delve/delve/cmd/dlv@latest \
|
||||
github.com/golangci/golangci-lint/cmd/golangci-lint@latest"
|
||||
if [ "${INSTALL_GO_TOOLS}" = "true" ]; then
|
||||
echo "Installing common Go tools..."
|
||||
export PATH=${TARGET_GOROOT}/bin:${PATH}
|
||||
mkdir -p /tmp/gotools /usr/local/etc/vscode-dev-containers ${TARGET_GOPATH}/bin
|
||||
cd /tmp/gotools
|
||||
export GOPATH=/tmp/gotools
|
||||
export GOCACHE=/tmp/gotools/cache
|
||||
|
||||
# Use go get for versions of go under 1.17
|
||||
go_install_command=install
|
||||
if [[ "1.16" > "$(go version | grep -oP 'go\K[0-9]+\.[0-9]+(\.[0-9]+)?')" ]]; then
|
||||
export GO111MODULE=on
|
||||
go_install_command=get
|
||||
echo "Go version < 1.17, using go get."
|
||||
fi
|
||||
|
||||
(echo "${GO_TOOLS}" | xargs -n 1 go ${go_install_command} -v )2>&1 | tee -a /usr/local/etc/vscode-dev-containers/go.log
|
||||
|
||||
# Move Go tools into path and clean up
|
||||
mv /tmp/gotools/bin/* ${TARGET_GOPATH}/bin/
|
||||
|
||||
# install dlv-dap (dlv@master)
|
||||
go ${go_install_command} -v github.com/go-delve/delve/cmd/dlv@master 2>&1 | tee -a /usr/local/etc/vscode-dev-containers/go.log
|
||||
mv /tmp/gotools/bin/dlv ${TARGET_GOPATH}/bin/dlv-dap
|
||||
|
||||
rm -rf /tmp/gotools
|
||||
chown -R ${USERNAME} "${TARGET_GOPATH}"
|
||||
fi
|
||||
|
||||
# Add GOPATH variable and bin directory into PATH in bashrc/zshrc files (unless disabled)
|
||||
updaterc "$(cat << EOF
|
||||
export GOPATH="${TARGET_GOPATH}"
|
||||
if [[ "\${PATH}" != *"\${GOPATH}/bin"* ]]; then export PATH="\${PATH}:\${GOPATH}/bin"; fi
|
||||
export GOROOT="${TARGET_GOROOT}"
|
||||
if [[ "\${PATH}" != *"\${GOROOT}/bin"* ]]; then export PATH="\${PATH}:\${GOROOT}/bin"; fi
|
||||
EOF
|
||||
)"
|
||||
|
||||
echo "Done!"
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# This is a replicated script.
|
||||
#
|
||||
# Syntax: ./k3s-debian.sh [k3s version] [k3s SHA256]
|
||||
|
||||
set -e
|
||||
|
||||
K3S_VERSION="${1:-"latest"}" # latest is also valid
|
||||
K3S_SHA256="${2:-"automatic"}"
|
||||
|
||||
GPG_KEY_SERVERS="keyserver hkp://keyserver.ubuntu.com:80
|
||||
keyserver hkps://keys.openpgp.org
|
||||
keyserver hkp://keyserver.pgp.com"
|
||||
|
||||
architecture="$(uname -m)"
|
||||
case $architecture in
|
||||
x86_64) architecture="amd64";;
|
||||
aarch64 | armv8*) architecture="arm64";;
|
||||
aarch32 | armv7* | armvhf*) architecture="armhf";;
|
||||
*) echo "(!) Architecture $architecture unsupported"; exit 1 ;;
|
||||
esac
|
||||
|
||||
# Figure out correct version of a three part version number is not passed
|
||||
find_version_from_git_tags() {
|
||||
local variable_name=$1
|
||||
local requested_version=${!variable_name}
|
||||
if [ "${requested_version}" = "none" ]; then return; fi
|
||||
local repository=$2
|
||||
local prefix=${3:-"tags/v"}
|
||||
local separator=${4:-"."}
|
||||
local last_part_optional=${5:-"false"}
|
||||
if [ "$(echo "${requested_version}" | grep -o "." | wc -l)" != "2" ]; then
|
||||
local escaped_separator=${separator//./\\.}
|
||||
local last_part
|
||||
if [ "${last_part_optional}" = "true" ]; then
|
||||
last_part="(${escaped_separator}[0-9ks\+]+)?"
|
||||
else
|
||||
last_part="${escaped_separator}[0-9ks\+]+"
|
||||
fi
|
||||
local regex="${prefix}\\K[0-9]+${escaped_separator}[0-9]+${last_part}$"
|
||||
local version_list="$(git ls-remote --tags ${repository} | grep -oP "${regex}" | tr -d ' ' | tr "${separator}" "." | sort -rV)"
|
||||
echo $version_list
|
||||
if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "lts" ]; then
|
||||
declare -g ${variable_name}="$(echo "${version_list}" | head -n 1)"
|
||||
else
|
||||
set +e
|
||||
declare -g ${variable_name}="$(echo "${version_list}" | grep -E -m 1 "^${requested_version//./\\.}([\\.\\s+]|$)")"
|
||||
set -e
|
||||
fi
|
||||
fi
|
||||
if [ -z "${!variable_name}" ] || ! echo "${version_list}" | grep "^${!variable_name//./\\.}$" > /dev/null 2>&1; then
|
||||
echo -e "Invalid ${variable_name} value: ${requested_version}\nValid values:\n${version_list}" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "${variable_name}=${!variable_name}"
|
||||
}
|
||||
|
||||
# Install K3s, verify checksum
|
||||
if [ "${K3S_VERSION}" != "none" ]; then
|
||||
echo "Downloading k3s..."
|
||||
urlPrefix=
|
||||
if [ "${K3S_VERSION}" = "latest" ] || [ "${K3S_VERSION}" = "lts" ] || [ "${K3S_VERSION}" = "current" ] || [ "${K3S_VERSION}" = "stable" ]; then
|
||||
K3S_VERSION="latest"
|
||||
urlPrefix="https://github.com/k3s-io/k3s/releases/latest/download"
|
||||
else
|
||||
find_version_from_git_tags K3S_VERSION https://github.com/k3s-io/k3s
|
||||
if [ "${K3S_VERSION::1}" != "v" ]; then
|
||||
K3S_VERSION="v${K3S_VERSION}"
|
||||
fi
|
||||
urlPrefix="https://github.com/k3s-io/k3s/releases/download/${K3S_VERSION}"
|
||||
fi
|
||||
|
||||
# URL encode plus sign
|
||||
K3S_VERSION="$(echo $K3S_VERSION | sed --expression='s/+/%2B/g')"
|
||||
|
||||
# latest is also valid in the download URLs
|
||||
downloadUrl="${urlPrefix}/k3s${architecture}"
|
||||
if [ "${architecture}" = "amd64" ]; then
|
||||
downloadUrl="${urlPrefix}/k3s"
|
||||
fi
|
||||
|
||||
curl -sSL -o /usr/local/bin/k3s "${downloadUrl}"
|
||||
chmod 0755 /usr/local/bin/k3s
|
||||
|
||||
if [ "$K3S_SHA256" = "automatic" ]; then
|
||||
|
||||
shaUrl="${urlPrefix}/sha256sum-${architecture}.txt"
|
||||
if [ "${architecture}" = "armhf" ]; then
|
||||
shaUrl="${urlPrefix}/sha256sum-arm.txt"
|
||||
fi
|
||||
|
||||
# Manifest contains image hashes, but we only need the binary
|
||||
K3S_SHA256="$(curl -sSL $shaUrl | grep -P '(^|\s)\Kk3s(?=\s|$)' | cut -d ' ' -f1 )"
|
||||
fi
|
||||
echo $K3S_SHA256
|
||||
([ "${K3S_SHA256}" = "dev-mode" ] || (echo "${K3S_SHA256} */usr/local/bin/k3s" | sha256sum -c -))
|
||||
if ! type k3s > /dev/null 2>&1; then
|
||||
echo '(!) k3s installation failed!'
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo -e "\nDone!"
|
||||
@@ -0,0 +1,218 @@
|
||||
#!/usr/bin/env bash
|
||||
#-------------------------------------------------------------------------------------------------------------
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
|
||||
#-------------------------------------------------------------------------------------------------------------
|
||||
#
|
||||
# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/kubectl-helm.md
|
||||
# Maintainer: The VS Code and Codespaces Teams
|
||||
#
|
||||
# Syntax: ./kubectl-helm-debian.sh [kubectl verison] [Helm version] [minikube version] [kubectl SHA256] [Helm SHA256] [minikube SHA256]
|
||||
|
||||
set -e
|
||||
|
||||
KUBECTL_VERSION="${1:-"latest"}"
|
||||
HELM_VERSION="${2:-"latest"}"
|
||||
MINIKUBE_VERSION="${3:-"none"}" # latest is also valid
|
||||
KUBECTL_SHA256="${4:-"automatic"}"
|
||||
HELM_SHA256="${5:-"automatic"}"
|
||||
MINIKUBE_SHA256="${6:-"automatic"}"
|
||||
|
||||
HELM_GPG_KEYS_URI="https://raw.githubusercontent.com/helm/helm/main/KEYS"
|
||||
GPG_KEY_SERVERS="keyserver hkp://keyserver.ubuntu.com:80
|
||||
keyserver hkps://keys.openpgp.org
|
||||
keyserver hkp://keyserver.pgp.com"
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get central common setting
|
||||
get_common_setting() {
|
||||
if [ "${common_settings_file_loaded}" != "true" ]; then
|
||||
curl -sfL "https://aka.ms/vscode-dev-containers/script-library/settings.env" 2>/dev/null -o /tmp/vsdc-settings.env || echo "Could not download settings file. Skipping."
|
||||
common_settings_file_loaded=true
|
||||
fi
|
||||
if [ -f "/tmp/vsdc-settings.env" ]; then
|
||||
local multi_line=""
|
||||
if [ "$2" = "true" ]; then multi_line="-z"; fi
|
||||
local result="$(grep ${multi_line} -oP "$1=\"?\K[^\"]+" /tmp/vsdc-settings.env | tr -d '\0')"
|
||||
if [ ! -z "${result}" ]; then declare -g $1="${result}"; fi
|
||||
fi
|
||||
echo "$1=${!1}"
|
||||
}
|
||||
|
||||
# Figure out correct version of a three part version number is not passed
|
||||
find_version_from_git_tags() {
|
||||
local variable_name=$1
|
||||
local requested_version=${!variable_name}
|
||||
if [ "${requested_version}" = "none" ]; then return; fi
|
||||
local repository=$2
|
||||
local prefix=${3:-"tags/v"}
|
||||
local separator=${4:-"."}
|
||||
local last_part_optional=${5:-"false"}
|
||||
if [ "$(echo "${requested_version}" | grep -o "." | wc -l)" != "2" ]; then
|
||||
local escaped_separator=${separator//./\\.}
|
||||
local last_part
|
||||
if [ "${last_part_optional}" = "true" ]; then
|
||||
last_part="(${escaped_separator}[0-9]+)?"
|
||||
else
|
||||
last_part="${escaped_separator}[0-9]+"
|
||||
fi
|
||||
local regex="${prefix}\\K[0-9]+${escaped_separator}[0-9]+${last_part}$"
|
||||
local version_list="$(git ls-remote --tags ${repository} | grep -oP "${regex}" | tr -d ' ' | tr "${separator}" "." | sort -rV)"
|
||||
if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "lts" ]; then
|
||||
declare -g ${variable_name}="$(echo "${version_list}" | head -n 1)"
|
||||
else
|
||||
set +e
|
||||
declare -g ${variable_name}="$(echo "${version_list}" | grep -E -m 1 "^${requested_version//./\\.}([\\.\\s]|$)")"
|
||||
set -e
|
||||
fi
|
||||
fi
|
||||
if [ -z "${!variable_name}" ] || ! echo "${version_list}" | grep "^${!variable_name//./\\.}$" > /dev/null 2>&1; then
|
||||
echo -e "Invalid ${variable_name} value: ${requested_version}\nValid values:\n${version_list}" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "${variable_name}=${!variable_name}"
|
||||
}
|
||||
|
||||
# Function to run apt-get if needed
|
||||
apt_get_update_if_needed()
|
||||
{
|
||||
if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then
|
||||
echo "Running apt-get update..."
|
||||
apt-get update
|
||||
else
|
||||
echo "Skipping apt-get update."
|
||||
fi
|
||||
}
|
||||
|
||||
# Checks if packages are installed and installs them if not
|
||||
check_packages() {
|
||||
if ! dpkg -s "$@" > /dev/null 2>&1; then
|
||||
apt_get_update_if_needed
|
||||
apt-get -y install --no-install-recommends "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
# Ensure apt is in non-interactive to avoid prompts
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Install dependencies
|
||||
check_packages curl ca-certificates coreutils gnupg2 dirmngr bash-completion
|
||||
if ! type git > /dev/null 2>&1; then
|
||||
apt_get_update_if_needed
|
||||
apt-get -y install --no-install-recommends git
|
||||
fi
|
||||
|
||||
architecture="$(uname -m)"
|
||||
case $architecture in
|
||||
x86_64) architecture="amd64";;
|
||||
aarch64 | armv8*) architecture="arm64";;
|
||||
aarch32 | armv7* | armvhf*) architecture="arm";;
|
||||
i?86) architecture="386";;
|
||||
*) echo "(!) Architecture $architecture unsupported"; exit 1 ;;
|
||||
esac
|
||||
|
||||
# Install the kubectl, verify checksum
|
||||
echo "Downloading kubectl..."
|
||||
if [ "${KUBECTL_VERSION}" = "latest" ] || [ "${KUBECTL_VERSION}" = "lts" ] || [ "${KUBECTL_VERSION}" = "current" ] || [ "${KUBECTL_VERSION}" = "stable" ]; then
|
||||
KUBECTL_VERSION="$(curl -sSL https://dl.k8s.io/release/stable.txt)"
|
||||
else
|
||||
find_version_from_git_tags KUBECTL_VERSION https://github.com/kubernetes/kubernetes
|
||||
fi
|
||||
if [ "${KUBECTL_VERSION::1}" != 'v' ]; then
|
||||
KUBECTL_VERSION="v${KUBECTL_VERSION}"
|
||||
fi
|
||||
curl -sSL -o /usr/local/bin/kubectl "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/${architecture}/kubectl"
|
||||
chmod 0755 /usr/local/bin/kubectl
|
||||
if [ "$KUBECTL_SHA256" = "automatic" ]; then
|
||||
KUBECTL_SHA256="$(curl -sSL "https://dl.k8s.io/${KUBECTL_VERSION}/bin/linux/${architecture}/kubectl.sha256")"
|
||||
fi
|
||||
([ "${KUBECTL_SHA256}" = "dev-mode" ] || (echo "${KUBECTL_SHA256} */usr/local/bin/kubectl" | sha256sum -c -))
|
||||
if ! type kubectl > /dev/null 2>&1; then
|
||||
echo '(!) kubectl installation failed!'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# kubectl bash completion
|
||||
kubectl completion bash > /etc/bash_completion.d/kubectl
|
||||
|
||||
# kubectl zsh completion
|
||||
mkdir -p /home/${USERNAME}/.oh-my-zsh/completions
|
||||
kubectl completion zsh > /home/${USERNAME}/.oh-my-zsh/completions/_kubectl
|
||||
|
||||
# Install Helm, verify signature and checksum
|
||||
echo "Downloading Helm..."
|
||||
find_version_from_git_tags HELM_VERSION "https://github.com/helm/helm"
|
||||
if [ "${HELM_VERSION::1}" != 'v' ]; then
|
||||
HELM_VERSION="v${HELM_VERSION}"
|
||||
fi
|
||||
mkdir -p /tmp/helm
|
||||
helm_filename="helm-${HELM_VERSION}-linux-${architecture}.tar.gz"
|
||||
tmp_helm_filename="/tmp/helm/${helm_filename}"
|
||||
curl -sSL "https://get.helm.sh/${helm_filename}" -o "${tmp_helm_filename}"
|
||||
curl -sSL "https://github.com/helm/helm/releases/download/${HELM_VERSION}/${helm_filename}.asc" -o "${tmp_helm_filename}.asc"
|
||||
export GNUPGHOME="/tmp/helm/gnupg"
|
||||
mkdir -p "${GNUPGHOME}"
|
||||
chmod 700 ${GNUPGHOME}
|
||||
get_common_setting HELM_GPG_KEYS_URI
|
||||
get_common_setting GPG_KEY_SERVERS true
|
||||
curl -sSL "${HELM_GPG_KEYS_URI}" -o /tmp/helm/KEYS
|
||||
echo -e "disable-ipv6\n${GPG_KEY_SERVERS}" > ${GNUPGHOME}/dirmngr.conf
|
||||
gpg -q --import "/tmp/helm/KEYS"
|
||||
if ! gpg --verify "${tmp_helm_filename}.asc" > ${GNUPGHOME}/verify.log 2>&1; then
|
||||
echo "Verification failed!"
|
||||
cat /tmp/helm/gnupg/verify.log
|
||||
exit 1
|
||||
fi
|
||||
if [ "${HELM_SHA256}" = "automatic" ]; then
|
||||
curl -sSL "https://get.helm.sh/${helm_filename}.sha256" -o "${tmp_helm_filename}.sha256"
|
||||
curl -sSL "https://github.com/helm/helm/releases/download/${HELM_VERSION}/${helm_filename}.sha256.asc" -o "${tmp_helm_filename}.sha256.asc"
|
||||
if ! gpg --verify "${tmp_helm_filename}.sha256.asc" > /tmp/helm/gnupg/verify.log 2>&1; then
|
||||
echo "Verification failed!"
|
||||
cat /tmp/helm/gnupg/verify.log
|
||||
exit 1
|
||||
fi
|
||||
HELM_SHA256="$(cat "${tmp_helm_filename}.sha256")"
|
||||
fi
|
||||
([ "${HELM_SHA256}" = "dev-mode" ] || (echo "${HELM_SHA256} *${tmp_helm_filename}" | sha256sum -c -))
|
||||
tar xf "${tmp_helm_filename}" -C /tmp/helm
|
||||
mv -f "/tmp/helm/linux-${architecture}/helm" /usr/local/bin/
|
||||
chmod 0755 /usr/local/bin/helm
|
||||
rm -rf /tmp/helm
|
||||
if ! type helm > /dev/null 2>&1; then
|
||||
echo '(!) Helm installation failed!'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Install Minikube, verify checksum
|
||||
if [ "${MINIKUBE_VERSION}" != "none" ]; then
|
||||
echo "Downloading minikube..."
|
||||
if [ "${MINIKUBE_VERSION}" = "latest" ] || [ "${MINIKUBE_VERSION}" = "lts" ] || [ "${MINIKUBE_VERSION}" = "current" ] || [ "${MINIKUBE_VERSION}" = "stable" ]; then
|
||||
MINIKUBE_VERSION="latest"
|
||||
else
|
||||
find_version_from_git_tags MINIKUBE_VERSION https://github.com/kubernetes/minikube
|
||||
if [ "${MINIKUBE_VERSION::1}" != "v" ]; then
|
||||
MINIKUBE_VERSION="v${MINIKUBE_VERSION}"
|
||||
fi
|
||||
fi
|
||||
# latest is also valid in the download URLs
|
||||
curl -sSL -o /usr/local/bin/minikube "https://storage.googleapis.com/minikube/releases/${MINIKUBE_VERSION}/minikube-linux-${architecture}"
|
||||
chmod 0755 /usr/local/bin/minikube
|
||||
if [ "$MINIKUBE_SHA256" = "automatic" ]; then
|
||||
MINIKUBE_SHA256="$(curl -sSL "https://storage.googleapis.com/minikube/releases/${MINIKUBE_VERSION}/minikube-linux-${architecture}.sha256")"
|
||||
fi
|
||||
([ "${MINIKUBE_SHA256}" = "dev-mode" ] || (echo "${MINIKUBE_SHA256} */usr/local/bin/minikube" | sha256sum -c -))
|
||||
if ! type minikube > /dev/null 2>&1; then
|
||||
echo '(!) minikube installation failed!'
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! type docker > /dev/null 2>&1; then
|
||||
echo -e '\n(*) Warning: The docker command was not found.\n\nYou can use one of the following scripts to install it:\n\nhttps://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/docker-in-docker.md\n\nor\n\nhttps://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/docker.md'
|
||||
fi
|
||||
|
||||
echo -e "\nDone!"
|
||||
@@ -0,0 +1 @@
|
||||
VERSION='dev'
|
||||
@@ -0,0 +1,141 @@
|
||||
#!/bin/bash
|
||||
#-------------------------------------------------------------------------------------------------------------
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
|
||||
#-------------------------------------------------------------------------------------------------------------
|
||||
#
|
||||
# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/node.md
|
||||
# Maintainer: The VS Code and Codespaces Teams
|
||||
#
|
||||
# Syntax: ./node-debian.sh [directory to install nvm] [node version to install (use "none" to skip)] [non-root user] [Update rc files flag]
|
||||
|
||||
export NVM_DIR=${1:-"/usr/local/share/nvm"}
|
||||
export NODE_VERSION=${2:-"lts"}
|
||||
USERNAME=${3:-"automatic"}
|
||||
UPDATE_RC=${4:-"true"}
|
||||
export NVM_VERSION="0.38.0"
|
||||
|
||||
set -e
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Ensure that login shells get the correct path if the user updated the PATH using ENV.
|
||||
rm -f /etc/profile.d/00-restore-env.sh
|
||||
echo "export PATH=${PATH//$(sh -lc 'echo $PATH')/\$PATH}" > /etc/profile.d/00-restore-env.sh
|
||||
chmod +x /etc/profile.d/00-restore-env.sh
|
||||
|
||||
# Determine the appropriate non-root user
|
||||
if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then
|
||||
USERNAME=""
|
||||
POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)")
|
||||
for CURRENT_USER in ${POSSIBLE_USERS[@]}; do
|
||||
if id -u ${CURRENT_USER} > /dev/null 2>&1; then
|
||||
USERNAME=${CURRENT_USER}
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ "${USERNAME}" = "" ]; then
|
||||
USERNAME=root
|
||||
fi
|
||||
elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then
|
||||
USERNAME=root
|
||||
fi
|
||||
|
||||
updaterc() {
|
||||
if [ "${UPDATE_RC}" = "true" ]; then
|
||||
echo "Updating /etc/bash.bashrc and /etc/zsh/zshrc..."
|
||||
echo -e "$1" >> /etc/bash.bashrc
|
||||
if [ -f "/etc/zsh/zshrc" ]; then
|
||||
echo -e "$1" >> /etc/zsh/zshrc
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to run apt-get if needed
|
||||
apt_get_update_if_needed()
|
||||
{
|
||||
if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then
|
||||
echo "Running apt-get update..."
|
||||
apt-get update
|
||||
else
|
||||
echo "Skipping apt-get update."
|
||||
fi
|
||||
}
|
||||
|
||||
# Checks if packages are installed and installs them if not
|
||||
check_packages() {
|
||||
if ! dpkg -s "$@" > /dev/null 2>&1; then
|
||||
apt_get_update_if_needed
|
||||
apt-get -y install --no-install-recommends "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
# Ensure apt is in non-interactive to avoid prompts
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Install dependencies
|
||||
check_packages apt-transport-https curl ca-certificates tar gnupg2
|
||||
|
||||
# Install yarn
|
||||
if type yarn > /dev/null 2>&1; then
|
||||
echo "Yarn already installed."
|
||||
else
|
||||
# Import key safely (new method rather than deprecated apt-key approach) and install
|
||||
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor > /usr/share/keyrings/yarn-archive-keyring.gpg
|
||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/yarn-archive-keyring.gpg] https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list
|
||||
apt-get update
|
||||
apt-get -y install --no-install-recommends yarn
|
||||
fi
|
||||
|
||||
# Adjust node version if required
|
||||
if [ "${NODE_VERSION}" = "none" ]; then
|
||||
export NODE_VERSION=
|
||||
elif [ "${NODE_VERSION}" = "lts" ]; then
|
||||
export NODE_VERSION="lts/*"
|
||||
fi
|
||||
|
||||
# Install the specified node version if NVM directory already exists, then exit
|
||||
if [ -d "${NVM_DIR}" ]; then
|
||||
echo "NVM already installed."
|
||||
if [ "${NODE_VERSION}" != "" ]; then
|
||||
su ${USERNAME} -c ". $NVM_DIR/nvm.sh && nvm install ${NODE_VERSION} && nvm clear-cache"
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Create nvm group, nvm dir, and set sticky bit
|
||||
if ! cat /etc/group | grep -e "^nvm:" > /dev/null 2>&1; then
|
||||
groupadd -r nvm
|
||||
fi
|
||||
umask 0002
|
||||
usermod -a -G nvm ${USERNAME}
|
||||
mkdir -p ${NVM_DIR}
|
||||
chown :nvm ${NVM_DIR}
|
||||
chmod g+s ${NVM_DIR}
|
||||
su ${USERNAME} -c "$(cat << EOF
|
||||
set -e
|
||||
umask 0002
|
||||
# Do not update profile - we'll do this manually
|
||||
export PROFILE=/dev/null
|
||||
curl -so- https://raw.githubusercontent.com/nvm-sh/nvm/v${NVM_VERSION}/install.sh | bash
|
||||
source ${NVM_DIR}/nvm.sh
|
||||
if [ "${NODE_VERSION}" != "" ]; then
|
||||
nvm alias default ${NODE_VERSION}
|
||||
fi
|
||||
nvm clear-cache
|
||||
EOF
|
||||
)" 2>&1
|
||||
# Update rc files
|
||||
if [ "${UPDATE_RC}" = "true" ]; then
|
||||
updaterc "$(cat <<EOF
|
||||
export NVM_DIR="${NVM_DIR}"
|
||||
[ -s "\$NVM_DIR/nvm.sh" ] && . "\$NVM_DIR/nvm.sh"
|
||||
[ -s "\$NVM_DIR/bash_completion" ] && . "\$NVM_DIR/bash_completion"
|
||||
EOF
|
||||
)"
|
||||
fi
|
||||
|
||||
echo "Done!"
|
||||
@@ -0,0 +1,307 @@
|
||||
#!/usr/bin/env bash
|
||||
#-------------------------------------------------------------------------------------------------------------
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
|
||||
#-------------------------------------------------------------------------------------------------------------
|
||||
#
|
||||
# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/python.md
|
||||
# Maintainer: The VS Code and Codespaces Teams
|
||||
#
|
||||
# Syntax: ./python-debian.sh [Python Version] [Python intall path] [PIPX_HOME] [non-root user] [Update rc files flag] [install tools]
|
||||
|
||||
PYTHON_VERSION=${1:-"latest"}
|
||||
PYTHON_INSTALL_PATH=${2:-"/usr/local/python"}
|
||||
export PIPX_HOME=${3:-"/usr/local/py-utils"}
|
||||
USERNAME=${4:-"automatic"}
|
||||
UPDATE_RC=${5:-"true"}
|
||||
INSTALL_PYTHON_TOOLS=${6:-"true"}
|
||||
USE_PPA_IF_AVAILABLE=${7:-"true"}
|
||||
|
||||
DEADSNAKES_PPA_ARCHIVE_GPG_KEY="F23C5A6CF475977595C89F51BA6932366A755776"
|
||||
PYTHON_SOURCE_GPG_KEYS="64E628F8D684696D B26995E310250568 2D347EA6AA65421D FB9921286F5E1540 3A5CA953F73C700D 04C367C218ADD4FF 0EDDC5F26A45C816 6AF053F07D9DC8D2 C9BE28DEE6DF025C 126EB563A74B06BF D9866941EA5BBD71 ED9D77D5"
|
||||
GPG_KEY_SERVERS="keyserver hkp://keyserver.ubuntu.com:80
|
||||
keyserver hkps://keys.openpgp.org
|
||||
keyserver hkp://keyserver.pgp.com"
|
||||
|
||||
set -e
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Ensure that login shells get the correct path if the user updated the PATH using ENV.
|
||||
rm -f /etc/profile.d/00-restore-env.sh
|
||||
echo "export PATH=${PATH//$(sh -lc 'echo $PATH')/\$PATH}" > /etc/profile.d/00-restore-env.sh
|
||||
chmod +x /etc/profile.d/00-restore-env.sh
|
||||
|
||||
# Determine the appropriate non-root user
|
||||
if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then
|
||||
USERNAME=""
|
||||
POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)")
|
||||
for CURRENT_USER in ${POSSIBLE_USERS[@]}; do
|
||||
if id -u ${CURRENT_USER} > /dev/null 2>&1; then
|
||||
USERNAME=${CURRENT_USER}
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ "${USERNAME}" = "" ]; then
|
||||
USERNAME=root
|
||||
fi
|
||||
elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then
|
||||
USERNAME=root
|
||||
fi
|
||||
|
||||
updaterc() {
|
||||
if [ "${UPDATE_RC}" = "true" ]; then
|
||||
echo "Updating /etc/bash.bashrc and /etc/zsh/zshrc..."
|
||||
echo -e "$1" >> /etc/bash.bashrc
|
||||
if [ -f "/etc/zsh/zshrc" ]; then
|
||||
echo -e "$1" >> /etc/zsh/zshrc
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Get central common setting
|
||||
get_common_setting() {
|
||||
if [ "${common_settings_file_loaded}" != "true" ]; then
|
||||
curl -sfL "https://aka.ms/vscode-dev-containers/script-library/settings.env" 2>/dev/null -o /tmp/vsdc-settings.env || echo "Could not download settings file. Skipping."
|
||||
common_settings_file_loaded=true
|
||||
fi
|
||||
if [ -f "/tmp/vsdc-settings.env" ]; then
|
||||
local multi_line=""
|
||||
if [ "$2" = "true" ]; then multi_line="-z"; fi
|
||||
local result="$(grep ${multi_line} -oP "$1=\"?\K[^\"]+" /tmp/vsdc-settings.env | tr -d '\0')"
|
||||
if [ ! -z "${result}" ]; then declare -g $1="${result}"; fi
|
||||
fi
|
||||
echo "$1=${!1}"
|
||||
}
|
||||
|
||||
# Import the specified key in a variable name passed in as
|
||||
receive_gpg_keys() {
|
||||
get_common_setting $1
|
||||
local keys=${!1}
|
||||
get_common_setting GPG_KEY_SERVERS true
|
||||
local keyring_args=""
|
||||
if [ ! -z "$2" ]; then
|
||||
mkdir -p "$(dirname \"$2\")"
|
||||
keyring_args="--no-default-keyring --keyring $2"
|
||||
fi
|
||||
|
||||
# Use a temporary locaiton for gpg keys to avoid polluting image
|
||||
export GNUPGHOME="/tmp/tmp-gnupg"
|
||||
mkdir -p ${GNUPGHOME}
|
||||
chmod 700 ${GNUPGHOME}
|
||||
echo -e "disable-ipv6\n${GPG_KEY_SERVERS}" > ${GNUPGHOME}/dirmngr.conf
|
||||
# GPG key download sometimes fails for some reason and retrying fixes it.
|
||||
local retry_count=0
|
||||
local gpg_ok="false"
|
||||
set +e
|
||||
until [ "${gpg_ok}" = "true" ] || [ "${retry_count}" -eq "5" ];
|
||||
do
|
||||
echo "(*) Downloading GPG key..."
|
||||
( echo "${keys}" | xargs -n 1 gpg -q ${keyring_args} --recv-keys) 2>&1 && gpg_ok="true"
|
||||
if [ "${gpg_ok}" != "true" ]; then
|
||||
echo "(*) Failed getting key, retring in 10s..."
|
||||
(( retry_count++ ))
|
||||
sleep 10s
|
||||
fi
|
||||
done
|
||||
set -e
|
||||
if [ "${gpg_ok}" = "false" ]; then
|
||||
echo "(!) Failed to install rvm."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Figure out correct version of a three part version number is not passed
|
||||
find_version_from_git_tags() {
|
||||
local variable_name=$1
|
||||
local requested_version=${!variable_name}
|
||||
if [ "${requested_version}" = "none" ]; then return; fi
|
||||
local repository=$2
|
||||
local prefix=${3:-"tags/v"}
|
||||
local separator=${4:-"."}
|
||||
local last_part_optional=${5:-"false"}
|
||||
if [ "$(echo "${requested_version}" | grep -o "." | wc -l)" != "2" ]; then
|
||||
local escaped_separator=${separator//./\\.}
|
||||
local last_part
|
||||
if [ "${last_part_optional}" = "true" ]; then
|
||||
last_part="(${escaped_separator}[0-9]+)?"
|
||||
else
|
||||
last_part="${escaped_separator}[0-9]+"
|
||||
fi
|
||||
local regex="${prefix}\\K[0-9]+${escaped_separator}[0-9]+${last_part}$"
|
||||
local version_list="$(git ls-remote --tags ${repository} | grep -oP "${regex}" | tr -d ' ' | tr "${separator}" "." | sort -rV)"
|
||||
if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "lts" ]; then
|
||||
declare -g ${variable_name}="$(echo "${version_list}" | head -n 1)"
|
||||
else
|
||||
set +e
|
||||
declare -g ${variable_name}="$(echo "${version_list}" | grep -E -m 1 "^${requested_version//./\\.}([\\.\\s]|$)")"
|
||||
set -e
|
||||
fi
|
||||
fi
|
||||
if [ -z "${!variable_name}" ] || ! echo "${version_list}" | grep "^${!variable_name//./\\.}$" > /dev/null 2>&1; then
|
||||
echo -e "Invalid ${variable_name} value: ${requested_version}\nValid values:\n${version_list}" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "${variable_name}=${!variable_name}"
|
||||
}
|
||||
|
||||
# Function to run apt-get if needed
|
||||
apt_get_update_if_needed()
|
||||
{
|
||||
if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then
|
||||
echo "Running apt-get update..."
|
||||
apt-get update
|
||||
else
|
||||
echo "Skipping apt-get update."
|
||||
fi
|
||||
}
|
||||
|
||||
# Checks if packages are installed and installs them if not
|
||||
check_packages() {
|
||||
if ! dpkg -s "$@" > /dev/null 2>&1; then
|
||||
apt_get_update_if_needed
|
||||
apt-get -y install --no-install-recommends "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
install_from_ppa() {
|
||||
local requested_version="python${PYTHON_VERSION}"
|
||||
echo "Using PPA to install Python..."
|
||||
check_packages apt-transport-https curl ca-certificates gnupg2
|
||||
receive_gpg_keys DEADSNAKES_PPA_ARCHIVE_GPG_KEY /usr/share/keyrings/deadsnakes-archive-keyring.gpg
|
||||
echo -e "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/deadsnakes-archive-keyring.gpg] http://ppa.launchpad.net/deadsnakes/ppa/ubuntu ${VERSION_CODENAME} main\ndeb-src [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/deadsnakes-archive-keyring.gpg] http://ppa.launchpad.net/deadsnakes/ppa/ubuntu ${VERSION_CODENAME} main" > /etc/apt/sources.list.d/deadsnakes-ppa.list
|
||||
apt-get update
|
||||
if [ "${PYTHON_VERSION}" = "latest" ] || [ "${PYTHON_VERSION}" = "current" ] || [ "${PYTHON_VERSION}" = "lts" ]; then
|
||||
requested_version="$(apt-cache search '^python3\.[0-9]$' | grep -oE '^python3\.[0-9]' | sort -rV | head -n 1)"
|
||||
echo "Using ${requested_version} in place of ${PYTHON_VERSION}."
|
||||
fi
|
||||
apt-get -y install ${requested_version}
|
||||
rm -rf /tmp/tmp-gnupg
|
||||
exit 0
|
||||
}
|
||||
|
||||
install_from_source() {
|
||||
if [ -d "${PYTHON_INSTALL_PATH}" ]; then
|
||||
echo "Path ${PYTHON_INSTALL_PATH} already exists. Remove this existing path or select a different one."
|
||||
exit 1
|
||||
else
|
||||
echo "Building Python ${PYTHON_VERSION} from source..."
|
||||
# Install prereqs if missing
|
||||
check_packages curl ca-certificates tar make build-essential libssl-dev zlib1g-dev \
|
||||
wget libbz2-dev libreadline-dev libxml2-dev xz-utils tk-dev gnupg2 \
|
||||
libxmlsec1-dev libsqlite3-dev libffi-dev liblzma-dev llvm dirmngr
|
||||
if ! type git > /dev/null 2>&1; then
|
||||
apt_get_update_if_needed
|
||||
apt-get -y install --no-install-recommends git
|
||||
fi
|
||||
|
||||
# Find version using soft match
|
||||
find_version_from_git_tags PYTHON_VERSION "https://github.com/python/cpython"
|
||||
|
||||
# Download tgz of source
|
||||
mkdir -p /tmp/python-src "${PYTHON_INSTALL_PATH}"
|
||||
cd /tmp/python-src
|
||||
TGZ_FILENAME="Python-${PYTHON_VERSION}.tgz"
|
||||
TGZ_URL="https://www.python.org/ftp/python/${PYTHON_VERSION}/${TGZ_FILENAME}"
|
||||
echo "Downloading ${TGZ_FILENAME}..."
|
||||
curl -sSL -o "/tmp/python-src/${TGZ_FILENAME}" "${TGZ_URL}"
|
||||
|
||||
# Verify signature
|
||||
if [ "${SKIP_SIGNATURE_CHECK}" != "true" ]; then
|
||||
receive_gpg_keys PYTHON_SOURCE_GPG_KEYS
|
||||
echo "Downloading ${TGZ_FILENAME}.asc..."
|
||||
curl -sSL -o "/tmp/python-src/${TGZ_FILENAME}.asc" "${TGZ_URL}.asc"
|
||||
gpg --verify "${TGZ_FILENAME}.asc"
|
||||
fi
|
||||
|
||||
# Update min protocol for testing only - https://bugs.python.org/issue41561
|
||||
cp /etc/ssl/openssl.cnf /tmp/python-src/
|
||||
sed -i -E 's/MinProtocol[=\ ]+.*/MinProtocol = TLSv1.0/g' /tmp/python-src/openssl.cnf
|
||||
export OPENSSL_CONF=/tmp/python-src/openssl.cnf
|
||||
|
||||
# Untar and build
|
||||
tar -xzf "/tmp/python-src/${TGZ_FILENAME}" -C "/tmp/python-src" --strip-components=1
|
||||
./configure --prefix="${PYTHON_INSTALL_PATH}" --enable-optimizations --with-ensurepip=install
|
||||
make -j 8
|
||||
make install
|
||||
cd /tmp
|
||||
rm -rf /tmp/python-src ${GNUPGHOME} /tmp/vscdc-settings.env
|
||||
chown -R ${USERNAME} "${PYTHON_INSTALL_PATH}"
|
||||
ln -s ${PYTHON_INSTALL_PATH}/bin/python3 ${PYTHON_INSTALL_PATH}/bin/python
|
||||
ln -s ${PYTHON_INSTALL_PATH}/bin/pip3 ${PYTHON_INSTALL_PATH}/bin/pip
|
||||
ln -s ${PYTHON_INSTALL_PATH}/bin/idle3 ${PYTHON_INSTALL_PATH}/bin/idle
|
||||
ln -s ${PYTHON_INSTALL_PATH}/bin/pydoc3 ${PYTHON_INSTALL_PATH}/bin/pydoc
|
||||
ln -s ${PYTHON_INSTALL_PATH}/bin/python3-config ${PYTHON_INSTALL_PATH}/bin/python-config
|
||||
updaterc "export PATH=${PYTHON_INSTALL_PATH}/bin:\${PATH}"
|
||||
fi
|
||||
}
|
||||
|
||||
# Ensure apt is in non-interactive to avoid prompts
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Install python from source if needed
|
||||
if [ "${PYTHON_VERSION}" != "none" ]; then
|
||||
# Source /etc/os-release to get OS info
|
||||
. /etc/os-release
|
||||
# If ubuntu, PPAs allowed - install from there
|
||||
if [ "${ID}" = "ubuntu" ] && [ "${USE_PPA_IF_AVAILABLE}" = "true" ]; then
|
||||
install_from_ppa
|
||||
else
|
||||
install_from_source
|
||||
fi
|
||||
fi
|
||||
|
||||
# If not installing python tools, exit
|
||||
if [ "${INSTALL_PYTHON_TOOLS}" != "true" ]; then
|
||||
echo "Done!"
|
||||
exit 0;
|
||||
fi
|
||||
|
||||
DEFAULT_UTILS="\
|
||||
pylint \
|
||||
flake8 \
|
||||
autopep8 \
|
||||
black \
|
||||
yapf \
|
||||
mypy \
|
||||
pydocstyle \
|
||||
pycodestyle \
|
||||
bandit \
|
||||
pipenv \
|
||||
virtualenv"
|
||||
|
||||
export PIPX_BIN_DIR=${PIPX_HOME}/bin
|
||||
export PATH=${PYTHON_INSTALL_PATH}/bin:${PIPX_BIN_DIR}:${PATH}
|
||||
|
||||
# Update pip
|
||||
echo "Updating pip..."
|
||||
python3 -m pip install --no-cache-dir --upgrade pip
|
||||
|
||||
# Create pipx group, dir, and set sticky bit
|
||||
if ! cat /etc/group | grep -e "^pipx:" > /dev/null 2>&1; then
|
||||
groupadd -r pipx
|
||||
fi
|
||||
usermod -a -G pipx ${USERNAME}
|
||||
umask 0002
|
||||
mkdir -p ${PIPX_BIN_DIR}
|
||||
chown :pipx ${PIPX_HOME} ${PIPX_BIN_DIR}
|
||||
chmod g+s ${PIPX_HOME} ${PIPX_BIN_DIR}
|
||||
|
||||
# Install tools
|
||||
echo "Installing Python tools..."
|
||||
export PYTHONUSERBASE=/tmp/pip-tmp
|
||||
export PIP_CACHE_DIR=/tmp/pip-tmp/cache
|
||||
pip3 install --disable-pip-version-check --no-warn-script-location --no-cache-dir --user pipx
|
||||
/tmp/pip-tmp/bin/pipx install --pip-args=--no-cache-dir pipx
|
||||
echo "${DEFAULT_UTILS}" | xargs -n 1 /tmp/pip-tmp/bin/pipx install --system-site-packages --pip-args '--no-cache-dir --force-reinstall'
|
||||
rm -rf /tmp/pip-tmp
|
||||
|
||||
updaterc "$(cat << EOF
|
||||
export PIPX_HOME="${PIPX_HOME}"
|
||||
export PIPX_BIN_DIR="${PIPX_BIN_DIR}"
|
||||
if [[ "\${PATH}" != *"\${PIPX_BIN_DIR}"* ]]; then export PATH="\${PATH}:\${PIPX_BIN_DIR}"; fi
|
||||
EOF
|
||||
)"
|
||||
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# k3d
|
||||
# v5 RC is needed to deterministically set the Registry port. Should be replaces with official release
|
||||
curl -s https://raw.githubusercontent.com/rancher/k3d/main/install.sh | TAG=v5.0.0-rc.4 bash
|
||||
|
||||
# kustomize
|
||||
pushd /tmp
|
||||
curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash
|
||||
popd
|
||||
sudo mv /tmp/kustomize /usr/local/bin/
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# install Krew
|
||||
# TODO (dans): ditch krew and just download the latest binaries on the path in Dockerfile
|
||||
(
|
||||
set -x; cd "$(mktemp -d)" &&
|
||||
OS="$(uname | tr '[:upper:]' '[:lower:]')" &&
|
||||
ARCH="$(uname -m | sed -e 's/x86_64/amd64/' -e 's/\(arm\)\(64\)\?.*/\1\2/' -e 's/aarch64$/arm64/')" &&
|
||||
curl -fsSLO "https://github.com/kubernetes-sigs/krew/releases/latest/download/krew.tar.gz" &&
|
||||
tar zxvf krew.tar.gz &&
|
||||
KREW=./krew-"${OS}_${ARCH}" &&
|
||||
"$KREW" install krew
|
||||
)
|
||||
|
||||
# install krew plugins
|
||||
kubectl krew install schemahero
|
||||
kubectl krew install support-bundle
|
||||
kubectl krew install preflights
|
||||
kubectl krew install view-secret
|
||||
|
||||
# Make the cache from master branch
|
||||
pushd /tmp
|
||||
git clone https://github.com/replicatedhq/troubleshoot.git
|
||||
pushd troubleshoot
|
||||
# TODO (dans): find a way to cache images on image build
|
||||
go mod download
|
||||
popd
|
||||
rm -rf kots
|
||||
popd
|
||||
|
||||
# Clone any extra repos here
|
||||
|
||||
# Autocomplete Kubernetes
|
||||
cat >> ~/.zshrc << EOF
|
||||
|
||||
source <(kubectl completion zsh)
|
||||
alias k=kubectl
|
||||
complete -F __start_kubectl k
|
||||
EOF
|
||||
|
||||
# Set Git Editor Preference
|
||||
cat >> ~/.zshrc << EOF
|
||||
|
||||
export VISUAL=vim
|
||||
export EDITOR="$VISUAL"
|
||||
EOF
|
||||
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
# modified from https://github.com/microsoft/vscode-dev-containers/blob/main/containers/codespaces-linux/.devcontainer/setup-user.sh
|
||||
# not part of the standard script library
|
||||
|
||||
USERNAME=${1:-codespace}
|
||||
SECURE_PATH_BASE=${2:-$PATH}
|
||||
|
||||
echo "Defaults secure_path=\"/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/bin:${SECURE_PATH_BASE}\"" >> /etc/sudoers.d/securepath
|
||||
|
||||
# Add user to a Docker group
|
||||
sudo -u ${USERNAME} mkdir /home/${USERNAME}/.vsonline
|
||||
groupadd -g 800 docker
|
||||
usermod -a -G docker ${USERNAME}
|
||||
|
||||
# Create user's .local/bin
|
||||
sudo -u ${USERNAME} mkdir -p /home/${USERNAME}/.local/bin
|
||||
@@ -0,0 +1,165 @@
|
||||
#!/usr/bin/env bash
|
||||
#-------------------------------------------------------------------------------------------------------------
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
|
||||
#-------------------------------------------------------------------------------------------------------------
|
||||
#
|
||||
# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/sshd.md
|
||||
# Maintainer: The VS Code and Codespaces Teams
|
||||
#
|
||||
# Syntax: ./sshd-debian.sh [SSH Port (don't use 22)] [non-root user] [start sshd now flag] [new password for user] [fix environment flag]
|
||||
#
|
||||
# Note: You can change your user's password with "sudo passwd $(whoami)" (or just "passwd" if running as root).
|
||||
|
||||
SSHD_PORT=${1:-"2222"}
|
||||
USERNAME=${2:-"automatic"}
|
||||
START_SSHD=${3:-"false"}
|
||||
NEW_PASSWORD=${4:-"skip"}
|
||||
FIX_ENVIRONMENT=${5:-"true"}
|
||||
|
||||
set -e
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Determine the appropriate non-root user
|
||||
if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then
|
||||
USERNAME=""
|
||||
POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)")
|
||||
for CURRENT_USER in ${POSSIBLE_USERS[@]}; do
|
||||
if id -u ${CURRENT_USER} > /dev/null 2>&1; then
|
||||
USERNAME=${CURRENT_USER}
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ "${USERNAME}" = "" ]; then
|
||||
USERNAME=root
|
||||
fi
|
||||
elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then
|
||||
USERNAME=root
|
||||
fi
|
||||
|
||||
# Function to run apt-get if needed
|
||||
apt_get_update_if_needed()
|
||||
{
|
||||
if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then
|
||||
echo "Running apt-get update..."
|
||||
apt-get update
|
||||
else
|
||||
echo "Skipping apt-get update."
|
||||
fi
|
||||
}
|
||||
|
||||
# Checks if packages are installed and installs them if not
|
||||
check_packages() {
|
||||
if ! dpkg -s "$@" > /dev/null 2>&1; then
|
||||
apt_get_update_if_needed
|
||||
apt-get -y install --no-install-recommends "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
# Ensure apt is in non-interactive to avoid prompts
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Install openssh-server openssh-client
|
||||
check_packages openssh-server openssh-client lsof
|
||||
|
||||
# Generate password if new password set to the word "random"
|
||||
if [ "${NEW_PASSWORD}" = "random" ]; then
|
||||
NEW_PASSWORD="$(openssl rand -hex 16)"
|
||||
EMIT_PASSWORD="true"
|
||||
elif [ "${NEW_PASSWORD}" != "skip" ]; then
|
||||
# If new password not set to skip, set it for the specified user
|
||||
echo "${USERNAME}:${NEW_PASSWORD}" | chpasswd
|
||||
fi
|
||||
|
||||
# Add user to ssh group
|
||||
if [ "${USERNAME}" != "root" ]; then
|
||||
usermod -aG ssh ${USERNAME}
|
||||
fi
|
||||
|
||||
# Setup sshd
|
||||
mkdir -p /var/run/sshd
|
||||
sed -i 's/session\s*required\s*pam_loginuid\.so/session optional pam_loginuid.so/g' /etc/pam.d/sshd
|
||||
sed -i 's/#*PermitRootLogin prohibit-password/PermitRootLogin yes/g' /etc/ssh/sshd_config
|
||||
sed -i -E "s/#*\s*Port\s+.+/Port ${SSHD_PORT}/g" /etc/ssh/sshd_config
|
||||
# Need to UsePAM so /etc/environment is processed
|
||||
sed -i -E "s/#?\s*UsePAM\s+.+/UsePAM yes/g" /etc/ssh/sshd_config
|
||||
|
||||
# Script to store variables that exist at the time the ENTRYPOINT is fired
|
||||
store_env_script="$(cat << 'EOF'
|
||||
# Wire in codespaces secret processing to zsh if present (since may have been added to image after script was run)
|
||||
if [ -f /etc/zsh/zlogin ] && ! grep '/etc/profile.d/00-restore-secrets.sh' /etc/zsh/zlogin > /dev/null 2>&1; then
|
||||
echo -e "if [ -f /etc/profile.d/00-restore-secrets.sh ]; then . /etc/profile.d/00-restore-secrets.sh; fi\n$(cat /etc/zsh/zlogin 2>/dev/null || echo '')" | sudoIf tee /etc/zsh/zlogin > /dev/null
|
||||
fi
|
||||
EOF
|
||||
)"
|
||||
|
||||
# Script to ensure login shells get the latest Codespaces secrets
|
||||
restore_secrets_script="$(cat << 'EOF'
|
||||
#!/bin/sh
|
||||
if [ "${CODESPACES}" != "true" ] || [ "${VSCDC_FIXED_SECRETS}" = "true" ] || [ ! -z "${GITHUB_CODESPACES_TOKEN}" ]; then
|
||||
# Not codespaces, already run, or secrets already in environment, so return
|
||||
return
|
||||
fi
|
||||
if [ -f /workspaces/.codespaces/shared/.env ]; then
|
||||
set -o allexport
|
||||
. /workspaces/.codespaces/shared/.env
|
||||
set +o allexport
|
||||
fi
|
||||
export VSCDC_FIXED_SECRETS=true
|
||||
EOF
|
||||
)"
|
||||
|
||||
# Write out a scripts that can be referenced as an ENTRYPOINT to auto-start sshd and fix login environments
|
||||
tee /usr/local/share/ssh-init.sh > /dev/null \
|
||||
<< 'EOF'
|
||||
#!/usr/bin/env bash
|
||||
# This script is intended to be run as root with a container that runs as root (even if you connect with a different user)
|
||||
# However, it supports running as a user other than root if passwordless sudo is configured for that same user.
|
||||
|
||||
set -e
|
||||
|
||||
sudoIf()
|
||||
{
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
sudo "$@"
|
||||
else
|
||||
"$@"
|
||||
fi
|
||||
}
|
||||
|
||||
EOF
|
||||
if [ "${FIX_ENVIRONMENT}" = "true" ]; then
|
||||
echo "${store_env_script}" >> /usr/local/share/ssh-init.sh
|
||||
echo "${restore_secrets_script}" > /etc/profile.d/00-restore-secrets.sh
|
||||
chmod +x /etc/profile.d/00-restore-secrets.sh
|
||||
# Wire in zsh if present
|
||||
if type zsh > /dev/null 2>&1; then
|
||||
echo -e "if [ -f /etc/profile.d/00-restore-secrets.sh ]; then . /etc/profile.d/00-restore-secrets.sh; fi\n$(cat /etc/zsh/zlogin 2>/dev/null || echo '')" > /etc/zsh/zlogin
|
||||
fi
|
||||
fi
|
||||
tee -a /usr/local/share/ssh-init.sh > /dev/null \
|
||||
<< 'EOF'
|
||||
|
||||
# ** Start SSH server **
|
||||
sudoIf /etc/init.d/ssh start 2>&1 | sudoIf tee /tmp/sshd.log > /dev/null
|
||||
|
||||
set +e
|
||||
exec "$@"
|
||||
EOF
|
||||
chmod +x /usr/local/share/ssh-init.sh
|
||||
|
||||
# If we should start sshd now, do so
|
||||
if [ "${START_SSHD}" = "true" ]; then
|
||||
/usr/local/share/ssh-init.sh
|
||||
fi
|
||||
|
||||
# Output success details
|
||||
echo -e "Done!\n\n- Port: ${SSHD_PORT}\n- User: ${USERNAME}"
|
||||
if [ "${EMIT_PASSWORD}" = "true" ]; then
|
||||
echo "- Password: ${NEW_PASSWORD}"
|
||||
fi
|
||||
echo -e "\nForward port ${SSHD_PORT} to your local machine and run:\n\n ssh -p ${SSHD_PORT} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o GlobalKnownHostsFile=/dev/null ${USERNAME}@localhost\n"
|
||||
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Setup the cluster
|
||||
k3d cluster create --config /etc/replicated/k3d-cluster.yaml --kubeconfig-update-default
|
||||
|
||||
# Clone any extra repos here
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Start the cluster here
|
||||
k3d cluster start replicated
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
* @replicatedhq/day2
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Report a problem with Troubleshoot
|
||||
labels: 'bug'
|
||||
|
||||
---
|
||||
|
||||
**Bug Description**
|
||||
|
||||
Write a concise description of the problem here.
|
||||
|
||||
**Expected Behavior**
|
||||
|
||||
Include a description of the expected behaviour here.
|
||||
|
||||
**Steps To Reproduce**
|
||||
|
||||
Include the commands to reproduce the issue including any output. Any information that will help us to understand the problem is useful. Feel free to paste long output into a [Github gist](https://gist.github.com) and include the link here.
|
||||
|
||||
|
||||
**Additional Context**
|
||||
|
||||
Include the following information.
|
||||
- Troubleshoot version. If you built from source, note that including the version of Go you used to build with.
|
||||
- Operating system
|
||||
- Operating system version
|
||||
- Other details that might be helpful in diagnosing the problem
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Suggest a feature
|
||||
labels: 'feature'
|
||||
|
||||
---
|
||||
|
||||
**Describe the rationale for the suggested feature.**
|
||||
|
||||
Insert a description of the rationale for the new feature here. For example, you could describe a problem that Troubleshoot doesn't address, a limitation, or an idea to improve Troubleshoot.
|
||||
|
||||
**Describe the feature**
|
||||
|
||||
Insert a description of the feature here. Be specific about how it addresses the any problems, limitations, or suggested improvements outlined in the rationale for the feature.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
|
||||
Describe alternative solutions here. Include any workarounds you've considered.
|
||||
|
||||
**Additional context**
|
||||
|
||||
Add additional context about the feature request. If the change is substantial, consider attaching files to the issue outlining architectural changes, data flows, file formats etc., anything that helps describe the requested change.
|
||||
@@ -0,0 +1,15 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "gomod" # See documentation for possible values
|
||||
directory: "/" # Location of package manifests
|
||||
labels:
|
||||
- "dependencies"
|
||||
- "go"
|
||||
- "type::chore"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
@@ -0,0 +1,196 @@
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
tags:
|
||||
- "v*.*.*"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-18.04
|
||||
steps:
|
||||
- uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: "1.17"
|
||||
|
||||
- name: setup env
|
||||
run: |
|
||||
echo "GOPATH=$(go env GOPATH)" >> $GITHUB_ENV
|
||||
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
|
||||
shell: bash
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- run: make
|
||||
|
||||
compile-preflight:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
steps:
|
||||
- uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: '1.17'
|
||||
- name: setup env
|
||||
run: |
|
||||
echo "GOPATH=$(go env GOPATH)" >> $GITHUB_ENV
|
||||
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
|
||||
shell: bash
|
||||
- uses: actions/checkout@master
|
||||
- run: make preflight
|
||||
- uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: preflight
|
||||
path: bin/preflight
|
||||
|
||||
validate-preflight:
|
||||
runs-on: ubuntu-latest
|
||||
needs: compile-preflight
|
||||
steps:
|
||||
- name: Download preflight binary
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: preflight
|
||||
path: bin/
|
||||
- uses: engineerd/setup-kind@v0.5.0
|
||||
- run: chmod +x bin/preflight
|
||||
- run: |
|
||||
./bin/preflight --interactive=false --format=json https://preflight.replicated.com > result.json
|
||||
cat result.json
|
||||
|
||||
EXIT_STATUS=0
|
||||
if grep -q "was not collected" result.json; then
|
||||
echo "Some files were not collected"
|
||||
EXIT_STATUS=1
|
||||
fi
|
||||
|
||||
if (( `jq '.pass | length' result.json` < 1 )); then
|
||||
echo "No passing preflights found"
|
||||
EXIT_STATUS=1
|
||||
fi
|
||||
|
||||
if (( `jq '.warn | length' result.json` < 1 )); then
|
||||
echo "No warnings found"
|
||||
EXIT_STATUS=1
|
||||
fi
|
||||
|
||||
if (( `jq '.fail | length' result.json` < 1 )); then
|
||||
echo "No failed preflights found"
|
||||
EXIT_STATUS=1
|
||||
fi
|
||||
|
||||
exit $EXIT_STATUS
|
||||
|
||||
compile-supportbundle:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
steps:
|
||||
- uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: '1.17'
|
||||
- name: setup env
|
||||
run: |
|
||||
echo "GOPATH=$(go env GOPATH)" >> $GITHUB_ENV
|
||||
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
|
||||
shell: bash
|
||||
- uses: actions/checkout@master
|
||||
- run: make support-bundle
|
||||
- uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: support-bundle
|
||||
path: bin/support-bundle
|
||||
|
||||
validate-supportbundle:
|
||||
runs-on: ubuntu-latest
|
||||
needs: compile-supportbundle
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Download support-bundle binary
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: support-bundle
|
||||
path: bin/
|
||||
- uses: engineerd/setup-kind@v0.5.0
|
||||
- run: chmod +x bin/support-bundle
|
||||
- run: ./bin/support-bundle ./examples/support-bundle/sample-collectors.yaml
|
||||
- run: ./bin/support-bundle ./examples/support-bundle/sample-supportbundle.yaml
|
||||
- run: ./bin/support-bundle https://kots.io
|
||||
|
||||
goreleaser-test:
|
||||
runs-on: ubuntu-latest
|
||||
if: startsWith(github.ref, 'refs/tags/v') != true
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Unshallow
|
||||
run: git fetch --prune --unshallow
|
||||
|
||||
- uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: "1.17"
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v2
|
||||
with:
|
||||
version: "v0.183.0"
|
||||
args: build --rm-dist --snapshot --config deploy/.goreleaser.yaml
|
||||
|
||||
goreleaser:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- validate-preflight
|
||||
- validate-supportbundle
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- uses: azure/docker-login@v1
|
||||
with:
|
||||
username: repldeploy2
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
|
||||
- name: Unshallow
|
||||
run: git fetch --prune --unshallow
|
||||
|
||||
- uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: "1.17"
|
||||
|
||||
- uses: sigstore/cosign-installer@main
|
||||
with:
|
||||
cosign-release: "v1.2.1"
|
||||
|
||||
- name: Get Cosign Key
|
||||
run: |
|
||||
echo $COSIGN_KEY | base64 -d > ./cosign.key
|
||||
env:
|
||||
COSIGN_KEY: ${{secrets.COSIGN_KEY}}
|
||||
|
||||
- name: Generate SBOM
|
||||
run: |
|
||||
make sbom
|
||||
env:
|
||||
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
|
||||
COSIGN_KEY: ${{ secrets.COSIGN_KEY }}
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v2
|
||||
with:
|
||||
version: "v0.183.0"
|
||||
args: release --rm-dist --config deploy/.goreleaser.yaml
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
||||
- name: Update new preflight version in krew-index
|
||||
uses: rajatjindal/krew-release-bot@v0.0.38
|
||||
with:
|
||||
krew_template_file: deploy/krew/preflight.yaml
|
||||
- name: Update new support-bundle version in krew-index
|
||||
uses: rajatjindal/krew-release-bot@v0.0.38
|
||||
with:
|
||||
krew_template_file: deploy/krew/support-bundle.yaml
|
||||
@@ -27,3 +27,10 @@ vendor
|
||||
*~
|
||||
|
||||
dist
|
||||
try.sh
|
||||
|
||||
.vscode/
|
||||
workspace.*
|
||||
|
||||
cosign.key
|
||||
sbom/
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
oss@replicated.com.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
@@ -0,0 +1,11 @@
|
||||
# Contributing to Troubleshoot
|
||||
|
||||
Thank you for your interest in Troubleshoot, we welcome your participation. Please familiarize yourself with our [Code of Conduct](https://github.com/replicatedhq/troubleshoot/blob/master/CODE_OF_CONDUCT.md) prior to contributing. There are a number of ways to participate in Troubleshoot as outlined below:
|
||||
|
||||
## Issues
|
||||
- [Request a New Feature](https://github.com/replicatedhq/troubleshoot/issues/new?assignees=&labels=feature&template=feature_enhancement.md) Create an issue to add functionality that addresses a problem or adds an enhancement.
|
||||
- [Report a Bug](https://github.com/replicatedhq/troubleshoot/issues/new?assignees=&labels=bug&template=bug_report.md) Report a problem or unexpected behaviour with Troubleshoot.
|
||||
|
||||
## Pull Requests
|
||||
|
||||
If you are interested in contributing a change to the code or documentation please open a pull request with your set of changes. The pull request will be reviewed in a timely manner.
|
||||
@@ -1,46 +1,65 @@
|
||||
|
||||
# Image URL to use all building/pushing image targets
|
||||
IMG ?= controller:latest
|
||||
export GO111MODULE=on
|
||||
|
||||
all: test manager
|
||||
SHELL := /bin/bash -o pipefail
|
||||
VERSION_PACKAGE = github.com/replicatedhq/troubleshoot/pkg/version
|
||||
VERSION ?=`git describe --tags --dirty`
|
||||
DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"`
|
||||
|
||||
GIT_TREE = $(shell git rev-parse --is-inside-work-tree 2>/dev/null)
|
||||
ifneq "$(GIT_TREE)" ""
|
||||
define GIT_UPDATE_INDEX_CMD
|
||||
git update-index --assume-unchanged
|
||||
endef
|
||||
define GIT_SHA
|
||||
`git rev-parse HEAD`
|
||||
endef
|
||||
else
|
||||
define GIT_UPDATE_INDEX_CMD
|
||||
echo "Not a git repo, skipping git update-index"
|
||||
endef
|
||||
define GIT_SHA
|
||||
""
|
||||
endef
|
||||
endif
|
||||
|
||||
define LDFLAGS
|
||||
-ldflags "\
|
||||
-s -w \
|
||||
-X ${VERSION_PACKAGE}.version=${VERSION} \
|
||||
-X ${VERSION_PACKAGE}.gitSHA=${GIT_SHA} \
|
||||
-X ${VERSION_PACKAGE}.buildTime=${DATE} \
|
||||
"
|
||||
endef
|
||||
|
||||
BUILDFLAGS = -tags "netgo containers_image_ostree_stub exclude_graphdriver_devicemapper exclude_graphdriver_btrfs containers_image_openpgp" -installsuffix netgo
|
||||
|
||||
all: test
|
||||
|
||||
.PHONY: ffi
|
||||
ffi: fmt vet
|
||||
go build ${BUILDFLAGS} ${LDFLAGS} -o bin/troubleshoot.so -buildmode=c-shared ffi/main.go
|
||||
|
||||
# Run tests
|
||||
test: generate fmt vet manifests
|
||||
go test ./pkg/... ./cmd/... -coverprofile cover.out
|
||||
|
||||
.PHONY: manager
|
||||
manager: generate fmt vet
|
||||
go build -o bin/manager github.com/replicatedhq/troubleshoot/cmd/manager
|
||||
test: generate fmt vet
|
||||
go test ${BUILDFLAGS} ./pkg/... ./cmd/... -coverprofile cover.out
|
||||
|
||||
.PHONY: support-bundle
|
||||
support-bundle: generate fmt vet
|
||||
go build -o bin/support-bundle github.com/replicatedhq/troubleshoot/cmd/troubleshoot
|
||||
|
||||
.PHONY: collector
|
||||
collector: generate fmt vet
|
||||
go build -o bin/collector github.com/replicatedhq/troubleshoot/cmd/collector
|
||||
go build ${BUILDFLAGS} ${LDFLAGS} -o bin/support-bundle github.com/replicatedhq/troubleshoot/cmd/troubleshoot
|
||||
|
||||
.PHONY: preflight
|
||||
preflight: generate fmt vet
|
||||
go build -o bin/preflight github.com/replicatedhq/troubleshoot/cmd/preflight
|
||||
go build ${BUILDFLAGS} ${LDFLAGS} -o bin/preflight github.com/replicatedhq/troubleshoot/cmd/preflight
|
||||
|
||||
.PHONY: run
|
||||
run: generate fmt vet
|
||||
TROUBLESHOOT_EXTERNAL_MANAGER=1 go run ./cmd/manager/main.go
|
||||
|
||||
.PHONY: install
|
||||
install: manifests
|
||||
kubectl apply -f config/crds
|
||||
|
||||
.PHONY: deploy
|
||||
deploy: manifests
|
||||
kubectl apply -f config/crds
|
||||
kustomize build config/default | kubectl apply -f -
|
||||
|
||||
.PHONY: manifests
|
||||
manifests:
|
||||
controller-gen paths=./pkg/apis/... output:dir=./config/crds
|
||||
.PHONY: analyze
|
||||
analyze: generate fmt vet
|
||||
go build ${BUILDFLAGS} ${LDFLAGS} -o bin/analyze github.com/replicatedhq/troubleshoot/cmd/analyze
|
||||
|
||||
.PHONY: collect
|
||||
collect: generate fmt vet
|
||||
go build ${BUILDFLAGS} ${LDFLAGS} -o bin/collect github.com/replicatedhq/troubleshoot/cmd/collect
|
||||
|
||||
.PHONY: fmt
|
||||
fmt:
|
||||
@@ -48,64 +67,105 @@ fmt:
|
||||
|
||||
.PHONY: vet
|
||||
vet:
|
||||
go vet ./pkg/... ./cmd/...
|
||||
go vet ${BUILDFLAGS} ./pkg/... ./cmd/...
|
||||
|
||||
.PHONY: generate
|
||||
generate: controller-gen # client-gen
|
||||
controller-gen object:headerFile=./hack/boilerplate.go.txt paths=./pkg/apis/...
|
||||
# client-gen --output-package=github.com/replicatedhq/troubleshoot/pkg/client --clientset-name troubleshootclientset --input-base github.com/replicatedhq/troubleshoot/pkg/apis --input troubleshoot/v1beta1 -h ./hack/boilerplate.go.txt
|
||||
generate: controller-gen client-gen
|
||||
$(CONTROLLER_GEN) \
|
||||
object:headerFile=./hack/boilerplate.go.txt paths=./pkg/apis/...
|
||||
$(CLIENT_GEN) \
|
||||
--output-package=github.com/replicatedhq/troubleshoot/pkg/client \
|
||||
--clientset-name troubleshootclientset \
|
||||
--input-base github.com/replicatedhq/troubleshoot/pkg/apis \
|
||||
--input troubleshoot/v1beta1 \
|
||||
--input troubleshoot/v1beta2 \
|
||||
-h ./hack/boilerplate.go.txt
|
||||
|
||||
.PHONY: openapischema
|
||||
openapischema: controller-gen
|
||||
controller-gen crd +output:dir=./config/crds paths=./pkg/apis/troubleshoot/v1beta1
|
||||
controller-gen crd +output:dir=./config/crds paths=./pkg/apis/troubleshoot/v1beta2
|
||||
|
||||
.PHONY: schemas
|
||||
schemas: fmt vet openapischema
|
||||
go build ${LDFLAGS} -o bin/schemagen github.com/replicatedhq/troubleshoot/cmd/schemagen
|
||||
./bin/schemagen --output-dir ./schemas
|
||||
|
||||
# find or download controller-gen
|
||||
# download controller-gen if necessary
|
||||
controller-gen:
|
||||
ifeq (, $(shell which controller-gen))
|
||||
go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.2.0-beta.2
|
||||
CONTROLLER_GEN=$(shell go env GOPATH)/bin/controller-gen
|
||||
else
|
||||
go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.7.0
|
||||
CONTROLLER_GEN=$(shell which controller-gen)
|
||||
endif
|
||||
|
||||
# find or download client-gen
|
||||
.PHONY: client-gen
|
||||
client-gen:
|
||||
ifeq (, $(shell which client-gen))
|
||||
go get k8s.io/code-generator/cmd/client-gen@kubernetes-1.13.5
|
||||
go install k8s.io/code-generator/cmd/client-gen@v0.22.2
|
||||
CLIENT_GEN=$(shell go env GOPATH)/bin/client-gen
|
||||
else
|
||||
CLIENT_GEN=$(shell which client-gen)
|
||||
endif
|
||||
|
||||
.PHONY: snapshot-release
|
||||
snapshot-release:
|
||||
curl -sL https://git.io/goreleaser | bash -s -- --rm-dist --snapshot --config deploy/.goreleaser.snapshot.yml
|
||||
docker push replicated/troubleshoot:alpha
|
||||
docker push replicated/preflight:alpha
|
||||
docker push replicated/troubleshoot-manager:alpha
|
||||
|
||||
.PHONY: release
|
||||
release: export GITHUB_TOKEN = $(shell echo ${GITHUB_TOKEN_TROUBLESHOOT})
|
||||
release:
|
||||
curl -sL https://git.io/goreleaser | bash -s -- --rm-dist --config deploy/.goreleaser.yml
|
||||
|
||||
.PHONY: snapshot-release
|
||||
snapshot-release:
|
||||
curl -sL https://git.io/goreleaser | bash -s -- --rm-dist --snapshot --config deploy/.goreleaser.snapshot.yml
|
||||
docker push replicated/troubleshoot:alpha
|
||||
docker push replicated/preflight:alpha
|
||||
|
||||
.PHONY: local-release
|
||||
local-release:
|
||||
curl -sL https://git.io/goreleaser | bash -s -- --rm-dist --snapshot --config deploy/.goreleaser.local.yml
|
||||
curl -sL https://git.io/goreleaser | bash -s -- --rm-dist --snapshot --config deploy/.goreleaser.yaml
|
||||
docker tag replicated/troubleshoot:alpha localhost:32000/troubleshoot:alpha
|
||||
docker tag replicated/preflight:alpha localhost:32000/preflight:alpha
|
||||
docker tag replicated/troubleshoot-manager:alpha localhost:32000/troubleshoot-manager:alpha
|
||||
docker push localhost:32000/troubleshoot:alpha
|
||||
docker push localhost:32000/preflight:alpha
|
||||
docker push localhost:32000/troubleshoot-manager:alpha
|
||||
|
||||
.PHONY: run-preflight
|
||||
run-preflight: preflight
|
||||
./bin/preflight \
|
||||
--image=localhost:32000/troubleshoot:alpha \
|
||||
--pullpolicy=Always \
|
||||
./examples/preflight/sample-preflight.yaml
|
||||
./bin/preflight ./examples/preflight/sample-preflight.yaml
|
||||
|
||||
.PHONY: run-troubleshoot
|
||||
run-troubleshoot: support-bundle
|
||||
./bin/support-bundle \
|
||||
--image=localhost:32000/troubleshoot:alpha \
|
||||
--pullpolicy=Always \
|
||||
./examples/troubleshoot/sample-troubleshoot.yaml
|
||||
./bin/support-bundle ./examples/support-bundle/sample-supportbundle.yaml
|
||||
|
||||
.PHONY: run-analyze
|
||||
run-analyze: analyze
|
||||
./bin/analyze --analyzers ./examples/support-bundle/sample-analyzers.yaml ./support-bundle.tar.gz
|
||||
|
||||
.PHONY: init-sbom
|
||||
init-sbom:
|
||||
mkdir -p sbom/spdx sbom/assets
|
||||
|
||||
.PHONY: install-spdx-sbom-generator
|
||||
install-spdx-sbom-generator: init-sbom
|
||||
./scripts/initialize-sbom-build.sh
|
||||
|
||||
SPDX_GENERATOR=./sbom/spdx-sbom-generator
|
||||
|
||||
.PHONY: generate-sbom
|
||||
generate-sbom: install-spdx-sbom-generator
|
||||
$(SPDX_GENERATOR) -o ./sbom/spdx
|
||||
|
||||
sbom/assets/troubleshoot-sbom.tgz: generate-sbom
|
||||
tar -czf sbom/assets/troubleshoot-sbom.tgz sbom/spdx/*.spdx
|
||||
|
||||
sbom: sbom/assets/troubleshoot-sbom.tgz
|
||||
cosign sign-blob -key cosign.key sbom/assets/troubleshoot-sbom.tgz > sbom/assets/troubleshoot-sbom.tgz.sig
|
||||
cosign public-key -key cosign.key -outfile sbom/assets/key.pub
|
||||
|
||||
longhorn:
|
||||
git clone https://github.com/longhorn/longhorn-manager.git
|
||||
cd longhorn-manager && git checkout v1.2.2 && cd ..
|
||||
rm -rf pkg/longhorn
|
||||
mv longhorn-manager/k8s/pkg pkg/longhorn
|
||||
mv longhorn-manager/types pkg/longhorn/types
|
||||
mv longhorn-manager/util pkg/longhorn/util
|
||||
rm -rf pkg/longhorn/util/daemon
|
||||
rm -rf pkg/longhorn/util/server
|
||||
find pkg/longhorn -type f | xargs sed -i "s/github.com\/longhorn\/longhorn-manager\/k8s\/pkg/github.com\/replicatedhq\/troubleshoot\/pkg\/longhorn/g"
|
||||
find pkg/longhorn -type f | xargs sed -i "s/github.com\/longhorn\/longhorn-manager\/types/github.com\/replicatedhq\/troubleshoot\/pkg\/longhorn\/types/g"
|
||||
find pkg/longhorn -type f | xargs sed -i "s/github.com\/longhorn\/longhorn-manager\/util/github.com\/replicatedhq\/troubleshoot\/pkg\/longhorn\/util/g"
|
||||
rm -rf longhorn-manager
|
||||
|
||||
@@ -1,23 +1,54 @@
|
||||
# Replicated Troubleshoot
|
||||
|
||||
Replicated Troubleshoot is a CLI tool that provides pre-installation cluster conformance testing and validation (preflight checks) and post-installation troubleshooting and diagnostics (support bundles).
|
||||
Replicated Troubleshoot is a framework for collecting, redacting, and analyzing highly customizable diagnostic information about a Kubernetes cluster. Troubleshoot specs are created by 3rd-party application developers/maintainers and run by cluster operators in the initial and ongoing operation of those applications.
|
||||
|
||||
Troubleshoot provides two CLI tools as kubectl plugins (using [Krew](https://krew.dev)): `kubectl preflight` and `kubectl support-bundle`. Preflight provides pre-installation cluster conformance testing and validation (preflight checks) and support-bundle provides post-installation troubleshooting and diagnostics (support bundles).
|
||||
|
||||
## Preflight Checks
|
||||
Preflight checks are an easy-to-run set of conformance tests that can be written to verify that specific requirements in a cluster are met.
|
||||
|
||||
To run a sample preflight check from a sample application, [install the preflight kubectl plugin](https://help.replicated.com/docs/troubleshoot/kubernetes/preflight/executing/) and run:
|
||||
To run a sample preflight check from a sample application, install the preflight kubectl plugin:
|
||||
|
||||
```shell
|
||||
curl https://krew.sh/preflight | bash
|
||||
```
|
||||
and run:
|
||||
|
||||
```shell
|
||||
kubectl preflight https://preflight.replicated.com
|
||||
```
|
||||
|
||||
For a full description of the supported preflight checks, visit the [docs](https://help.replicated.com/docs/troubleshoot/kubernetes/analysis/).
|
||||
For more details on creating the custom resource files that drive preflight checks, visit [creating preflight checks](https://troubleshoot.sh/docs/preflight/introduction/).
|
||||
|
||||
|
||||
## Support Bundle
|
||||
A support bundle is an archive that's created in-cluster, by collecting logs, cluster information and executing various commands. After creating a support bundle, the cluster operator will normally deliver it to the application vendor for analysis and remote debugging.
|
||||
A support bundle is an archive that's created in-cluster, by collecting logs and cluster information, and executing specified commands (including redaction of sensitive information). After creating a support bundle, the cluster operator will normally deliver it to the 3rd-party application vendor for analysis and disconnected debugging. Another Replicated project, [KOTS](https://github.com/replicatedhq/kots), provides k8s apps an in-cluster UI for processing support bundles and viewing analyzers (as well as support bundle collection).
|
||||
|
||||
To collect a sample support bundle, [install the troubleshoot kubectl plugin](https://help.replicated.com/docs/troubleshoot/kubernetes/support-bundle/collecting/) and run:
|
||||
To collect a sample support bundle, install the troubleshoot kubectl plugin:
|
||||
|
||||
```shell
|
||||
kubectl support-bundle https://troubleshoot.replicated.com
|
||||
curl https://krew.sh/support-bundle | bash
|
||||
```
|
||||
and run:
|
||||
|
||||
```shell
|
||||
kubectl support-bundle https://support-bundle.replicated.com
|
||||
```
|
||||
For more details on creating the custom resource files that drive support-bundle collection, visit [creating collectors](https://troubleshoot.sh/docs/collect/) and [creating analyzers](https://troubleshoot.sh/docs/analyze/).
|
||||
|
||||
# Community
|
||||
|
||||
For questions about using Troubleshoot, there's a [Replicated Community](https://help.replicated.com/community) forum, and a [#app-troubleshoot channel in Kubernetes Slack](https://kubernetes.slack.com/channels/app-troubleshoot).
|
||||
|
||||
# Software Bill of Materials
|
||||
A signed SBOM that includes Troubleshoot dependencies is included in each release.
|
||||
- **troubleshoot-sbom.tgz** contains a software bill of materials for Troubleshoot.
|
||||
- **troubleshoot-sbom.tgz.sig** is the digital signature for troubleshoot-sbom.tgz
|
||||
- **key.pub** is the public key from the key pair used to sign troubleshoot-sbom.tgz
|
||||
|
||||
The following example illustrates using [cosign](https://github.com/sigstore/cosign) to verify that **troubleshoot-sbom.tgz** has
|
||||
not been tampered with.
|
||||
```shell
|
||||
$ cosign verify-blob -key key.pub -signature troubleshoot-sbom.tgz.sig troubleshoot-sbom.tgz
|
||||
Verified OK
|
||||
```
|
||||
@@ -0,0 +1,54 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/logger"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func RootCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "analyze [url]",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Short: "Analyze a support bundle",
|
||||
Long: `Run a series of analyzers on a support bundle archive`,
|
||||
SilenceUsage: true,
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
viper.BindPFlags(cmd.Flags())
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
v := viper.GetViper()
|
||||
|
||||
logger.SetQuiet(v.GetBool("quiet"))
|
||||
|
||||
return runAnalyzers(v, args[0])
|
||||
},
|
||||
}
|
||||
|
||||
cobra.OnInitialize(initConfig)
|
||||
|
||||
cmd.Flags().String("analyzers", "", "filename or url of the analyzers to use")
|
||||
|
||||
viper.BindPFlags(cmd.Flags())
|
||||
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
|
||||
|
||||
k8sutil.AddFlags(cmd.Flags())
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func InitAndExecute() {
|
||||
if err := RootCmd().Execute(); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func initConfig() {
|
||||
viper.SetEnvPrefix("TROUBLESHOOT")
|
||||
viper.AutomaticEnv()
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/replicatedhq/troubleshoot/cmd/util"
|
||||
analyzer "github.com/replicatedhq/troubleshoot/pkg/analyze"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func runAnalyzers(v *viper.Viper, bundlePath string) error {
|
||||
specPath := v.GetString("analyzers")
|
||||
|
||||
specContent := ""
|
||||
var err error
|
||||
if _, err = os.Stat(specPath); err == nil {
|
||||
b, err := ioutil.ReadFile(specPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
specContent = string(b)
|
||||
} else {
|
||||
if !util.IsURL(specPath) {
|
||||
return fmt.Errorf("%s is not a URL and was not found (err %s)", specPath, err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", specPath, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("User-Agent", "Replicated_Analyzer/v1beta1")
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
specContent = string(body)
|
||||
}
|
||||
|
||||
analyzeResults, err := analyzer.DownloadAndAnalyze(bundlePath, specContent)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to download and analyze bundle")
|
||||
}
|
||||
|
||||
for _, analyzeResult := range analyzeResults {
|
||||
if analyzeResult.IsPass {
|
||||
fmt.Printf("Pass: %s\n %s\n", analyzeResult.Title, analyzeResult.Message)
|
||||
} else if analyzeResult.IsWarn {
|
||||
fmt.Printf("Warn: %s\n %s\n", analyzeResult.Title, analyzeResult.Message)
|
||||
} else if analyzeResult.IsFail {
|
||||
fmt.Printf("Fail: %s\n %s\n", analyzeResult.Title, analyzeResult.Message)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/replicatedhq/troubleshoot/cmd/analyze/cli"
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cli.InitAndExecute()
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/logger"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func RootCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "collect [url]",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Short: "Run a collector",
|
||||
Long: `Run a collector and output the results.`,
|
||||
SilenceUsage: true,
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
viper.BindPFlags(cmd.Flags())
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
v := viper.GetViper()
|
||||
|
||||
logger.SetQuiet(v.GetBool("quiet"))
|
||||
return runCollect(v, args[0])
|
||||
},
|
||||
}
|
||||
|
||||
cobra.OnInitialize(initConfig)
|
||||
|
||||
cmd.AddCommand(VersionCmd())
|
||||
|
||||
cmd.Flags().StringSlice("redactors", []string{}, "names of the additional redactors to use")
|
||||
cmd.Flags().Bool("redact", true, "enable/disable default redactions")
|
||||
cmd.Flags().String("format", "json", "output format, one of json or raw.")
|
||||
cmd.Flags().String("collector-image", "", "the full name of the collector image to use")
|
||||
cmd.Flags().String("collector-pull-policy", "", "the pull policy of the collector image")
|
||||
cmd.Flags().String("selector", "", "selector (label query) to filter remote collection nodes on.")
|
||||
cmd.Flags().Bool("collect-without-permissions", false, "always generate a support bundle, even if it some require additional permissions")
|
||||
|
||||
// hidden in favor of the `insecure-skip-tls-verify` flag
|
||||
cmd.Flags().Bool("allow-insecure-connections", false, "when set, do not verify TLS certs when retrieving spec and reporting results")
|
||||
cmd.Flags().MarkHidden("allow-insecure-connections")
|
||||
|
||||
viper.BindPFlags(cmd.Flags())
|
||||
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
|
||||
|
||||
k8sutil.AddFlags(cmd.Flags())
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func InitAndExecute() {
|
||||
if err := RootCmd().Execute(); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func initConfig() {
|
||||
viper.SetEnvPrefix("TROUBLESHOOT")
|
||||
viper.AutomaticEnv()
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/replicatedhq/troubleshoot/cmd/util"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/scheme"
|
||||
troubleshootclientsetscheme "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/scheme"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/docrewrite"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/specs"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/supportbundle"
|
||||
"github.com/spf13/viper"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultTimeout = 30 * time.Second
|
||||
)
|
||||
|
||||
func runCollect(v *viper.Viper, arg string) error {
|
||||
go func() {
|
||||
signalChan := make(chan os.Signal, 1)
|
||||
signal.Notify(signalChan, os.Interrupt)
|
||||
<-signalChan
|
||||
os.Exit(0)
|
||||
}()
|
||||
|
||||
var collectorContent []byte
|
||||
var err error
|
||||
if strings.HasPrefix(arg, "secret/") {
|
||||
// format secret/namespace-name/secret-name
|
||||
pathParts := strings.Split(arg, "/")
|
||||
if len(pathParts) != 3 {
|
||||
return errors.Errorf("path %s must have 3 components", arg)
|
||||
}
|
||||
|
||||
spec, err := specs.LoadFromSecret(pathParts[1], pathParts[2], "collect-spec")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get spec from secret")
|
||||
}
|
||||
|
||||
collectorContent = spec
|
||||
} else if _, err = os.Stat(arg); err == nil {
|
||||
b, err := ioutil.ReadFile(arg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
collectorContent = b
|
||||
} else {
|
||||
if !util.IsURL(arg) {
|
||||
return fmt.Errorf("%s is not a URL and was not found (err %s)", arg, err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", arg, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("User-Agent", "Replicated_Collect/v1beta2")
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
collectorContent = body
|
||||
}
|
||||
|
||||
collectorContent, err = docrewrite.ConvertToV1Beta2(collectorContent)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to convert to v1beta2")
|
||||
}
|
||||
|
||||
multidocs := strings.Split(string(collectorContent), "\n---\n")
|
||||
|
||||
troubleshootclientsetscheme.AddToScheme(scheme.Scheme)
|
||||
decode := scheme.Codecs.UniversalDeserializer().Decode
|
||||
|
||||
additionalRedactors := &troubleshootv1beta2.Redactor{}
|
||||
for idx, redactor := range v.GetStringSlice("redactors") {
|
||||
redactorObj, err := supportbundle.GetRedactorFromURI(redactor)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to get redactor spec %s, #%d", redactor, idx)
|
||||
}
|
||||
|
||||
if redactorObj != nil {
|
||||
additionalRedactors.Spec.Redactors = append(additionalRedactors.Spec.Redactors, redactorObj.Spec.Redactors...)
|
||||
}
|
||||
}
|
||||
|
||||
for i, additionalDoc := range multidocs {
|
||||
if i == 0 {
|
||||
continue
|
||||
}
|
||||
additionalDoc, err := docrewrite.ConvertToV1Beta2([]byte(additionalDoc))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to convert to v1beta2")
|
||||
}
|
||||
obj, _, err := decode(additionalDoc, nil, nil)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to parse additional doc %d", i)
|
||||
}
|
||||
multidocRedactors, ok := obj.(*troubleshootv1beta2.Redactor)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
additionalRedactors.Spec.Redactors = append(additionalRedactors.Spec.Redactors, multidocRedactors.Spec.Redactors...)
|
||||
}
|
||||
|
||||
// make sure we don't block any senders
|
||||
progressCh := make(chan interface{})
|
||||
defer close(progressCh)
|
||||
go func() {
|
||||
for range progressCh {
|
||||
}
|
||||
}()
|
||||
|
||||
restConfig, err := k8sutil.GetRESTConfig()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to convert kube flags to rest config")
|
||||
}
|
||||
|
||||
labelSelector, err := labels.Parse(v.GetString("selector"))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to parse selector")
|
||||
}
|
||||
|
||||
namespace := v.GetString("namespace")
|
||||
if namespace == "" {
|
||||
namespace = "default"
|
||||
}
|
||||
|
||||
timeout := v.GetDuration("request-timeout")
|
||||
if timeout == 0 {
|
||||
timeout = defaultTimeout
|
||||
}
|
||||
|
||||
createOpts := collect.CollectorRunOpts{
|
||||
CollectWithoutPermissions: v.GetBool("collect-without-permissions"),
|
||||
KubernetesRestConfig: restConfig,
|
||||
Image: v.GetString("collector-image"),
|
||||
PullPolicy: v.GetString("collector-pullpolicy"),
|
||||
LabelSelector: labelSelector.String(),
|
||||
Namespace: namespace,
|
||||
Timeout: timeout,
|
||||
ProgressChan: progressCh,
|
||||
}
|
||||
|
||||
// we only support HostCollector or RemoteCollector kinds.
|
||||
hostCollector, err := collect.ParseHostCollectorFromDoc([]byte(multidocs[0]))
|
||||
if err == nil {
|
||||
results, err := collect.CollectHost(hostCollector, additionalRedactors, createOpts)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to collect from host")
|
||||
}
|
||||
return showHostStdoutResults(v.GetString("format"), hostCollector.Name, results)
|
||||
}
|
||||
|
||||
remoteCollector, err := collect.ParseRemoteCollectorFromDoc([]byte(multidocs[0]))
|
||||
if err == nil {
|
||||
results, err := collect.CollectRemote(remoteCollector, additionalRedactors, createOpts)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to collect from remote host(s)")
|
||||
}
|
||||
return showRemoteStdoutResults(v.GetString("format"), remoteCollector.Name, results)
|
||||
}
|
||||
|
||||
return errors.New("failed to parse hostCollector or remoteCollector")
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
)
|
||||
|
||||
const (
|
||||
// FormatJSON is intended for CLI output.
|
||||
FormatJSON = "json"
|
||||
|
||||
// FormatRaw is intended for consumption by a remote collector. Output is a
|
||||
// string of quoted JSON.
|
||||
FormatRaw = "raw"
|
||||
)
|
||||
|
||||
func showHostStdoutResults(format string, collectName string, results *collect.HostCollectResult) error {
|
||||
switch format {
|
||||
case FormatJSON:
|
||||
return showHostStdoutResultsJSON(collectName, results.AllCollectedData)
|
||||
case FormatRaw:
|
||||
return showHostStdoutResultsRaw(collectName, results.AllCollectedData)
|
||||
default:
|
||||
return errors.Errorf("unknown output format: %q", format)
|
||||
}
|
||||
}
|
||||
|
||||
func showRemoteStdoutResults(format string, collectName string, results *collect.RemoteCollectResult) error {
|
||||
switch format {
|
||||
case FormatJSON:
|
||||
return showRemoteStdoutResultsJSON(collectName, results.AllCollectedData)
|
||||
case FormatRaw:
|
||||
return errors.Errorf("raw format not supported for remote collectors")
|
||||
default:
|
||||
return errors.Errorf("unknown output format: %q", format)
|
||||
}
|
||||
}
|
||||
|
||||
func showHostStdoutResultsJSON(collectName string, results map[string][]byte) error {
|
||||
output := make(map[string]interface{})
|
||||
for file, collectorResult := range results {
|
||||
var collectedItems map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(collectorResult), &collectedItems); err != nil {
|
||||
return errors.Wrap(err, "failed to marshal collector results")
|
||||
}
|
||||
output[file] = collectedItems
|
||||
}
|
||||
|
||||
formatted, err := json.MarshalIndent(output, "", " ")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to convert output to json")
|
||||
}
|
||||
|
||||
fmt.Print(string(formatted))
|
||||
return nil
|
||||
}
|
||||
|
||||
// showHostStdoutResultsRaw outputs the collector output as a string of quoted json.
|
||||
func showHostStdoutResultsRaw(collectName string, results map[string][]byte) error {
|
||||
strData := map[string]string{}
|
||||
for k, v := range results {
|
||||
strData[k] = string(v)
|
||||
}
|
||||
formatted, err := json.MarshalIndent(strData, "", " ")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to convert output to json")
|
||||
}
|
||||
fmt.Print(string(formatted))
|
||||
return nil
|
||||
}
|
||||
|
||||
func showRemoteStdoutResultsJSON(collectName string, results map[string][]byte) error {
|
||||
type CollectorResult map[string]interface{}
|
||||
type NodeResult map[string]CollectorResult
|
||||
|
||||
var output = make(map[string]NodeResult)
|
||||
|
||||
for node, result := range results {
|
||||
var nodeResult map[string]string
|
||||
if err := json.Unmarshal(result, &nodeResult); err != nil {
|
||||
return errors.Wrap(err, "failed to marshal node results")
|
||||
}
|
||||
nr := make(NodeResult)
|
||||
for file, collectorResult := range nodeResult {
|
||||
var collectedItems map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(collectorResult), &collectedItems); err != nil {
|
||||
return errors.Wrap(err, "failed to marshal collector results")
|
||||
}
|
||||
nr[file] = collectedItems
|
||||
}
|
||||
output[node] = nr
|
||||
}
|
||||
|
||||
formatted, err := json.MarshalIndent(output, "", " ")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to convert output to json")
|
||||
}
|
||||
fmt.Print(string(formatted))
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/replicatedhq/troubleshoot/pkg/version"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func VersionCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Print the current version and exit",
|
||||
Long: `Print the current version and exit`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
fmt.Printf("Replicated Collect %s\n", version.Version())
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/replicatedhq/troubleshoot/cmd/collect/cli"
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cli.InitAndExecute()
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func RootCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "collector",
|
||||
Short: "Run the cluster-side server for bundle collection",
|
||||
Long: ``,
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
cobra.OnInitialize(initConfig)
|
||||
|
||||
cmd.AddCommand(Run())
|
||||
cmd.AddCommand(Server())
|
||||
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
|
||||
return cmd
|
||||
}
|
||||
|
||||
func InitAndExecute() {
|
||||
if err := RootCmd().Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func initConfig() {
|
||||
viper.SetEnvPrefix("TROUBLESHOOT")
|
||||
viper.AutomaticEnv()
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func Run() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "run",
|
||||
Short: "run a single collector",
|
||||
Long: `...`,
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
viper.BindPFlag("collector", cmd.Flags().Lookup("collector"))
|
||||
viper.BindPFlag("redact", cmd.Flags().Lookup("redact"))
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
v := viper.GetViper()
|
||||
|
||||
specContents, err := ioutil.ReadFile(v.GetString("collector"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c, err := collect.ParseSpec(string(specContents))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
collector := collect.Collector{
|
||||
Collect: c,
|
||||
Redact: v.GetBool("redact"),
|
||||
}
|
||||
b, err := collector.RunCollectorSync()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("%s", b)
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().String("collector", "", "path to a single collector spec to collect")
|
||||
cmd.Flags().Bool("redact", true, "enable/disable default redactions")
|
||||
|
||||
cmd.MarkFlagRequired("collector")
|
||||
|
||||
viper.BindPFlags(cmd.Flags())
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
|
||||
"github.com/replicatedhq/troubleshoot/pkg/server"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func Server() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "server",
|
||||
Short: "run the http server",
|
||||
Hidden: true,
|
||||
Long: `...`,
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
viper.BindPFlag("port", cmd.Flags().Lookup("port"))
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
v := viper.GetViper()
|
||||
|
||||
server.ServeCollector(context.Background(), fmt.Sprintf(":%d", v.GetInt("port")))
|
||||
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt)
|
||||
|
||||
select {
|
||||
case <-c:
|
||||
return nil
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().Int("port", 8000, "port to listen on")
|
||||
|
||||
viper.BindPFlags(cmd.Flags())
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package main
|
||||
|
||||
import "github.com/replicatedhq/troubleshoot/cmd/collector/cli"
|
||||
|
||||
func main() {
|
||||
cli.InitAndExecute()
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
Copyright 2019 Replicated, 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 main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
|
||||
"github.com/replicatedhq/troubleshoot/pkg/apis"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/controller"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/webhook"
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/config"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
|
||||
"sigs.k8s.io/controller-runtime/pkg/runtime/signals"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var metricsAddr string
|
||||
flag.StringVar(&metricsAddr, "metrics-addr", ":8088", "The address the metric endpoint binds to.")
|
||||
flag.Parse()
|
||||
logf.SetLogger(logf.ZapLogger(false))
|
||||
log := logf.Log.WithName("entrypoint")
|
||||
|
||||
// Get a config to talk to the apiserver
|
||||
log.Info("setting up client for manager")
|
||||
cfg, err := config.GetConfig()
|
||||
if err != nil {
|
||||
log.Error(err, "unable to set up client config")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Create a new Cmd to provide shared dependencies and start components
|
||||
log.Info("setting up manager")
|
||||
mgr, err := manager.New(cfg, manager.Options{MetricsBindAddress: metricsAddr})
|
||||
if err != nil {
|
||||
log.Error(err, "unable to set up overall controller manager")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
log.Info("Registering Components.")
|
||||
|
||||
// Setup Scheme for all resources
|
||||
log.Info("setting up scheme")
|
||||
if err := apis.AddToScheme(mgr.GetScheme()); err != nil {
|
||||
log.Error(err, "unable add APIs to scheme")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Setup all Controllers
|
||||
log.Info("Setting up controller")
|
||||
if err := controller.AddToManager(mgr); err != nil {
|
||||
log.Error(err, "unable to register controllers to the manager")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
log.Info("setting up webhooks")
|
||||
if err := webhook.AddToManager(mgr); err != nil {
|
||||
log.Error(err, "unable to register webhooks to the manager")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Start the Cmd
|
||||
log.Info("Starting the Cmd.")
|
||||
if err := mgr.Start(signals.SetupSignalHandler()); err != nil {
|
||||
log.Error(err, "unable to run the manager")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -4,22 +4,23 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
ui "github.com/gizak/termui/v3"
|
||||
"github.com/gizak/termui/v3/widgets"
|
||||
"github.com/pkg/errors"
|
||||
ui "github.com/replicatedhq/termui/v3"
|
||||
"github.com/replicatedhq/termui/v3/widgets"
|
||||
"github.com/replicatedhq/troubleshoot/cmd/util"
|
||||
analyzerunner "github.com/replicatedhq/troubleshoot/pkg/analyze"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/convert"
|
||||
)
|
||||
|
||||
var (
|
||||
selectedResult = 0
|
||||
table = widgets.NewTable()
|
||||
isShowingSaved = false
|
||||
)
|
||||
|
||||
func showInteractiveResults(preflightName string, analyzeResults []*analyzerunner.AnalyzeResult) error {
|
||||
func showInteractiveResults(preflightName string, outputPath string, analyzeResults []*analyzerunner.AnalyzeResult) error {
|
||||
if err := ui.Init(); err != nil {
|
||||
return errors.Wrap(err, "failed to create terminal ui")
|
||||
}
|
||||
@@ -43,7 +44,7 @@ func showInteractiveResults(preflightName string, analyzeResults []*analyzerunne
|
||||
return nil
|
||||
}
|
||||
case "s":
|
||||
filename, err := save(preflightName, analyzeResults)
|
||||
filename, err := save(preflightName, outputPath, analyzeResults)
|
||||
if err != nil {
|
||||
// show
|
||||
} else {
|
||||
@@ -63,7 +64,9 @@ func showInteractiveResults(preflightName string, analyzeResults []*analyzerunne
|
||||
selectedResult++
|
||||
} else {
|
||||
selectedResult = 0
|
||||
table.SelectedRow = 0
|
||||
}
|
||||
table.ScrollDown()
|
||||
ui.Clear()
|
||||
drawUI(preflightName, analyzeResults)
|
||||
case "<Up>":
|
||||
@@ -71,7 +74,9 @@ func showInteractiveResults(preflightName string, analyzeResults []*analyzerunne
|
||||
selectedResult--
|
||||
} else {
|
||||
selectedResult = len(analyzeResults) - 1
|
||||
table.SelectedRow = len(analyzeResults)
|
||||
}
|
||||
table.ScrollUp()
|
||||
ui.Clear()
|
||||
drawUI(preflightName, analyzeResults)
|
||||
}
|
||||
@@ -94,7 +99,7 @@ func drawHeader(preflightName string) {
|
||||
termWidth, _ := ui.TerminalDimensions()
|
||||
|
||||
title := widgets.NewParagraph()
|
||||
title.Text = fmt.Sprintf("%s Preflight Checks", appName(preflightName))
|
||||
title.Text = fmt.Sprintf("%s Preflight Checks", util.AppName(preflightName))
|
||||
title.TextStyle.Fg = ui.ColorWhite
|
||||
title.TextStyle.Bg = ui.ColorClear
|
||||
title.TextStyle.Modifier = ui.ModifierBold
|
||||
@@ -126,7 +131,6 @@ func drawFooter() {
|
||||
func drawPreflightTable(analyzeResults []*analyzerunner.AnalyzeResult) {
|
||||
termWidth, termHeight := ui.TerminalDimensions()
|
||||
|
||||
table := widgets.NewTable()
|
||||
table.SetRect(0, 3, termWidth/2, termHeight-6)
|
||||
table.FillRow = true
|
||||
table.Border = true
|
||||
@@ -135,6 +139,9 @@ func drawPreflightTable(analyzeResults []*analyzerunner.AnalyzeResult) {
|
||||
|
||||
for i, analyzeResult := range analyzeResults {
|
||||
title := analyzeResult.Title
|
||||
if analyzeResult.Strict {
|
||||
title = title + fmt.Sprintf(" (Strict: %t)", analyzeResult.Strict)
|
||||
}
|
||||
if analyzeResult.IsPass {
|
||||
title = fmt.Sprintf("✔ %s", title)
|
||||
} else if analyzeResult.IsWarn {
|
||||
@@ -213,14 +220,26 @@ func estimateNumberOfLines(text string, width int) int {
|
||||
return lines
|
||||
}
|
||||
|
||||
func save(preflightName string, analyzeResults []*analyzerunner.AnalyzeResult) (string, error) {
|
||||
filename := path.Join(homeDir(), fmt.Sprintf("%s-results.txt", preflightName))
|
||||
func save(preflightName string, outputPath string, analyzeResults []*analyzerunner.AnalyzeResult) (string, error) {
|
||||
filename := ""
|
||||
if outputPath != "" {
|
||||
// use override output path
|
||||
overridePath, err := convert.ValidateOutputPath(outputPath)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "override output file path")
|
||||
}
|
||||
filename = overridePath
|
||||
} else {
|
||||
// use default output path
|
||||
filename = fmt.Sprintf("%s-results-%s.txt", preflightName, time.Now().Format("2006-01-02T15_04_05"))
|
||||
}
|
||||
|
||||
_, err := os.Stat(filename)
|
||||
if err == nil {
|
||||
os.Remove(filename)
|
||||
}
|
||||
|
||||
results := fmt.Sprintf("%s Preflight Checks\n\n", appName(preflightName))
|
||||
results := fmt.Sprintf("%s Preflight Checks\n\n", util.AppName(preflightName))
|
||||
for _, analyzeResult := range analyzeResults {
|
||||
result := ""
|
||||
|
||||
@@ -239,6 +258,10 @@ func save(preflightName string, analyzeResults []*analyzerunner.AnalyzeResult) (
|
||||
result = result + fmt.Sprintf("URI: %s\n", analyzeResult.URI)
|
||||
}
|
||||
|
||||
if analyzeResult.Strict {
|
||||
result = result + fmt.Sprintf("Strict: %t\n", analyzeResult.Strict)
|
||||
}
|
||||
|
||||
result = result + "\n------------\n"
|
||||
|
||||
results = results + result
|
||||
@@ -269,19 +292,3 @@ func showSaved(filename string) {
|
||||
|
||||
isShowingSaved = true
|
||||
}
|
||||
|
||||
func appName(preflightName string) string {
|
||||
words := strings.Split(strings.Title(strings.Replace(preflightName, "-", " ", -1)), " ")
|
||||
casedWords := []string{}
|
||||
for i, word := range words {
|
||||
if strings.ToLower(word) == "ai" {
|
||||
casedWords = append(casedWords, "AI")
|
||||
} else if strings.ToLower(word) == "io" && i > 0 {
|
||||
casedWords[i-1] += ".io"
|
||||
} else {
|
||||
casedWords = append(casedWords, word)
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(casedWords, " ")
|
||||
}
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/replicatedhq/troubleshoot/pkg/logger"
|
||||
kuberneteserrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func receivePreflightResults(preflightJobNamespace string, preflightJobName string) error {
|
||||
// poll until there are no more "running" collectors
|
||||
troubleshootClient, err := createTroubleshootK8sClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bundlePath, err := ioutil.TempDir("", "troubleshoot")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(bundlePath)
|
||||
|
||||
receivedPreflights := []string{}
|
||||
for {
|
||||
job, err := troubleshootClient.PreflightJobs(preflightJobNamespace).Get(preflightJobName, metav1.GetOptions{})
|
||||
if err != nil && kuberneteserrors.IsNotFound(err) {
|
||||
// where did it go!
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, readyPreflight := range job.Status.AnalyzersSuccessful {
|
||||
alreadyReceived := false
|
||||
for _, receivedPreflight := range receivedPreflights {
|
||||
if receivedPreflight == readyPreflight {
|
||||
alreadyReceived = true
|
||||
}
|
||||
}
|
||||
|
||||
if alreadyReceived {
|
||||
continue
|
||||
}
|
||||
|
||||
preflightResp, err := http.Get(fmt.Sprintf("http://localhost:8000/preflight/%s", readyPreflight))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer preflightResp.Body.Close()
|
||||
body, err := ioutil.ReadAll(preflightResp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Printf("%s\n", body)
|
||||
receivedPreflights = append(receivedPreflights, readyPreflight)
|
||||
}
|
||||
|
||||
if len(job.Status.AnalyzersRunning) == 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
-33
@@ -1,12 +1,10 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
@@ -14,47 +12,43 @@ import (
|
||||
func RootCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "preflight [url]",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Short: "Run and retrieve preflight checks in a cluster",
|
||||
Long: `A preflight check is a set of validations that can and should be run to ensure
|
||||
that a cluster meets the requirements to run an application.`,
|
||||
SilenceUsage: true,
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
viper.BindPFlags(cmd.Flags())
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
v := viper.GetViper()
|
||||
|
||||
if len(args) == 0 {
|
||||
return runPreflightsCRD(v)
|
||||
}
|
||||
|
||||
return runPreflightsNoCRD(v, args[0])
|
||||
return runPreflights(v, args[0])
|
||||
},
|
||||
}
|
||||
|
||||
cobra.OnInitialize(initConfig)
|
||||
|
||||
cmd.AddCommand(VersionCmd())
|
||||
|
||||
cmd.Flags().Bool("interactive", true, "interactive preflights")
|
||||
cmd.Flags().String("format", "human", "output format, one of human, json, yaml. only used when interactive is set to false")
|
||||
|
||||
cmd.Flags().String("preflight", "", "name of the preflight to use")
|
||||
cmd.Flags().String("namespace", "default", "namespace the preflight can be found in")
|
||||
|
||||
cmd.Flags().String("kubecontext", filepath.Join(homeDir(), ".kube", "config"), "the kubecontext to use when connecting")
|
||||
|
||||
cmd.Flags().String("image", "", "the full name of the preflight image to use")
|
||||
cmd.Flags().String("pullpolicy", "", "the pull policy of the preflight image")
|
||||
cmd.Flags().String("collector-image", "", "the full name of the collector image to use")
|
||||
cmd.Flags().String("collector-pullpolicy", "", "the pull policy of the collector image")
|
||||
|
||||
cmd.Flags().String("serviceaccount", "", "name of the service account to use. if not provided, one will be created")
|
||||
cmd.Flags().Bool("collect-without-permissions", true, "always run preflight checks even if some require permissions that preflight does not have")
|
||||
cmd.Flags().String("selector", "", "selector (label query) to filter remote collection nodes on.")
|
||||
cmd.Flags().String("since-time", "", "force pod logs collectors to return logs after a specific date (RFC3339)")
|
||||
cmd.Flags().String("since", "", "force pod logs collectors to return logs newer than a relative duration like 5s, 2m, or 3h.")
|
||||
cmd.Flags().StringP("output", "o", "", "specify the output file path for the preflight checks")
|
||||
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
|
||||
|
||||
k8sutil.AddFlags(cmd.Flags())
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func InitAndExecute() {
|
||||
if err := RootCmd().Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -63,16 +57,3 @@ func initConfig() {
|
||||
viper.SetEnvPrefix("PREFLIGHT")
|
||||
viper.AutomaticEnv()
|
||||
}
|
||||
|
||||
func ensureCollectorInList(list []*troubleshootv1beta1.Collect, collector troubleshootv1beta1.Collect) []*troubleshootv1beta1.Collect {
|
||||
for _, inList := range list {
|
||||
if collector.ClusterResources != nil && inList.ClusterResources != nil {
|
||||
return list
|
||||
}
|
||||
if collector.ClusterInfo != nil && inList.ClusterInfo != nil {
|
||||
return list
|
||||
}
|
||||
}
|
||||
|
||||
return append(list, &collector)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,353 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
cursor "github.com/ahmetalpbalkan/go-cursor"
|
||||
"github.com/fatih/color"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/replicatedhq/troubleshoot/cmd/util"
|
||||
analyzer "github.com/replicatedhq/troubleshoot/pkg/analyze"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
troubleshootclientsetscheme "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/scheme"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/docrewrite"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/preflight"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/specs"
|
||||
"github.com/spf13/viper"
|
||||
spin "github.com/tj/go-spin"
|
||||
"golang.org/x/sync/errgroup"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
)
|
||||
|
||||
func runPreflights(v *viper.Viper, arg string) error {
|
||||
if v.GetBool("interactive") {
|
||||
fmt.Print(cursor.Hide())
|
||||
defer fmt.Print(cursor.Show())
|
||||
}
|
||||
|
||||
go func() {
|
||||
signalChan := make(chan os.Signal, 1)
|
||||
signal.Notify(signalChan, os.Interrupt)
|
||||
<-signalChan
|
||||
os.Exit(0)
|
||||
}()
|
||||
|
||||
var preflightContent []byte
|
||||
var err error
|
||||
if strings.HasPrefix(arg, "secret/") {
|
||||
// format secret/namespace-name/secret-name
|
||||
pathParts := strings.Split(arg, "/")
|
||||
if len(pathParts) != 3 {
|
||||
return errors.Errorf("path %s must have 3 components", arg)
|
||||
}
|
||||
|
||||
spec, err := specs.LoadFromSecret(pathParts[1], pathParts[2], "preflight-spec")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get spec from secret")
|
||||
}
|
||||
|
||||
preflightContent = spec
|
||||
} else if _, err = os.Stat(arg); err == nil {
|
||||
b, err := ioutil.ReadFile(arg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
preflightContent = b
|
||||
} else {
|
||||
if !util.IsURL(arg) {
|
||||
return fmt.Errorf("%s is not a URL and was not found (err %s)", arg, err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", arg, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("User-Agent", "Replicated_Preflight/v1beta2")
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
preflightContent = body
|
||||
}
|
||||
|
||||
preflightContent, err = docrewrite.ConvertToV1Beta2(preflightContent)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to convert to v1beta2")
|
||||
}
|
||||
|
||||
troubleshootclientsetscheme.AddToScheme(scheme.Scheme)
|
||||
decode := scheme.Codecs.UniversalDeserializer().Decode
|
||||
obj, _, err := decode([]byte(preflightContent), nil, nil)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to parse %s", arg)
|
||||
}
|
||||
|
||||
var collectResults []preflight.CollectResult
|
||||
preflightSpecName := ""
|
||||
|
||||
progressCh := make(chan interface{})
|
||||
defer close(progressCh)
|
||||
|
||||
ctx, stopProgressCollection := context.WithCancel(context.Background())
|
||||
// make sure we shut down progress collection goroutines if an error occurs
|
||||
defer stopProgressCollection()
|
||||
progressCollection, ctx := errgroup.WithContext(ctx)
|
||||
|
||||
if v.GetBool("interactive") {
|
||||
progressCollection.Go(collectInteractiveProgress(ctx, progressCh))
|
||||
} else {
|
||||
progressCollection.Go(collectNonInteractiveProgess(ctx, progressCh))
|
||||
}
|
||||
|
||||
if preflightSpec, ok := obj.(*troubleshootv1beta2.Preflight); ok {
|
||||
r, err := collectInCluster(preflightSpec, progressCh)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to collect in cluster")
|
||||
}
|
||||
collectResults = append(collectResults, *r)
|
||||
preflightSpecName = preflightSpec.Name
|
||||
} else if hostPreflightSpec, ok := obj.(*troubleshootv1beta2.HostPreflight); ok {
|
||||
if len(hostPreflightSpec.Spec.Collectors) > 0 {
|
||||
r, err := collectHost(hostPreflightSpec, progressCh)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to collect from host")
|
||||
}
|
||||
collectResults = append(collectResults, *r)
|
||||
}
|
||||
if len(hostPreflightSpec.Spec.RemoteCollectors) > 0 {
|
||||
r, err := collectRemote(hostPreflightSpec, progressCh)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to collect remotely")
|
||||
}
|
||||
collectResults = append(collectResults, *r)
|
||||
}
|
||||
preflightSpecName = hostPreflightSpec.Name
|
||||
}
|
||||
|
||||
if collectResults == nil {
|
||||
return errors.New("no results")
|
||||
}
|
||||
|
||||
analyzeResults := []*analyzer.AnalyzeResult{}
|
||||
for _, res := range collectResults {
|
||||
analyzeResults = append(analyzeResults, res.Analyze()...)
|
||||
}
|
||||
|
||||
if preflightSpec, ok := obj.(*troubleshootv1beta2.Preflight); ok {
|
||||
if preflightSpec.Spec.UploadResultsTo != "" {
|
||||
err := uploadResults(preflightSpec.Spec.UploadResultsTo, analyzeResults)
|
||||
if err != nil {
|
||||
progressCh <- err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stopProgressCollection()
|
||||
progressCollection.Wait()
|
||||
|
||||
if v.GetBool("interactive") {
|
||||
if len(analyzeResults) == 0 {
|
||||
return errors.New("no data has been collected")
|
||||
}
|
||||
return showInteractiveResults(preflightSpecName, v.GetString("output"), analyzeResults)
|
||||
}
|
||||
|
||||
return showStdoutResults(v.GetString("format"), preflightSpecName, analyzeResults)
|
||||
}
|
||||
|
||||
func collectInteractiveProgress(ctx context.Context, progressCh <-chan interface{}) func() error {
|
||||
return func() error {
|
||||
spinner := spin.New()
|
||||
lastMsg := ""
|
||||
|
||||
errorTxt := color.New(color.FgHiRed)
|
||||
infoTxt := color.New(color.FgCyan)
|
||||
|
||||
for {
|
||||
select {
|
||||
case msg := <-progressCh:
|
||||
switch msg := msg.(type) {
|
||||
case error:
|
||||
errorTxt.Printf("%s\r * %v\n", cursor.ClearEntireLine(), msg)
|
||||
case string:
|
||||
if lastMsg == msg {
|
||||
break
|
||||
}
|
||||
lastMsg = msg
|
||||
infoTxt.Printf("%s\r * %s\n", cursor.ClearEntireLine(), msg)
|
||||
|
||||
}
|
||||
case <-time.After(time.Millisecond * 100):
|
||||
fmt.Printf("\r %s %s ", color.CyanString("Running Preflight Checks"), spinner.Next())
|
||||
case <-ctx.Done():
|
||||
fmt.Printf("\r%s\r", cursor.ClearEntireLine())
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func collectNonInteractiveProgess(ctx context.Context, progressCh <-chan interface{}) func() error {
|
||||
return func() error {
|
||||
for {
|
||||
select {
|
||||
case msg := <-progressCh:
|
||||
switch msg := msg.(type) {
|
||||
case error:
|
||||
fmt.Fprintf(os.Stderr, "error - %v\n", msg)
|
||||
case string:
|
||||
fmt.Fprintf(os.Stderr, "%s\n", msg)
|
||||
case preflight.CollectProgress:
|
||||
fmt.Fprintf(os.Stderr, "%s\n", msg.String())
|
||||
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func collectInCluster(preflightSpec *troubleshootv1beta2.Preflight, progressCh chan interface{}) (*preflight.CollectResult, error) {
|
||||
v := viper.GetViper()
|
||||
|
||||
restConfig, err := k8sutil.GetRESTConfig()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to convert kube flags to rest config")
|
||||
}
|
||||
|
||||
collectOpts := preflight.CollectOpts{
|
||||
Namespace: v.GetString("namespace"),
|
||||
IgnorePermissionErrors: v.GetBool("collect-without-permissions"),
|
||||
ProgressChan: progressCh,
|
||||
KubernetesRestConfig: restConfig,
|
||||
}
|
||||
|
||||
if v.GetString("since") != "" || v.GetString("since-time") != "" {
|
||||
err := parseTimeFlags(v, preflightSpec.Spec.Collectors)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
collectResults, err := preflight.Collect(collectOpts, preflightSpec)
|
||||
if err != nil {
|
||||
if !collectResults.IsRBACAllowed() {
|
||||
if preflightSpec.Spec.UploadResultsTo != "" {
|
||||
clusterCollectResults := collectResults.(preflight.ClusterCollectResult)
|
||||
err := uploadErrors(preflightSpec.Spec.UploadResultsTo, clusterCollectResults.Collectors)
|
||||
if err != nil {
|
||||
progressCh <- err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &collectResults, nil
|
||||
}
|
||||
|
||||
func collectRemote(preflightSpec *troubleshootv1beta2.HostPreflight, progressCh chan interface{}) (*preflight.CollectResult, error) {
|
||||
v := viper.GetViper()
|
||||
|
||||
restConfig, err := k8sutil.GetRESTConfig()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to convert kube flags to rest config")
|
||||
}
|
||||
|
||||
labelSelector, err := labels.Parse(v.GetString("selector"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to parse selector")
|
||||
}
|
||||
|
||||
namespace := v.GetString("namespace")
|
||||
if namespace == "" {
|
||||
namespace = "default"
|
||||
}
|
||||
|
||||
timeout := v.GetDuration("request-timeout")
|
||||
if timeout == 0 {
|
||||
timeout = 30 * time.Second
|
||||
}
|
||||
|
||||
collectOpts := preflight.CollectOpts{
|
||||
Namespace: namespace,
|
||||
IgnorePermissionErrors: v.GetBool("collect-without-permissions"),
|
||||
ProgressChan: progressCh,
|
||||
KubernetesRestConfig: restConfig,
|
||||
Image: v.GetString("collector-image"),
|
||||
PullPolicy: v.GetString("collector-pullpolicy"),
|
||||
LabelSelector: labelSelector.String(),
|
||||
Timeout: timeout,
|
||||
}
|
||||
|
||||
collectResults, err := preflight.CollectRemote(collectOpts, preflightSpec)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to collect from remote")
|
||||
}
|
||||
|
||||
return &collectResults, nil
|
||||
}
|
||||
|
||||
func collectHost(hostPreflightSpec *troubleshootv1beta2.HostPreflight, progressCh chan interface{}) (*preflight.CollectResult, error) {
|
||||
collectOpts := preflight.CollectOpts{
|
||||
ProgressChan: progressCh,
|
||||
}
|
||||
|
||||
collectResults, err := preflight.CollectHost(collectOpts, hostPreflightSpec)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to collect from host")
|
||||
}
|
||||
|
||||
return &collectResults, nil
|
||||
}
|
||||
|
||||
func parseTimeFlags(v *viper.Viper, collectors []*troubleshootv1beta2.Collect) error {
|
||||
var (
|
||||
sinceTime time.Time
|
||||
err error
|
||||
)
|
||||
if v.GetString("since-time") != "" {
|
||||
if v.GetString("since") != "" {
|
||||
return errors.Errorf("at most one of `sinceTime` or `since` may be specified")
|
||||
}
|
||||
sinceTime, err = time.Parse(time.RFC3339, v.GetString("since-time"))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to parse --since-time flag")
|
||||
}
|
||||
} else {
|
||||
parsedDuration, err := time.ParseDuration(v.GetString("since"))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to parse --since flag")
|
||||
}
|
||||
now := time.Now()
|
||||
sinceTime = now.Add(0 - parsedDuration)
|
||||
}
|
||||
for _, collector := range collectors {
|
||||
if collector.Logs != nil {
|
||||
if collector.Logs.Limits == nil {
|
||||
collector.Logs.Limits = new(troubleshootv1beta2.LogLimits)
|
||||
}
|
||||
collector.Logs.Limits.SinceTime = metav1.NewTime(sinceTime)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
|
||||
"github.com/spf13/viper"
|
||||
kuberneteserrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func runPreflightsCRD(v *viper.Viper) error {
|
||||
troubleshootClient, err := createTroubleshootK8sClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
preflightName := v.GetString("preflight")
|
||||
if preflightName == "" {
|
||||
preflights, err := troubleshootClient.Preflights(v.GetString("namespace")).List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(preflights.Items) == 1 {
|
||||
preflightName = preflights.Items[0].Name
|
||||
}
|
||||
}
|
||||
|
||||
if preflightName == "" {
|
||||
return errors.New("unable to preflight, try using the --preflight flags")
|
||||
}
|
||||
|
||||
// generate a unique name
|
||||
now := time.Now()
|
||||
suffix := fmt.Sprintf("%d", now.Unix())
|
||||
|
||||
preflightJobName := fmt.Sprintf("%s-job-%s", preflightName, suffix[len(suffix)-4:])
|
||||
preflightJob := troubleshootv1beta1.PreflightJob{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: preflightJobName,
|
||||
Namespace: v.GetString("namespace"),
|
||||
},
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "preflightjob.troubleshoot.replicated.com",
|
||||
},
|
||||
Spec: troubleshootv1beta1.PreflightJobSpec{
|
||||
Preflight: troubleshootv1beta1.PreflightRef{
|
||||
Name: preflightName,
|
||||
Namespace: v.GetString("namespace"),
|
||||
},
|
||||
Image: v.GetString("image"),
|
||||
ImagePullPolicy: v.GetString("pullpolicy"),
|
||||
CollectorImage: v.GetString("collector-image"),
|
||||
CollectorImagePullPolicy: v.GetString("collector-pullpolicy"),
|
||||
},
|
||||
}
|
||||
if _, err := troubleshootClient.PreflightJobs(v.GetString("namespace")).Create(&preflightJob); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Poll the status of the Custom Resource for it to include a callback
|
||||
var found *troubleshootv1beta1.PreflightJob
|
||||
start := time.Now()
|
||||
for {
|
||||
current, err := troubleshootClient.PreflightJobs(v.GetString("namespace")).Get(preflightJobName, metav1.GetOptions{})
|
||||
if err != nil && kuberneteserrors.IsNotFound(err) {
|
||||
continue
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if current.Status.IsServerReady {
|
||||
found = current
|
||||
break
|
||||
}
|
||||
|
||||
if time.Now().Sub(start) > time.Duration(time.Second*10) {
|
||||
return errors.New("preflightjob failed to start")
|
||||
}
|
||||
|
||||
time.Sleep(time.Millisecond * 200)
|
||||
}
|
||||
|
||||
// Connect to the callback
|
||||
stopChan, err := k8sutil.PortForward(v.GetString("kubecontext"), 8000, 8000, found.Status.ServerPodNamespace, found.Status.ServerPodName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := receivePreflightResults(found.Namespace, found.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write
|
||||
|
||||
close(stopChan)
|
||||
return nil
|
||||
}
|
||||
@@ -1,195 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ahmetalpbalkan/go-cursor"
|
||||
"github.com/pkg/errors"
|
||||
analyzerunner "github.com/replicatedhq/troubleshoot/pkg/analyze"
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/logger"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/tj/go-spin"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
func runPreflightsNoCRD(v *viper.Viper, arg string) error {
|
||||
fmt.Print(cursor.Hide())
|
||||
defer fmt.Print(cursor.Show())
|
||||
|
||||
preflightContent := ""
|
||||
if !isURL(arg) {
|
||||
if _, err := os.Stat(arg); os.IsNotExist(err) {
|
||||
return fmt.Errorf("%s was not found", arg)
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadFile(arg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
preflightContent = string(b)
|
||||
} else {
|
||||
req, err := http.NewRequest("GET", arg, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("User-Agent", "Replicated_Preflight/v1beta1")
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
preflightContent = string(body)
|
||||
}
|
||||
|
||||
preflight := troubleshootv1beta1.Preflight{}
|
||||
if err := yaml.Unmarshal([]byte(preflightContent), &preflight); err != nil {
|
||||
return fmt.Errorf("unable to parse %s as a preflight", arg)
|
||||
}
|
||||
|
||||
s := spin.New()
|
||||
finishedCh := make(chan bool, 1)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-finishedCh:
|
||||
fmt.Printf("\r")
|
||||
return
|
||||
case <-time.After(time.Millisecond * 100):
|
||||
fmt.Printf("\r \033[36mRunning Preflight checks\033[m %s ", s.Next())
|
||||
}
|
||||
}
|
||||
}()
|
||||
defer func() {
|
||||
finishedCh <- true
|
||||
}()
|
||||
|
||||
allCollectedData, err := runCollectors(v, preflight)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
getCollectedFileContents := func(fileName string) ([]byte, error) {
|
||||
contents, ok := allCollectedData[fileName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("file %s was not collected", fileName)
|
||||
}
|
||||
|
||||
return contents, nil
|
||||
}
|
||||
getChildCollectedFileContents := func(prefix string) (map[string][]byte, error) {
|
||||
matching := make(map[string][]byte)
|
||||
for k, v := range allCollectedData {
|
||||
if strings.HasPrefix(k, prefix) {
|
||||
matching[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return matching, nil
|
||||
}
|
||||
|
||||
analyzeResults := []*analyzerunner.AnalyzeResult{}
|
||||
for _, analyzer := range preflight.Spec.Analyzers {
|
||||
analyzeResult, err := analyzerunner.Analyze(analyzer, getCollectedFileContents, getChildCollectedFileContents)
|
||||
if err != nil {
|
||||
logger.Printf("an analyzer failed to run: %v\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
analyzeResults = append(analyzeResults, analyzeResult)
|
||||
}
|
||||
|
||||
finishedCh <- true
|
||||
|
||||
if preflight.Spec.UploadResultsTo != "" {
|
||||
tryUploadResults(preflight.Spec.UploadResultsTo, preflight.Name, analyzeResults)
|
||||
}
|
||||
if v.GetBool("interactive") {
|
||||
return showInteractiveResults(preflight.Name, analyzeResults)
|
||||
}
|
||||
|
||||
logger.Printf("only interactive results are supported\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
func runCollectors(v *viper.Viper, preflight troubleshootv1beta1.Preflight) (map[string][]byte, error) {
|
||||
desiredCollectors := make([]*troubleshootv1beta1.Collect, 0, 0)
|
||||
for _, definedCollector := range preflight.Spec.Collectors {
|
||||
desiredCollectors = append(desiredCollectors, definedCollector)
|
||||
}
|
||||
desiredCollectors = ensureCollectorInList(desiredCollectors, troubleshootv1beta1.Collect{ClusterInfo: &troubleshootv1beta1.ClusterInfo{}})
|
||||
desiredCollectors = ensureCollectorInList(desiredCollectors, troubleshootv1beta1.Collect{ClusterResources: &troubleshootv1beta1.ClusterResources{}})
|
||||
|
||||
allCollectedData := make(map[string][]byte)
|
||||
|
||||
// Run preflights collectors synchronously
|
||||
for _, desiredCollector := range desiredCollectors {
|
||||
collector := collect.Collector{
|
||||
Redact: true,
|
||||
Collect: desiredCollector,
|
||||
}
|
||||
|
||||
result, err := collector.RunCollectorSync()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to run collector")
|
||||
}
|
||||
|
||||
output, err := parseCollectorOutput(string(result))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse collector output")
|
||||
}
|
||||
for k, v := range output {
|
||||
allCollectedData[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return allCollectedData, nil
|
||||
}
|
||||
|
||||
func parseCollectorOutput(output string) (map[string][]byte, error) {
|
||||
input := make(map[string]interface{})
|
||||
files := make(map[string][]byte)
|
||||
if err := json.Unmarshal([]byte(output), &input); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for filename, maybeContents := range input {
|
||||
fileDir, fileName := filepath.Split(filename)
|
||||
|
||||
switch maybeContents.(type) {
|
||||
case string:
|
||||
decoded, err := base64.StdEncoding.DecodeString(maybeContents.(string))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
files[filepath.Join(fileDir, fileName)] = decoded
|
||||
|
||||
case map[string]interface{}:
|
||||
for k, v := range maybeContents.(map[string]interface{}) {
|
||||
decoded, err := base64.StdEncoding.DecodeString(v.(string))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
files[filepath.Join(fileDir, fileName, k)] = decoded
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return files, nil
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
kuberneteserrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
func createServiceAccount(preflight troubleshootv1beta1.Preflight, namespace string, clientset *kubernetes.Clientset) (string, error) {
|
||||
name := fmt.Sprintf("preflight-%s", preflight.Name)
|
||||
|
||||
serviceAccount := corev1.ServiceAccount{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "ServiceAccount",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Secrets: []corev1.ObjectReference{
|
||||
{
|
||||
APIVersion: "v1",
|
||||
Kind: "Secret",
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := clientset.CoreV1().ServiceAccounts(namespace).Get(serviceAccount.Name, metav1.GetOptions{})
|
||||
if err != nil && !kuberneteserrors.IsNotFound(err) {
|
||||
return "", errors.Wrap(err, "failed to get existing service account")
|
||||
}
|
||||
if kuberneteserrors.IsNotFound(err) {
|
||||
_, err := clientset.CoreV1().ServiceAccounts(namespace).Create(&serviceAccount)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to create service account")
|
||||
}
|
||||
}
|
||||
|
||||
role := rbacv1.ClusterRole{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "ClusterRole",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Rules: []rbacv1.PolicyRule{
|
||||
{
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{
|
||||
"namespaces",
|
||||
"pods",
|
||||
"services",
|
||||
"secrets",
|
||||
},
|
||||
Verbs: metav1.Verbs{"list"},
|
||||
},
|
||||
{
|
||||
APIGroups: []string{"apps"},
|
||||
Resources: []string{"deployments"},
|
||||
Verbs: metav1.Verbs{"list"},
|
||||
},
|
||||
{
|
||||
APIGroups: []string{"extensions"},
|
||||
Resources: []string{"ingresses"},
|
||||
Verbs: metav1.Verbs{"list"},
|
||||
},
|
||||
{
|
||||
APIGroups: []string{"storage.k8s.io"},
|
||||
Resources: []string{"storageclasses"},
|
||||
Verbs: metav1.Verbs{"list"},
|
||||
},
|
||||
{
|
||||
APIGroups: []string{"apiextensions.k8s.io"},
|
||||
Resources: []string{"customresourcedefinitions"},
|
||||
Verbs: metav1.Verbs{"list"},
|
||||
},
|
||||
},
|
||||
}
|
||||
_, err = clientset.RbacV1().ClusterRoles().Get(role.Name, metav1.GetOptions{})
|
||||
if err != nil && !kuberneteserrors.IsNotFound(err) {
|
||||
return "", errors.Wrap(err, "failed to get eisting cluster role")
|
||||
}
|
||||
if kuberneteserrors.IsNotFound(err) {
|
||||
_, err = clientset.RbacV1().ClusterRoles().Create(&role)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to create cluster role")
|
||||
}
|
||||
}
|
||||
|
||||
roleBinding := rbacv1.ClusterRoleBinding{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "ClusterRoleBinding",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Subjects: []rbacv1.Subject{
|
||||
{
|
||||
Kind: "ServiceAccount",
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
},
|
||||
RoleRef: rbacv1.RoleRef{
|
||||
APIGroup: "rbac.authorization.k8s.io",
|
||||
Kind: "ClusterRole",
|
||||
Name: name,
|
||||
},
|
||||
}
|
||||
_, err = clientset.RbacV1().ClusterRoleBindings().Get(roleBinding.Name, metav1.GetOptions{})
|
||||
if err != nil && !kuberneteserrors.IsNotFound(err) {
|
||||
return "", errors.Wrap(err, "failed to get existing cluster role binding")
|
||||
}
|
||||
if kuberneteserrors.IsNotFound(err) {
|
||||
_, err = clientset.RbacV1().ClusterRoleBindings().Create(&roleBinding)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to create cluster role binding")
|
||||
}
|
||||
}
|
||||
|
||||
return name, nil
|
||||
}
|
||||
|
||||
func removeServiceAccount(name string, namespace string, clientset *kubernetes.Clientset) error {
|
||||
if err := clientset.RbacV1().ClusterRoleBindings().Delete(name, &metav1.DeleteOptions{}); err != nil {
|
||||
return errors.Wrap(err, "failed to delete cluster role binding")
|
||||
}
|
||||
|
||||
if err := clientset.RbacV1().ClusterRoles().Delete(name, &metav1.DeleteOptions{}); err != nil {
|
||||
return errors.Wrap(err, "failed to delete cluster role")
|
||||
}
|
||||
|
||||
if err := clientset.CoreV1().ServiceAccounts(namespace).Delete(name, &metav1.DeleteOptions{}); err != nil {
|
||||
return errors.Wrap(err, "failed to delete service account")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
analyzerunner "github.com/replicatedhq/troubleshoot/pkg/analyze"
|
||||
)
|
||||
|
||||
func showStdoutResults(format string, preflightName string, analyzeResults []*analyzerunner.AnalyzeResult) error {
|
||||
if format == "human" {
|
||||
return showStdoutResultsHuman(preflightName, analyzeResults)
|
||||
} else if format == "json" {
|
||||
return showStdoutResultsJSON(preflightName, analyzeResults)
|
||||
}
|
||||
|
||||
return errors.Errorf("unknown output format: %q", format)
|
||||
}
|
||||
|
||||
func showStdoutResultsHuman(preflightName string, analyzeResults []*analyzerunner.AnalyzeResult) error {
|
||||
fmt.Println("")
|
||||
var failed bool
|
||||
for _, analyzeResult := range analyzeResults {
|
||||
testResultfailed := outputResult(analyzeResult)
|
||||
if testResultfailed {
|
||||
failed = true
|
||||
}
|
||||
}
|
||||
if failed {
|
||||
fmt.Printf("--- FAIL %s\n", preflightName)
|
||||
fmt.Println("FAILED")
|
||||
} else {
|
||||
fmt.Printf("--- PASS %s\n", preflightName)
|
||||
fmt.Println("PASS")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func showStdoutResultsJSON(preflightName string, analyzeResults []*analyzerunner.AnalyzeResult) error {
|
||||
type ResultOutput struct {
|
||||
Title string `json:"title"`
|
||||
Message string `json:"message"`
|
||||
URI string `json:"uri,omitempty"`
|
||||
Strict bool `json:"strict,omitempty"`
|
||||
}
|
||||
type Output struct {
|
||||
Pass []ResultOutput `json:"pass,omitempty"`
|
||||
Warn []ResultOutput `json:"warn,omitempty"`
|
||||
Fail []ResultOutput `json:"fail,omitempty"`
|
||||
}
|
||||
|
||||
output := Output{
|
||||
Pass: []ResultOutput{},
|
||||
Warn: []ResultOutput{},
|
||||
Fail: []ResultOutput{},
|
||||
}
|
||||
|
||||
for _, analyzeResult := range analyzeResults {
|
||||
resultOutput := ResultOutput{
|
||||
Title: analyzeResult.Title,
|
||||
Message: analyzeResult.Message,
|
||||
URI: analyzeResult.URI,
|
||||
}
|
||||
|
||||
if analyzeResult.Strict {
|
||||
resultOutput.Strict = analyzeResult.Strict
|
||||
}
|
||||
|
||||
if analyzeResult.IsPass {
|
||||
output.Pass = append(output.Pass, resultOutput)
|
||||
} else if analyzeResult.IsWarn {
|
||||
output.Warn = append(output.Warn, resultOutput)
|
||||
} else if analyzeResult.IsFail {
|
||||
output.Fail = append(output.Fail, resultOutput)
|
||||
}
|
||||
}
|
||||
|
||||
b, err := json.MarshalIndent(output, "", " ")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to marshal results")
|
||||
}
|
||||
|
||||
fmt.Printf("%s\n", b)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func outputResult(analyzeResult *analyzerunner.AnalyzeResult) bool {
|
||||
if analyzeResult.IsPass {
|
||||
fmt.Printf(" --- PASS %s\n", analyzeResult.Title)
|
||||
fmt.Printf(" --- %s\n", analyzeResult.Message)
|
||||
} else if analyzeResult.IsWarn {
|
||||
fmt.Printf(" --- WARN: %s\n", analyzeResult.Title)
|
||||
fmt.Printf(" --- %s\n", analyzeResult.Message)
|
||||
} else if analyzeResult.IsFail {
|
||||
fmt.Printf(" --- FAIL: %s\n", analyzeResult.Title)
|
||||
fmt.Printf(" --- %s\n", analyzeResult.Message)
|
||||
}
|
||||
|
||||
if analyzeResult.Strict {
|
||||
fmt.Printf(" --- Strict: %t\n", analyzeResult.Strict)
|
||||
}
|
||||
|
||||
if analyzeResult.IsFail {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -5,29 +5,19 @@ import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
analyzerunner "github.com/replicatedhq/troubleshoot/pkg/analyze"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/preflight"
|
||||
)
|
||||
|
||||
type UploadPreflightResult struct {
|
||||
IsFail bool `json:"isFail,omitempty"`
|
||||
IsWarn bool `json:"isWarn,omitempty"`
|
||||
IsPass bool `json:"isPass,omitempty"`
|
||||
|
||||
Title string `json:"title"`
|
||||
Message string `json:"message"`
|
||||
URI string `json:"uri,omitempty"`
|
||||
}
|
||||
|
||||
type UploadPreflightResults struct {
|
||||
Results []*UploadPreflightResult `json:"results"`
|
||||
}
|
||||
|
||||
func tryUploadResults(uri string, preflightName string, analyzeResults []*analyzerunner.AnalyzeResult) error {
|
||||
uploadPreflightResults := UploadPreflightResults{
|
||||
Results: []*UploadPreflightResult{},
|
||||
func uploadResults(uri string, analyzeResults []*analyzerunner.AnalyzeResult) error {
|
||||
uploadPreflightResults := &preflight.UploadPreflightResults{
|
||||
Results: []*preflight.UploadPreflightResult{},
|
||||
}
|
||||
for _, analyzeResult := range analyzeResults {
|
||||
uploadPreflightResult := &UploadPreflightResult{
|
||||
uploadPreflightResult := &preflight.UploadPreflightResult{
|
||||
Strict: analyzeResult.Strict,
|
||||
IsFail: analyzeResult.IsFail,
|
||||
IsWarn: analyzeResult.IsWarn,
|
||||
IsPass: analyzeResult.IsPass,
|
||||
@@ -39,14 +29,35 @@ func tryUploadResults(uri string, preflightName string, analyzeResults []*analyz
|
||||
uploadPreflightResults.Results = append(uploadPreflightResults.Results, uploadPreflightResult)
|
||||
}
|
||||
|
||||
b, err := json.Marshal(uploadPreflightResults)
|
||||
return upload(uri, uploadPreflightResults)
|
||||
}
|
||||
|
||||
func uploadErrors(uri string, collectors collect.Collectors) error {
|
||||
errors := []*preflight.UploadPreflightError{}
|
||||
for _, collector := range collectors {
|
||||
for _, e := range collector.RBACErrors {
|
||||
errors = append(errors, &preflight.UploadPreflightError{
|
||||
Error: e.Error(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
results := &preflight.UploadPreflightResults{
|
||||
Errors: errors,
|
||||
}
|
||||
|
||||
return upload(uri, results)
|
||||
}
|
||||
|
||||
func upload(uri string, payload *preflight.UploadPreflightResults) error {
|
||||
b, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.Wrap(err, "failed to marshal payload")
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", uri, bytes.NewBuffer(b))
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.Wrap(err, "failed to create request")
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
@@ -54,11 +65,11 @@ func tryUploadResults(uri string, preflightName string, analyzeResults []*analyz
|
||||
client := http.DefaultClient
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.Wrap(err, "failed to execute request")
|
||||
}
|
||||
|
||||
if resp.StatusCode > 290 {
|
||||
return err
|
||||
return errors.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
troubleshootclientv1beta1 "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta1"
|
||||
"github.com/spf13/viper"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
)
|
||||
|
||||
func homeDir() string {
|
||||
if h := os.Getenv("HOME"); h != "" {
|
||||
return h
|
||||
}
|
||||
return os.Getenv("USERPROFILE") // windows
|
||||
}
|
||||
|
||||
func isURL(str string) bool {
|
||||
_, err := url.ParseRequestURI(str)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func createTroubleshootK8sClient() (*troubleshootclientv1beta1.TroubleshootV1beta1Client, error) {
|
||||
v := viper.GetViper()
|
||||
|
||||
config, err := clientcmd.BuildConfigFromFlags("", v.GetString("kubecontext"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
troubleshootClient, err := troubleshootclientv1beta1.NewForConfig(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return troubleshootClient, nil
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/replicatedhq/troubleshoot/pkg/version"
|
||||
)
|
||||
|
||||
func VersionCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Print the current version and exit",
|
||||
Long: `Print the current version and exit`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
fmt.Printf("Replicated Preflight %s\n", version.Version())
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"github.com/replicatedhq/troubleshoot/cmd/preflight/cli"
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
extensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
extensionsscheme "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
)
|
||||
|
||||
func RootCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "schemagen",
|
||||
Short: "Generate openapischemas for the kinds in this project",
|
||||
SilenceUsage: true,
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
viper.BindPFlags(cmd.Flags())
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
v := viper.GetViper()
|
||||
|
||||
return generateSchemas(v)
|
||||
},
|
||||
}
|
||||
|
||||
cobra.OnInitialize(initConfig)
|
||||
|
||||
cmd.Flags().String("output-dir", "./schemas", "directory to save the schemas in")
|
||||
|
||||
viper.BindPFlags(cmd.Flags())
|
||||
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func InitAndExecute() {
|
||||
if err := RootCmd().Execute(); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func initConfig() {
|
||||
viper.SetEnvPrefix("TROUBLESHOOT")
|
||||
viper.AutomaticEnv()
|
||||
}
|
||||
|
||||
func generateSchemas(v *viper.Viper) error {
|
||||
// we generate schemas from the config/crds in the root of this project
|
||||
// those crds can be created from controller-gen or by running `make openapischema`
|
||||
|
||||
workdir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get workdir")
|
||||
}
|
||||
|
||||
files := []struct {
|
||||
inFilename string
|
||||
outFilename string
|
||||
}{
|
||||
{
|
||||
"troubleshoot.replicated.com_preflights.yaml",
|
||||
"preflight-troubleshoot-v1beta1.json",
|
||||
},
|
||||
{
|
||||
"troubleshoot.replicated.com_analyzers.yaml",
|
||||
"analyzer-troubleshoot-v1beta1.json",
|
||||
},
|
||||
{
|
||||
"troubleshoot.replicated.com_collectors.yaml",
|
||||
"collector-troubleshoot-v1beta1.json",
|
||||
},
|
||||
{
|
||||
"troubleshoot.replicated.com_redactors.yaml",
|
||||
"redactor-troubleshoot-v1beta1.json",
|
||||
},
|
||||
{
|
||||
"troubleshoot.replicated.com_supportbundles.yaml",
|
||||
"supportbundle-troubleshoot-v1beta1.json",
|
||||
},
|
||||
{
|
||||
"troubleshoot.sh_analyzers.yaml",
|
||||
"analyzer-troubleshoot-v1beta2.json",
|
||||
},
|
||||
{
|
||||
"troubleshoot.sh_collectors.yaml",
|
||||
"collector-troubleshoot-v1beta2.json",
|
||||
},
|
||||
{
|
||||
"troubleshoot.sh_preflights.yaml",
|
||||
"preflight-troubleshoot-v1beta2.json",
|
||||
},
|
||||
{
|
||||
"troubleshoot.sh_redactors.yaml",
|
||||
"redactor-troubleshoot-v1beta2.json",
|
||||
},
|
||||
{
|
||||
"troubleshoot.sh_supportbundles.yaml",
|
||||
"supportbundle-troubleshoot-v1beta2.json",
|
||||
},
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
contents, err := ioutil.ReadFile(filepath.Join(workdir, "config", "crds", file.inFilename))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to read crd from %s", file.inFilename)
|
||||
}
|
||||
if err := generateSchemaFromCRD(contents, filepath.Join(workdir, v.GetString("output-dir"), file.outFilename)); err != nil {
|
||||
return errors.Wrapf(err, "failed to write crd schema to %s", file.outFilename)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateSchemaFromCRD(crd []byte, outfile string) error {
|
||||
extensionsscheme.AddToScheme(scheme.Scheme)
|
||||
decode := scheme.Codecs.UniversalDeserializer().Decode
|
||||
obj, _, err := decode(crd, nil, nil)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to decode crd")
|
||||
}
|
||||
|
||||
customResourceDefinition := obj.(*extensionsv1.CustomResourceDefinition)
|
||||
|
||||
if len(customResourceDefinition.Spec.Versions) == 0 {
|
||||
return errors.New("no versions found for CRD")
|
||||
}
|
||||
|
||||
crdSchema := customResourceDefinition.Spec.Versions[0].Schema
|
||||
if crdSchema == nil {
|
||||
return errors.New("CRD has a nil schema")
|
||||
}
|
||||
|
||||
b, err := json.MarshalIndent(crdSchema.OpenAPIV3Schema, "", " ")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to marshal json")
|
||||
}
|
||||
|
||||
_, err = os.Stat(outfile)
|
||||
if err == nil {
|
||||
if err := os.Remove(outfile); err != nil {
|
||||
return errors.Wrap(err, "failed to remove file")
|
||||
}
|
||||
}
|
||||
|
||||
d, _ := path.Split(outfile)
|
||||
_, err = os.Stat(d)
|
||||
if os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(d, 0755); err != nil {
|
||||
return errors.Wrap(err, "failed to mkdir")
|
||||
}
|
||||
}
|
||||
|
||||
// whoa now
|
||||
// working around the fact that controller-gen doesn't have tags to generate oneOf schemas, so this is hacky.
|
||||
// going to work to add an issue there to support and if they accept, this terrible thing can go away
|
||||
boolStringed := strings.ReplaceAll(string(b), `"type": "BoolString"`, `"oneOf": [{"type": "string"},{"type": "boolean"}]`)
|
||||
|
||||
err = ioutil.WriteFile(outfile, []byte(boolStringed), 0644)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to write file")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/replicatedhq/troubleshoot/cmd/schemagen/cli"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cli.InitAndExecute()
|
||||
}
|
||||
@@ -1,10 +1,13 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/replicatedhq/troubleshoot/cmd/util"
|
||||
analyzer "github.com/replicatedhq/troubleshoot/pkg/analyze"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/convert"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/logger"
|
||||
@@ -15,11 +18,12 @@ import (
|
||||
|
||||
func Analyze() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "analyze",
|
||||
Use: "analyze [url]",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Short: "analyze a support bundle",
|
||||
Long: `...`,
|
||||
Long: `Analyze a support bundle using the Analyzer definitions provided`,
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
viper.BindPFlag("url", cmd.Flags().Lookup("url"))
|
||||
viper.BindPFlag("bundle", cmd.Flags().Lookup("bundle"))
|
||||
viper.BindPFlag("output", cmd.Flags().Lookup("output"))
|
||||
viper.BindPFlag("quiet", cmd.Flags().Lookup("quiet"))
|
||||
},
|
||||
@@ -28,7 +32,13 @@ func Analyze() *cobra.Command {
|
||||
|
||||
logger.SetQuiet(v.GetBool("quiet"))
|
||||
|
||||
result, err := analyzer.DownloadAndAnalyze(context.TODO(), v.GetString("url"))
|
||||
specPath := args[0]
|
||||
analyzerSpec, err := downloadAnalyzerSpec(specPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result, err := analyzer.DownloadAndAnalyze(v.GetString("bundle"), analyzerSpec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -60,8 +70,8 @@ func Analyze() *cobra.Command {
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().String("url", "", "URL of the support bundle to analyze")
|
||||
cmd.MarkFlagRequired("url")
|
||||
cmd.Flags().String("bundle", "", "filename of the support bundle to analyze")
|
||||
cmd.MarkFlagRequired("bundle")
|
||||
cmd.Flags().String("output", "", "output format: json, yaml")
|
||||
cmd.Flags().String("compatibility", "", "output compatibility mode: support-bundle")
|
||||
cmd.Flags().MarkHidden("compatibility")
|
||||
@@ -71,3 +81,43 @@ func Analyze() *cobra.Command {
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func downloadAnalyzerSpec(specPath string) (string, error) {
|
||||
specContent := ""
|
||||
var err error
|
||||
if _, err = os.Stat(specPath); err == nil {
|
||||
if _, err := os.Stat(specPath); os.IsNotExist(err) {
|
||||
return "", fmt.Errorf("%s was not found", specPath)
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadFile(specPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
specContent = string(b)
|
||||
} else {
|
||||
if !util.IsURL(specPath) {
|
||||
return "", fmt.Errorf("%s is not a URL and was not found (err %s)", specPath, err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", specPath, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
req.Header.Set("User-Agent", "Replicated_Analyzer/v1beta1")
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
specContent = string(body)
|
||||
}
|
||||
return specContent, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,274 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
ui "github.com/replicatedhq/termui/v3"
|
||||
"github.com/replicatedhq/termui/v3/widgets"
|
||||
"github.com/replicatedhq/troubleshoot/cmd/util"
|
||||
analyzerunner "github.com/replicatedhq/troubleshoot/pkg/analyze"
|
||||
)
|
||||
|
||||
var (
|
||||
selectedResult = 0
|
||||
table = widgets.NewTable()
|
||||
isShowingSaved = false
|
||||
)
|
||||
|
||||
func showInteractiveResults(supportBundleName string, analyzeResults []*analyzerunner.AnalyzeResult) error {
|
||||
if err := ui.Init(); err != nil {
|
||||
return errors.Wrap(err, "failed to create terminal ui")
|
||||
}
|
||||
defer ui.Close()
|
||||
drawUI(supportBundleName, analyzeResults)
|
||||
|
||||
uiEvents := ui.PollEvents()
|
||||
for {
|
||||
select {
|
||||
case e := <-uiEvents:
|
||||
switch e.ID {
|
||||
case "<C-c>":
|
||||
return nil
|
||||
case "q":
|
||||
if isShowingSaved == true {
|
||||
isShowingSaved = false
|
||||
ui.Clear()
|
||||
drawUI(supportBundleName, analyzeResults)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
case "s":
|
||||
filename, err := save(analyzeResults)
|
||||
if err != nil {
|
||||
// show
|
||||
} else {
|
||||
showSaved(filename)
|
||||
go func() {
|
||||
time.Sleep(time.Second * 5)
|
||||
isShowingSaved = false
|
||||
ui.Clear()
|
||||
drawUI(supportBundleName, analyzeResults)
|
||||
}()
|
||||
}
|
||||
case "<Resize>":
|
||||
ui.Clear()
|
||||
drawUI(supportBundleName, analyzeResults)
|
||||
case "<Down>":
|
||||
if selectedResult < len(analyzeResults)-1 {
|
||||
selectedResult++
|
||||
} else {
|
||||
selectedResult = 0
|
||||
table.SelectedRow = 0
|
||||
}
|
||||
table.ScrollDown()
|
||||
ui.Clear()
|
||||
drawUI(supportBundleName, analyzeResults)
|
||||
case "<Up>":
|
||||
if selectedResult > 0 {
|
||||
selectedResult--
|
||||
} else {
|
||||
selectedResult = len(analyzeResults) - 1
|
||||
table.SelectedRow = len(analyzeResults)
|
||||
}
|
||||
table.ScrollUp()
|
||||
ui.Clear()
|
||||
drawUI(supportBundleName, analyzeResults)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func drawUI(supportBundleName string, analyzeResults []*analyzerunner.AnalyzeResult) {
|
||||
drawGrid(analyzeResults)
|
||||
drawHeader(supportBundleName)
|
||||
drawFooter()
|
||||
}
|
||||
|
||||
func drawGrid(analyzeResults []*analyzerunner.AnalyzeResult) {
|
||||
drawAnalyzersTable(analyzeResults)
|
||||
drawDetails(analyzeResults[selectedResult])
|
||||
}
|
||||
|
||||
func drawHeader(supportBundleName string) {
|
||||
termWidth, _ := ui.TerminalDimensions()
|
||||
|
||||
title := widgets.NewParagraph()
|
||||
title.Text = fmt.Sprintf("%s Support Bundle Analysis", util.AppName(supportBundleName))
|
||||
title.TextStyle.Fg = ui.ColorWhite
|
||||
title.TextStyle.Bg = ui.ColorClear
|
||||
title.TextStyle.Modifier = ui.ModifierBold
|
||||
title.Border = false
|
||||
|
||||
left := termWidth/2 - 2*len(title.Text)/3
|
||||
right := termWidth/2 + (termWidth/2 - left)
|
||||
|
||||
title.SetRect(left, 0, right, 1)
|
||||
ui.Render(title)
|
||||
}
|
||||
|
||||
func drawFooter() {
|
||||
termWidth, termHeight := ui.TerminalDimensions()
|
||||
|
||||
instructions := widgets.NewParagraph()
|
||||
instructions.Text = "[q] quit [s] save [↑][↓] scroll"
|
||||
instructions.Border = false
|
||||
|
||||
left := 0
|
||||
right := termWidth
|
||||
top := termHeight - 1
|
||||
bottom := termHeight
|
||||
|
||||
instructions.SetRect(left, top, right, bottom)
|
||||
ui.Render(instructions)
|
||||
}
|
||||
|
||||
func drawAnalyzersTable(analyzeResults []*analyzerunner.AnalyzeResult) {
|
||||
termWidth, termHeight := ui.TerminalDimensions()
|
||||
|
||||
table.SetRect(0, 3, termWidth/2, termHeight-6)
|
||||
table.FillRow = true
|
||||
table.Border = true
|
||||
table.Rows = [][]string{}
|
||||
table.ColumnWidths = []int{termWidth}
|
||||
|
||||
for i, analyzeResult := range analyzeResults {
|
||||
title := analyzeResult.Title
|
||||
if analyzeResult.IsPass {
|
||||
title = fmt.Sprintf("✔ %s", title)
|
||||
} else if analyzeResult.IsWarn {
|
||||
title = fmt.Sprintf("⚠️ %s", title)
|
||||
} else if analyzeResult.IsFail {
|
||||
title = fmt.Sprintf("✘ %s", title)
|
||||
}
|
||||
table.Rows = append(table.Rows, []string{
|
||||
title,
|
||||
})
|
||||
|
||||
if analyzeResult.IsPass {
|
||||
if i == selectedResult {
|
||||
table.RowStyles[i] = ui.NewStyle(ui.ColorGreen, ui.ColorClear, ui.ModifierReverse)
|
||||
} else {
|
||||
table.RowStyles[i] = ui.NewStyle(ui.ColorGreen, ui.ColorClear)
|
||||
}
|
||||
} else if analyzeResult.IsWarn {
|
||||
if i == selectedResult {
|
||||
table.RowStyles[i] = ui.NewStyle(ui.ColorYellow, ui.ColorClear, ui.ModifierReverse)
|
||||
} else {
|
||||
table.RowStyles[i] = ui.NewStyle(ui.ColorYellow, ui.ColorClear)
|
||||
}
|
||||
} else if analyzeResult.IsFail {
|
||||
if i == selectedResult {
|
||||
table.RowStyles[i] = ui.NewStyle(ui.ColorRed, ui.ColorClear, ui.ModifierReverse)
|
||||
} else {
|
||||
table.RowStyles[i] = ui.NewStyle(ui.ColorRed, ui.ColorClear)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ui.Render(table)
|
||||
}
|
||||
|
||||
func drawDetails(analysisResult *analyzerunner.AnalyzeResult) {
|
||||
termWidth, _ := ui.TerminalDimensions()
|
||||
|
||||
currentTop := 4
|
||||
title := widgets.NewParagraph()
|
||||
title.Text = analysisResult.Title
|
||||
title.Border = false
|
||||
if analysisResult.IsPass {
|
||||
title.TextStyle = ui.NewStyle(ui.ColorGreen, ui.ColorClear, ui.ModifierBold)
|
||||
} else if analysisResult.IsWarn {
|
||||
title.TextStyle = ui.NewStyle(ui.ColorYellow, ui.ColorClear, ui.ModifierBold)
|
||||
} else if analysisResult.IsFail {
|
||||
title.TextStyle = ui.NewStyle(ui.ColorRed, ui.ColorClear, ui.ModifierBold)
|
||||
}
|
||||
height := estimateNumberOfLines(title.Text, termWidth/2)
|
||||
title.SetRect(termWidth/2, currentTop, termWidth, currentTop+height)
|
||||
ui.Render(title)
|
||||
currentTop = currentTop + height + 1
|
||||
|
||||
message := widgets.NewParagraph()
|
||||
message.Text = analysisResult.Message
|
||||
message.Border = false
|
||||
height = estimateNumberOfLines(message.Text, termWidth/2) + 2
|
||||
message.SetRect(termWidth/2, currentTop, termWidth, currentTop+height)
|
||||
ui.Render(message)
|
||||
currentTop = currentTop + height + 1
|
||||
|
||||
if analysisResult.URI != "" {
|
||||
uri := widgets.NewParagraph()
|
||||
uri.Text = fmt.Sprintf("For more information: %s", analysisResult.URI)
|
||||
uri.Border = false
|
||||
height = estimateNumberOfLines(uri.Text, termWidth/2)
|
||||
uri.SetRect(termWidth/2, currentTop, termWidth, currentTop+height)
|
||||
ui.Render(uri)
|
||||
currentTop = currentTop + height + 1
|
||||
}
|
||||
}
|
||||
|
||||
func estimateNumberOfLines(text string, width int) int {
|
||||
lines := len(text)/width + 1
|
||||
return lines
|
||||
}
|
||||
|
||||
func showSaved(filename string) {
|
||||
termWidth, termHeight := ui.TerminalDimensions()
|
||||
|
||||
savedMessage := widgets.NewParagraph()
|
||||
savedMessage.Text = fmt.Sprintf("Support Bundle analysis results saved to\n\n%s", filename)
|
||||
savedMessage.WrapText = true
|
||||
savedMessage.Border = true
|
||||
|
||||
left := termWidth/2 - 20
|
||||
right := termWidth/2 + 20
|
||||
top := termHeight/2 - 4
|
||||
bottom := termHeight/2 + 4
|
||||
|
||||
savedMessage.SetRect(left, top, right, bottom)
|
||||
ui.Render(savedMessage)
|
||||
|
||||
isShowingSaved = true
|
||||
}
|
||||
|
||||
func save(analyzeResults []*analyzerunner.AnalyzeResult) (string, error) {
|
||||
filename := path.Join(util.HomeDir(), fmt.Sprintf("%s-results.txt", "support-bundle"))
|
||||
_, err := os.Stat(filename)
|
||||
if err == nil {
|
||||
os.Remove(filename)
|
||||
}
|
||||
|
||||
results := ""
|
||||
for _, analyzeResult := range analyzeResults {
|
||||
result := ""
|
||||
|
||||
if analyzeResult.IsPass {
|
||||
result = "Check PASS\n"
|
||||
} else if analyzeResult.IsWarn {
|
||||
result = "Check WARN\n"
|
||||
} else if analyzeResult.IsFail {
|
||||
result = "Check FAIL\n"
|
||||
}
|
||||
|
||||
result = result + fmt.Sprintf("Title: %s\n", analyzeResult.Title)
|
||||
result = result + fmt.Sprintf("Message: %s\n", analyzeResult.Message)
|
||||
|
||||
if analyzeResult.URI != "" {
|
||||
result = result + fmt.Sprintf("URI: %s\n", analyzeResult.URI)
|
||||
}
|
||||
|
||||
result = result + "\n------------\n"
|
||||
|
||||
results = results + result
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(filename, []byte(results), 0644); err != nil {
|
||||
return "", errors.Wrap(err, "failed to save preflight results")
|
||||
}
|
||||
|
||||
return filename, nil
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/mholt/archiver"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/logger"
|
||||
kuberneteserrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func receiveSupportBundle(collectorJobNamespace string, collectorJobName string) error {
|
||||
// poll until there are no more "running" collectors
|
||||
troubleshootClient, err := createTroubleshootK8sClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bundlePath, err := ioutil.TempDir("", "troubleshoot")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(bundlePath)
|
||||
|
||||
versionFilename, err := writeVersionFile(bundlePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
receivedCollectors := []string{}
|
||||
for {
|
||||
job, err := troubleshootClient.CollectorJobs(collectorJobNamespace).Get(collectorJobName, metav1.GetOptions{})
|
||||
if err != nil && kuberneteserrors.IsNotFound(err) {
|
||||
// where did it go!
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, readyCollector := range job.Status.Successful {
|
||||
alreadyReceived := false
|
||||
for _, receivedCollector := range receivedCollectors {
|
||||
if receivedCollector == readyCollector {
|
||||
alreadyReceived = true
|
||||
}
|
||||
}
|
||||
|
||||
if alreadyReceived {
|
||||
continue
|
||||
}
|
||||
|
||||
collectorResp, err := http.Get(fmt.Sprintf("http://localhost:8000/collector/%s", readyCollector))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer collectorResp.Body.Close()
|
||||
body, err := ioutil.ReadAll(collectorResp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
decoded, err := base64.StdEncoding.DecodeString(string(body))
|
||||
if err != nil {
|
||||
logger.Printf("failed to output for collector %s\n", readyCollector)
|
||||
continue
|
||||
}
|
||||
|
||||
files := make(map[string]interface{})
|
||||
if err := json.Unmarshal(decoded, &files); err != nil {
|
||||
logger.Printf("failed to unmarshal output for collector %s\n", readyCollector)
|
||||
}
|
||||
|
||||
for filename, maybeContents := range files {
|
||||
fileDir, fileName := filepath.Split(filename)
|
||||
outPath := filepath.Join(bundlePath, fileDir)
|
||||
|
||||
if err := os.MkdirAll(outPath, 0777); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch maybeContents.(type) {
|
||||
case string:
|
||||
decoded, err := base64.StdEncoding.DecodeString(maybeContents.(string))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeFile(filepath.Join(outPath, fileName), decoded); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case map[string]interface{}:
|
||||
for k, v := range maybeContents.(map[string]interface{}) {
|
||||
s, _ := filepath.Split(filepath.Join(outPath, fileName, k))
|
||||
if err := os.MkdirAll(s, 0777); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
decoded, err := base64.StdEncoding.DecodeString(v.(string))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeFile(filepath.Join(outPath, fileName, k), decoded); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
receivedCollectors = append(receivedCollectors, readyCollector)
|
||||
}
|
||||
|
||||
if len(job.Status.Running) == 0 {
|
||||
tarGz := archiver.TarGz{
|
||||
Tar: &archiver.Tar{
|
||||
ImplicitTopLevelFolder: false,
|
||||
},
|
||||
}
|
||||
|
||||
// version file should be first in tar archive for quick extraction
|
||||
paths := []string{
|
||||
versionFilename,
|
||||
}
|
||||
for _, id := range receivedCollectors {
|
||||
paths = append(paths, filepath.Join(bundlePath, id))
|
||||
}
|
||||
|
||||
if err := tarGz.Archive(paths, "support-bundle.tar.gz"); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func writeFile(filename string, contents []byte) error {
|
||||
if err := ioutil.WriteFile(filename, contents, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"path/filepath"
|
||||
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/logger"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func Retrieve() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "retrieve",
|
||||
Short: "retrieve a support bundle from a job in the cluster",
|
||||
Long: `...`,
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
viper.BindPFlag("collectors", cmd.Flags().Lookup("collectors"))
|
||||
viper.BindPFlag("namespace", cmd.Flags().Lookup("namespace"))
|
||||
viper.BindPFlag("kubecontext", cmd.Flags().Lookup("kubecontext"))
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
v := viper.GetViper()
|
||||
|
||||
troubleshootClient, err := createTroubleshootK8sClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
collectorJobs, err := troubleshootClient.CollectorJobs(v.GetString("namespace")).List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var collectorJob *troubleshootv1beta1.CollectorJob
|
||||
if v.GetString("collectors") == "" && len(collectorJobs.Items) == 1 {
|
||||
collectorJob = &collectorJobs.Items[0]
|
||||
} else {
|
||||
for _, foundCollectorJob := range collectorJobs.Items {
|
||||
if foundCollectorJob.Spec.Collector.Name == v.GetString("collectors") {
|
||||
collectorJob = &foundCollectorJob
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if collectorJob == nil {
|
||||
logger.Printf("unable to find collector job\n")
|
||||
return errors.New("no collectors")
|
||||
}
|
||||
|
||||
logger.Printf("connecting to collector job %s\n", collectorJob.Name)
|
||||
|
||||
stopChan, err := k8sutil.PortForward(v.GetString("kubecontext"), 8000, 8000, collectorJob.Status.ServerPodNamespace, collectorJob.Status.ServerPodName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := receiveSupportBundle(collectorJob.Namespace, collectorJob.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// write a zip
|
||||
|
||||
close(stopChan)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().String("collectors", "", "name of the collectors to use")
|
||||
cmd.Flags().String("namespace", "", "namespace the collectors can be found in")
|
||||
|
||||
cmd.Flags().String("kubecontext", filepath.Join(homeDir(), ".kube", "config"), "the kubecontext to use when connecting")
|
||||
|
||||
viper.BindPFlags(cmd.Flags())
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/logger"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
@@ -14,11 +14,12 @@ import (
|
||||
|
||||
func RootCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "troubleshoot [url]",
|
||||
Use: "support-bundle [url]",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Short: "Generate and manage support bundles",
|
||||
Short: "Generate a support bundle",
|
||||
Long: `A support bundle is an archive of files, output, metrics and state
|
||||
from a server that can be used to assist when troubleshooting a server.`,
|
||||
from a server that can be used to assist when troubleshooting a Kubernetes cluster.`,
|
||||
SilenceUsage: true,
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
viper.BindPFlags(cmd.Flags())
|
||||
},
|
||||
@@ -26,38 +27,38 @@ from a server that can be used to assist when troubleshooting a server.`,
|
||||
v := viper.GetViper()
|
||||
|
||||
logger.SetQuiet(v.GetBool("quiet"))
|
||||
|
||||
if len(args) == 0 {
|
||||
return runTroubleshootCRD(v)
|
||||
}
|
||||
|
||||
return runTroubleshootNoCRD(v, args[0])
|
||||
return runTroubleshoot(v, args[0])
|
||||
},
|
||||
}
|
||||
|
||||
cobra.OnInitialize(initConfig)
|
||||
|
||||
cmd.AddCommand(Analyze())
|
||||
cmd.AddCommand(VersionCmd())
|
||||
|
||||
cmd.Flags().String("collectors", "", "name of the collectors to use")
|
||||
cmd.Flags().String("namespace", "default", "namespace the collectors can be found in")
|
||||
|
||||
cmd.Flags().String("kubecontext", filepath.Join(homeDir(), ".kube", "config"), "the kubecontext to use when connecting")
|
||||
|
||||
cmd.Flags().String("image", "", "the full name of the collector image to use")
|
||||
cmd.Flags().String("pullpolicy", "", "the pull policy of the collector image")
|
||||
cmd.Flags().StringSlice("redactors", []string{}, "names of the additional redactors to use")
|
||||
cmd.Flags().Bool("redact", true, "enable/disable default redactions")
|
||||
cmd.Flags().Bool("interactive", true, "enable/disable interactive mode")
|
||||
cmd.Flags().Bool("collect-without-permissions", true, "always generate a support bundle, even if it some require additional permissions")
|
||||
cmd.Flags().String("since-time", "", "force pod logs collectors to return logs after a specific date (RFC3339)")
|
||||
cmd.Flags().String("since", "", "force pod logs collectors to return logs newer than a relative duration like 5s, 2m, or 3h.")
|
||||
cmd.Flags().StringP("output", "o", "", "specify the output file path for the support bundle")
|
||||
|
||||
// hidden in favor of the `insecure-skip-tls-verify` flag
|
||||
cmd.Flags().Bool("allow-insecure-connections", false, "when set, do not verify TLS certs when retrieving spec and reporting results")
|
||||
cmd.Flags().MarkHidden("allow-insecure-connections")
|
||||
|
||||
cmd.Flags().String("serviceaccount", "", "name of the service account to use. if not provided, one will be created")
|
||||
viper.BindPFlags(cmd.Flags())
|
||||
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
|
||||
|
||||
k8sutil.AddFlags(cmd.Flags())
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func InitAndExecute() {
|
||||
if err := RootCmd().Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -67,7 +68,7 @@ func initConfig() {
|
||||
viper.AutomaticEnv()
|
||||
}
|
||||
|
||||
func ensureCollectorInList(list []*troubleshootv1beta1.Collect, collector troubleshootv1beta1.Collect) []*troubleshootv1beta1.Collect {
|
||||
func ensureCollectorInList(list []*troubleshootv1beta2.Collect, collector troubleshootv1beta2.Collect) []*troubleshootv1beta2.Collect {
|
||||
for _, inList := range list {
|
||||
if collector.ClusterResources != nil && inList.ClusterResources != nil {
|
||||
return list
|
||||
@@ -79,3 +80,11 @@ func ensureCollectorInList(list []*troubleshootv1beta1.Collect, collector troubl
|
||||
|
||||
return append(list, &collector)
|
||||
}
|
||||
|
||||
func writeFile(filename string, contents []byte) error {
|
||||
if err := ioutil.WriteFile(filename, contents, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,327 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
cursor "github.com/ahmetalpbalkan/go-cursor"
|
||||
"github.com/fatih/color"
|
||||
"github.com/manifoldco/promptui"
|
||||
"github.com/mattn/go-isatty"
|
||||
"github.com/pkg/errors"
|
||||
analyzer "github.com/replicatedhq/troubleshoot/pkg/analyze"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/scheme"
|
||||
troubleshootclientsetscheme "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/scheme"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/convert"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/docrewrite"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/httputil"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/supportbundle"
|
||||
"github.com/spf13/viper"
|
||||
spin "github.com/tj/go-spin"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
func runTroubleshoot(v *viper.Viper, arg string) error {
|
||||
interactive := v.GetBool("interactive") && isatty.IsTerminal(os.Stdout.Fd())
|
||||
|
||||
if interactive {
|
||||
fmt.Print(cursor.Hide())
|
||||
defer fmt.Print(cursor.Show())
|
||||
}
|
||||
|
||||
go func() {
|
||||
signalChan := make(chan os.Signal, 1)
|
||||
signal.Notify(signalChan, os.Interrupt)
|
||||
<-signalChan
|
||||
if interactive {
|
||||
fmt.Print(cursor.Show())
|
||||
}
|
||||
os.Exit(0)
|
||||
}()
|
||||
|
||||
restConfig, err := k8sutil.GetRESTConfig()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to convert kube flags to rest config")
|
||||
}
|
||||
|
||||
var sinceTime *time.Time
|
||||
if v.GetString("since-time") != "" || v.GetString("since") != "" {
|
||||
sinceTime, err = parseTimeFlags(v)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed parse since time")
|
||||
}
|
||||
}
|
||||
|
||||
if v.GetBool("allow-insecure-connections") || v.GetBool("insecure-skip-tls-verify") {
|
||||
httputil.AddTransport(&http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
})
|
||||
}
|
||||
|
||||
collectorContent, err := supportbundle.LoadSupportBundleSpec(arg)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to load collector spec")
|
||||
}
|
||||
|
||||
multidocs := strings.Split(string(collectorContent), "\n---\n")
|
||||
|
||||
// we support both raw collector kinds and supportbundle kinds here
|
||||
supportBundle, err := supportbundle.ParseSupportBundleFromDoc([]byte(multidocs[0]))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to parse collector")
|
||||
}
|
||||
|
||||
troubleshootclientsetscheme.AddToScheme(scheme.Scheme)
|
||||
decode := scheme.Codecs.UniversalDeserializer().Decode
|
||||
|
||||
additionalRedactors := &troubleshootv1beta2.Redactor{}
|
||||
for idx, redactor := range v.GetStringSlice("redactors") {
|
||||
redactorObj, err := supportbundle.GetRedactorFromURI(redactor)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to get redactor spec %s, #%d", redactor, idx)
|
||||
}
|
||||
|
||||
if redactorObj != nil {
|
||||
additionalRedactors.Spec.Redactors = append(additionalRedactors.Spec.Redactors, redactorObj.Spec.Redactors...)
|
||||
}
|
||||
}
|
||||
|
||||
for i, additionalDoc := range multidocs {
|
||||
if i == 0 {
|
||||
continue
|
||||
}
|
||||
additionalDoc, err := docrewrite.ConvertToV1Beta2([]byte(additionalDoc))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to convert to v1beta2")
|
||||
}
|
||||
obj, _, err := decode(additionalDoc, nil, nil)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to parse additional doc %d", i)
|
||||
}
|
||||
multidocRedactors, ok := obj.(*troubleshootv1beta2.Redactor)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
additionalRedactors.Spec.Redactors = append(additionalRedactors.Spec.Redactors, multidocRedactors.Spec.Redactors...)
|
||||
}
|
||||
|
||||
var collectorCB func(chan interface{}, string)
|
||||
progressChan := make(chan interface{}) // non-zero buffer can result in missed messages
|
||||
finishedCh := make(chan bool, 1)
|
||||
isFinishedChClosed := false
|
||||
|
||||
if !interactive {
|
||||
// TODO (dans): custom warning handler to capture warning in `analysisOutput`
|
||||
restConfig.WarningHandler = rest.NoWarnings{}
|
||||
collectorCB = func(ch chan interface{}, name string) {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO (dans): maybe log to file
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case _ = <-progressChan:
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
}()
|
||||
} else {
|
||||
s := spin.New()
|
||||
go func() {
|
||||
currentDir := ""
|
||||
for {
|
||||
select {
|
||||
case msg := <-progressChan:
|
||||
switch msg := msg.(type) {
|
||||
case error:
|
||||
c := color.New(color.FgHiRed)
|
||||
c.Println(fmt.Sprintf("%s\r * %v", cursor.ClearEntireLine(), msg))
|
||||
case string:
|
||||
currentDir = filepath.Base(msg)
|
||||
}
|
||||
case <-finishedCh:
|
||||
fmt.Printf("\r%s\r", cursor.ClearEntireLine())
|
||||
return
|
||||
case <-time.After(time.Millisecond * 100):
|
||||
if currentDir == "" {
|
||||
fmt.Printf("\r%s \033[36mCollecting support bundle\033[m %s", cursor.ClearEntireLine(), s.Next())
|
||||
} else {
|
||||
fmt.Printf("\r%s \033[36mCollecting support bundle\033[m %s %s", cursor.ClearEntireLine(), s.Next(), currentDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
defer func() {
|
||||
if !isFinishedChClosed {
|
||||
close(finishedCh)
|
||||
}
|
||||
}()
|
||||
|
||||
collectorCB = func(c chan interface{}, msg string) {
|
||||
c <- fmt.Sprintf("%s", msg)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
createOpts := supportbundle.SupportBundleCreateOpts{
|
||||
CollectorProgressCallback: collectorCB,
|
||||
CollectWithoutPermissions: v.GetBool("collect-without-permissions"),
|
||||
KubernetesRestConfig: restConfig,
|
||||
Namespace: v.GetString("namespace"),
|
||||
ProgressChan: progressChan,
|
||||
SinceTime: sinceTime,
|
||||
OutputPath: v.GetString("output"),
|
||||
Redact: v.GetBool("redact"),
|
||||
FromCLI: true,
|
||||
}
|
||||
|
||||
nonInteractiveOutput := analysisOutput{}
|
||||
|
||||
if interactive {
|
||||
c := color.New()
|
||||
c.Println(fmt.Sprintf("\r%s\r", cursor.ClearEntireLine()))
|
||||
}
|
||||
|
||||
response, err := supportbundle.CollectSupportBundleFromSpec(&supportBundle.Spec, additionalRedactors, createOpts)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to run collect and analyze process")
|
||||
}
|
||||
if len(response.AnalyzerResults) > 0 {
|
||||
if interactive {
|
||||
close(finishedCh) // this removes the spinner
|
||||
isFinishedChClosed = true
|
||||
|
||||
if err := showInteractiveResults(supportBundle.Name, response.AnalyzerResults); err != nil {
|
||||
interactive = false
|
||||
}
|
||||
} else {
|
||||
nonInteractiveOutput.Analysis = response.AnalyzerResults
|
||||
}
|
||||
}
|
||||
|
||||
if !response.FileUploaded {
|
||||
if appName := supportBundle.Labels["applicationName"]; appName != "" {
|
||||
f := `A support bundle for %s has been created in this directory
|
||||
named %s. Please upload it on the Troubleshoot page of
|
||||
the %s Admin Console to begin analysis.`
|
||||
fmt.Printf(f, appName, response.ArchivePath, appName)
|
||||
return nil
|
||||
}
|
||||
|
||||
if !interactive {
|
||||
nonInteractiveOutput.ArchivePath = response.ArchivePath
|
||||
output, err := nonInteractiveOutput.FormattedAnalysisOutput()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to format non-interactive output")
|
||||
}
|
||||
fmt.Println(output)
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Printf("%s\n", response.ArchivePath)
|
||||
return nil
|
||||
}
|
||||
|
||||
if interactive {
|
||||
fmt.Printf("\r%s\r", cursor.ClearEntireLine())
|
||||
}
|
||||
if response.FileUploaded {
|
||||
fmt.Printf("A support bundle has been created and uploaded to your cluster for analysis. Please visit the Troubleshoot page to continue.\n")
|
||||
fmt.Printf("A copy of this support bundle was written to the current directory, named %q\n", response.ArchivePath)
|
||||
} else {
|
||||
fmt.Printf("A support bundle has been created in the current directory named %q\n", response.ArchivePath)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getExpectedContentType(uploadURL string) string {
|
||||
parsedURL, err := url.Parse(uploadURL)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return parsedURL.Query().Get("Content-Type")
|
||||
}
|
||||
|
||||
func parseTimeFlags(v *viper.Viper) (*time.Time, error) {
|
||||
var (
|
||||
sinceTime time.Time
|
||||
err error
|
||||
)
|
||||
if v.GetString("since-time") != "" {
|
||||
if v.GetString("since") != "" {
|
||||
return nil, errors.Errorf("at most one of `sinceTime` or `since` may be specified")
|
||||
}
|
||||
sinceTime, err = time.Parse(time.RFC3339, v.GetString("since-time"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to parse --since-time flag")
|
||||
}
|
||||
} else {
|
||||
parsedDuration, err := time.ParseDuration(v.GetString("since"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to parse --since flag")
|
||||
}
|
||||
now := time.Now()
|
||||
sinceTime = now.Add(0 - parsedDuration)
|
||||
}
|
||||
|
||||
return &sinceTime, nil
|
||||
}
|
||||
|
||||
func shouldRetryRequest(err error) bool {
|
||||
if strings.Contains(err.Error(), "x509") && canTryInsecure() {
|
||||
httputil.AddTransport(&http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
})
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func canTryInsecure() bool {
|
||||
if !isatty.IsTerminal(os.Stdout.Fd()) {
|
||||
return false
|
||||
}
|
||||
prompt := promptui.Prompt{
|
||||
Label: "Connection appears to be insecure. Would you like to attempt to create a support bundle anyway?",
|
||||
IsConfirm: true,
|
||||
}
|
||||
|
||||
_, err := prompt.Run()
|
||||
return err == nil
|
||||
}
|
||||
|
||||
type analysisOutput struct {
|
||||
Analysis []*analyzer.AnalyzeResult
|
||||
ArchivePath string
|
||||
}
|
||||
|
||||
func (a *analysisOutput) FormattedAnalysisOutput() (outputJson string, err error) {
|
||||
type convertedOutput struct {
|
||||
ConvertedAnalysis []*convert.Result `json:"analyzerResults"`
|
||||
ArchivePath string `json:"archivePath"`
|
||||
}
|
||||
|
||||
converted := convert.FromAnalyzerResult(a.Analysis)
|
||||
|
||||
o := convertedOutput{
|
||||
ConvertedAnalysis: converted,
|
||||
ArchivePath: a.ArchivePath,
|
||||
}
|
||||
|
||||
formatted, err := json.MarshalIndent(o, "", " ")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("\r * Failed to format analysis: %v\n", err)
|
||||
}
|
||||
return string(formatted), nil
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
|
||||
"github.com/spf13/viper"
|
||||
kuberneteserrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func runTroubleshootCRD(v *viper.Viper) error {
|
||||
troubleshootClient, err := createTroubleshootK8sClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
collectorName := v.GetString("collectors")
|
||||
if collectorName == "" {
|
||||
collectors, err := troubleshootClient.Collectors(v.GetString("namespace")).List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(collectors.Items) == 1 {
|
||||
collectorName = collectors.Items[0].Name
|
||||
}
|
||||
}
|
||||
|
||||
if collectorName == "" {
|
||||
return errors.New("unknown collectors, try using the --collectors flags")
|
||||
}
|
||||
|
||||
// generate a unique name
|
||||
now := time.Now()
|
||||
suffix := fmt.Sprintf("%d", now.Unix())
|
||||
|
||||
collectorJobName := fmt.Sprintf("%s-job-%s", collectorName, suffix[len(suffix)-4:])
|
||||
collectorJob := troubleshootv1beta1.CollectorJob{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: collectorJobName,
|
||||
Namespace: v.GetString("namespace"),
|
||||
},
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "collectorjob.troubleshoot.replicated.com",
|
||||
},
|
||||
Spec: troubleshootv1beta1.CollectorJobSpec{
|
||||
Collector: troubleshootv1beta1.CollectorRef{
|
||||
Name: collectorName,
|
||||
Namespace: v.GetString("namespace"),
|
||||
},
|
||||
Image: v.GetString("image"),
|
||||
ImagePullPolicy: v.GetString("pullpolicy"),
|
||||
Redact: v.GetBool("redact"),
|
||||
},
|
||||
}
|
||||
if _, err := troubleshootClient.CollectorJobs(v.GetString("namespace")).Create(&collectorJob); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Poll the status of the Custom Resource for it to include a callback
|
||||
var found *troubleshootv1beta1.CollectorJob
|
||||
start := time.Now()
|
||||
for {
|
||||
current, err := troubleshootClient.CollectorJobs(v.GetString("namespace")).Get(collectorJobName, metav1.GetOptions{})
|
||||
if err != nil && kuberneteserrors.IsNotFound(err) {
|
||||
continue
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if current.Status.IsServerReady {
|
||||
found = current
|
||||
break
|
||||
}
|
||||
|
||||
if time.Now().Sub(start) > time.Duration(time.Second*10) {
|
||||
return errors.New("collectorjob failed to start")
|
||||
}
|
||||
|
||||
time.Sleep(time.Millisecond * 200)
|
||||
}
|
||||
|
||||
// Connect to the callback
|
||||
stopChan, err := k8sutil.PortForward(v.GetString("kubecontext"), 8000, 8000, found.Status.ServerPodNamespace, found.Status.ServerPodName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := receiveSupportBundle(found.Namespace, found.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write
|
||||
|
||||
close(stopChan)
|
||||
return nil
|
||||
}
|
||||
@@ -1,314 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/ahmetalpbalkan/go-cursor"
|
||||
"github.com/fatih/color"
|
||||
"github.com/mholt/archiver"
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/tj/go-spin"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
func runTroubleshootNoCRD(v *viper.Viper, arg string) error {
|
||||
fmt.Print(cursor.Hide())
|
||||
defer fmt.Print(cursor.Show())
|
||||
|
||||
collectorContent := ""
|
||||
if !isURL(arg) {
|
||||
if _, err := os.Stat(arg); os.IsNotExist(err) {
|
||||
return fmt.Errorf("%s was not found", arg)
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadFile(arg)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read spec file")
|
||||
}
|
||||
|
||||
collectorContent = string(b)
|
||||
} else {
|
||||
req, err := http.NewRequest("GET", arg, nil)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "make request")
|
||||
}
|
||||
req.Header.Set("User-Agent", "Replicated_Troubleshoot/v1beta1")
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "execute request")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read responce body")
|
||||
}
|
||||
|
||||
collectorContent = string(body)
|
||||
}
|
||||
|
||||
collector := troubleshootv1beta1.Collector{}
|
||||
if err := yaml.Unmarshal([]byte(collectorContent), &collector); err != nil {
|
||||
return fmt.Errorf("unable to parse %s collectors", arg)
|
||||
}
|
||||
|
||||
s := spin.New()
|
||||
finishedCh := make(chan bool, 1)
|
||||
progressChan := make(chan interface{}, 1)
|
||||
go func() {
|
||||
currentDir := ""
|
||||
for {
|
||||
select {
|
||||
case msg := <-progressChan:
|
||||
switch msg := msg.(type) {
|
||||
case error:
|
||||
c := color.New(color.FgHiRed)
|
||||
c.Println(fmt.Sprintf("%s * %v", cursor.ClearEntireLine(), msg))
|
||||
case string:
|
||||
currentDir = filepath.Base(msg)
|
||||
}
|
||||
case <-finishedCh:
|
||||
fmt.Printf("\r")
|
||||
return
|
||||
case <-time.After(time.Millisecond * 100):
|
||||
if currentDir == "" {
|
||||
fmt.Printf("\r%s \033[36mCollecting support bundle\033[m %s", cursor.ClearEntireLine(), s.Next())
|
||||
} else {
|
||||
fmt.Printf("\r%s \033[36mCollecting support bundle\033[m %s %s", cursor.ClearEntireLine(), s.Next(), currentDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
defer func() {
|
||||
finishedCh <- true
|
||||
}()
|
||||
|
||||
archivePath, err := runCollectors(v, collector, progressChan)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "run collectors")
|
||||
}
|
||||
|
||||
fmt.Printf("\r%s", cursor.ClearEntireLine())
|
||||
|
||||
if len(collector.Spec.AfterCollection) == 0 {
|
||||
msg := archivePath
|
||||
if appName := collector.Labels["applicationName"]; appName != "" {
|
||||
f := `A support bundle for %s has been created in this directory
|
||||
named %s. Please upload it on the Troubleshoot page of
|
||||
the %s Admin Console to begin analysis.`
|
||||
msg = fmt.Sprintf(f, appName, archivePath, appName)
|
||||
}
|
||||
|
||||
fmt.Printf("%s\n", msg)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, ac := range collector.Spec.AfterCollection {
|
||||
if ac.UploadResultsTo != nil {
|
||||
if err := uploadSupportBundle(ac.UploadResultsTo, archivePath); err != nil {
|
||||
return errors.Wrap(err, "upload support bundle")
|
||||
}
|
||||
} else if ac.Callback != nil {
|
||||
if err := callbackSupportBundleAPI(ac.Callback, archivePath); err != nil {
|
||||
return errors.Wrap(err, "execute callback")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("A support bundle has been created in the current directory named %q\n", archivePath)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runCollectors(v *viper.Viper, collector troubleshootv1beta1.Collector, progressChan chan interface{}) (string, error) {
|
||||
bundlePath, err := ioutil.TempDir("", "troubleshoot")
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "create temp dir")
|
||||
}
|
||||
defer os.RemoveAll(bundlePath)
|
||||
|
||||
versionFilename, err := writeVersionFile(bundlePath)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "write version file")
|
||||
}
|
||||
|
||||
desiredCollectors := make([]*troubleshootv1beta1.Collect, 0, 0)
|
||||
for _, definedCollector := range collector.Spec.Collectors {
|
||||
desiredCollectors = append(desiredCollectors, definedCollector)
|
||||
}
|
||||
desiredCollectors = ensureCollectorInList(desiredCollectors, troubleshootv1beta1.Collect{ClusterInfo: &troubleshootv1beta1.ClusterInfo{}})
|
||||
desiredCollectors = ensureCollectorInList(desiredCollectors, troubleshootv1beta1.Collect{ClusterResources: &troubleshootv1beta1.ClusterResources{}})
|
||||
|
||||
collectorDirs := []string{}
|
||||
|
||||
// Run preflights collectors synchronously
|
||||
for _, desiredCollector := range desiredCollectors {
|
||||
collector := collect.Collector{
|
||||
Redact: true,
|
||||
Collect: desiredCollector,
|
||||
}
|
||||
|
||||
result, err := collector.RunCollectorSync()
|
||||
if err != nil {
|
||||
progressChan <- fmt.Errorf("failed to run collector %q: %v", collector.GetDisplayName(), err)
|
||||
continue
|
||||
}
|
||||
|
||||
collectorDir, err := parseAndSaveCollectorOutput(string(result), bundlePath)
|
||||
if err != nil {
|
||||
progressChan <- fmt.Errorf("failed to parse collector spec %q: %v", collector.GetDisplayName(), err)
|
||||
continue
|
||||
}
|
||||
|
||||
if collectorDir == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
progressChan <- collectorDir
|
||||
collectorDirs = append(collectorDirs, collectorDir)
|
||||
}
|
||||
|
||||
tarGz := archiver.TarGz{
|
||||
Tar: &archiver.Tar{
|
||||
ImplicitTopLevelFolder: false,
|
||||
},
|
||||
}
|
||||
|
||||
// version file should be first in tar archive for quick extraction
|
||||
paths := []string{
|
||||
versionFilename,
|
||||
}
|
||||
for _, collectorDir := range collectorDirs {
|
||||
paths = append(paths, collectorDir)
|
||||
}
|
||||
|
||||
if err := tarGz.Archive(paths, "support-bundle.tar.gz"); err != nil {
|
||||
return "", errors.Wrap(err, "create archive")
|
||||
}
|
||||
|
||||
return "support-bundle.tar.gz", nil
|
||||
}
|
||||
|
||||
func parseAndSaveCollectorOutput(output string, bundlePath string) (string, error) {
|
||||
dir := ""
|
||||
|
||||
input := make(map[string]interface{})
|
||||
if err := json.Unmarshal([]byte(output), &input); err != nil {
|
||||
return "", errors.Wrap(err, "unmarshal output")
|
||||
}
|
||||
|
||||
for filename, maybeContents := range input {
|
||||
fileDir, fileName := filepath.Split(filename)
|
||||
outPath := filepath.Join(bundlePath, fileDir)
|
||||
dir = outPath
|
||||
|
||||
if err := os.MkdirAll(outPath, 0777); err != nil {
|
||||
return "", errors.Wrap(err, "create output file")
|
||||
}
|
||||
|
||||
switch maybeContents.(type) {
|
||||
case string:
|
||||
decoded, err := base64.StdEncoding.DecodeString(maybeContents.(string))
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "decode collector output")
|
||||
}
|
||||
|
||||
if err := writeFile(filepath.Join(outPath, fileName), decoded); err != nil {
|
||||
return "", errors.Wrap(err, "write collector output")
|
||||
}
|
||||
|
||||
case map[string]interface{}:
|
||||
for k, v := range maybeContents.(map[string]interface{}) {
|
||||
s, _ := filepath.Split(filepath.Join(outPath, fileName, k))
|
||||
if err := os.MkdirAll(s, 0777); err != nil {
|
||||
return "", errors.Wrap(err, "write output directories")
|
||||
}
|
||||
|
||||
decoded, err := base64.StdEncoding.DecodeString(v.(string))
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "decode output")
|
||||
}
|
||||
if err := writeFile(filepath.Join(outPath, fileName, k), decoded); err != nil {
|
||||
return "", errors.Wrap(err, "write output")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
func uploadSupportBundle(r *troubleshootv1beta1.ResultRequest, archivePath string) error {
|
||||
contentType := getExpectedContentType(r.URI)
|
||||
if contentType != "" && contentType != "application/tar+gzip" {
|
||||
return fmt.Errorf("cannot upload content type %s", contentType)
|
||||
}
|
||||
|
||||
f, err := os.Open(archivePath)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "open file")
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
fileStat, err := f.Stat()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "stat file")
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(r.Method, r.URI, f)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "create request")
|
||||
}
|
||||
req.ContentLength = fileStat.Size()
|
||||
if contentType != "" {
|
||||
req.Header.Set("Content-Type", contentType)
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "execute request")
|
||||
}
|
||||
|
||||
if resp.StatusCode >= 300 {
|
||||
return fmt.Errorf("unexpected status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getExpectedContentType(uploadURL string) string {
|
||||
parsedURL, err := url.Parse(uploadURL)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return parsedURL.Query().Get("Content-Type")
|
||||
}
|
||||
|
||||
func callbackSupportBundleAPI(r *troubleshootv1beta1.ResultRequest, archivePath string) error {
|
||||
req, err := http.NewRequest(r.Method, r.URI, nil)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "create request")
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "execute request")
|
||||
}
|
||||
|
||||
if resp.StatusCode >= 300 {
|
||||
return fmt.Errorf("unexpected status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
func createServiceAccount(collector troubleshootv1beta1.Collector, namespace string, clientset *kubernetes.Clientset) (string, error) {
|
||||
name := fmt.Sprintf("troubleshoot-%s", collector.Name)
|
||||
|
||||
serviceAccount := corev1.ServiceAccount{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "ServiceAccount",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
}
|
||||
_, err := clientset.CoreV1().ServiceAccounts(namespace).Create(&serviceAccount)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
role := rbacv1.ClusterRole{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "ClusterRole",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Rules: []rbacv1.PolicyRule{
|
||||
{
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{
|
||||
"namespaces",
|
||||
"pods",
|
||||
"services",
|
||||
"secrets",
|
||||
},
|
||||
Verbs: metav1.Verbs{"list"},
|
||||
},
|
||||
{
|
||||
APIGroups: []string{"apps"},
|
||||
Resources: []string{"deployments"},
|
||||
Verbs: metav1.Verbs{"list"},
|
||||
},
|
||||
{
|
||||
APIGroups: []string{"extensions"},
|
||||
Resources: []string{"ingresses"},
|
||||
Verbs: metav1.Verbs{"list"},
|
||||
},
|
||||
{
|
||||
APIGroups: []string{"storage.k8s.io"},
|
||||
Resources: []string{"storageclasses"},
|
||||
Verbs: metav1.Verbs{"list"},
|
||||
},
|
||||
{
|
||||
APIGroups: []string{"apiextensions.k8s.io"},
|
||||
Resources: []string{"customresourcedefinitions"},
|
||||
Verbs: metav1.Verbs{"list"},
|
||||
},
|
||||
},
|
||||
}
|
||||
_, err = clientset.RbacV1().ClusterRoles().Create(&role)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
roleBinding := rbacv1.ClusterRoleBinding{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "ClusterRoleBinding",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Subjects: []rbacv1.Subject{
|
||||
{
|
||||
Kind: "ServiceAccount",
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
},
|
||||
RoleRef: rbacv1.RoleRef{
|
||||
APIGroup: "rbac.authorization.k8s.io",
|
||||
Kind: "ClusterRole",
|
||||
Name: name,
|
||||
},
|
||||
}
|
||||
_, err = clientset.RbacV1().ClusterRoleBindings().Create(&roleBinding)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return name, nil
|
||||
}
|
||||
|
||||
func removeServiceAccount(name string, namespace string, clientset *kubernetes.Clientset) error {
|
||||
if err := clientset.RbacV1().ClusterRoleBindings().Delete(name, &metav1.DeleteOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := clientset.RbacV1().ClusterRoles().Delete(name, &metav1.DeleteOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := clientset.CoreV1().ServiceAccounts(namespace).Delete(name, &metav1.DeleteOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,41 +1,24 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
troubleshootclientv1beta1 "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta1"
|
||||
"github.com/spf13/viper"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func homeDir() string {
|
||||
if h := os.Getenv("HOME"); h != "" {
|
||||
return h
|
||||
func findFileName(basename, extension string) (string, error) {
|
||||
n := 1
|
||||
name := basename
|
||||
for {
|
||||
filename := name + "." + extension
|
||||
if _, err := os.Stat(filename); os.IsNotExist(err) {
|
||||
return filename, nil
|
||||
} else if err != nil {
|
||||
return "", errors.Wrap(err, "check file exists")
|
||||
}
|
||||
|
||||
name = fmt.Sprintf("%s (%d)", basename, n)
|
||||
n = n + 1
|
||||
}
|
||||
return os.Getenv("USERPROFILE") // windows
|
||||
}
|
||||
|
||||
func isURL(str string) bool {
|
||||
_, err := url.ParseRequestURI(str)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func createTroubleshootK8sClient() (*troubleshootclientv1beta1.TroubleshootV1beta1Client, error) {
|
||||
v := viper.GetViper()
|
||||
|
||||
config, err := clientcmd.BuildConfigFromFlags("", v.GetString("kubecontext"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
troubleshootClient, err := troubleshootclientv1beta1.NewForConfig(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return troubleshootClient, nil
|
||||
}
|
||||
|
||||
@@ -1,27 +1,51 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
"gopkg.in/yaml.v2"
|
||||
"fmt"
|
||||
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/version"
|
||||
"github.com/spf13/cobra"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
func writeVersionFile(path string) (string, error) {
|
||||
version := troubleshootv1beta1.SupportBundleVersion{
|
||||
ApiVersion: "troubleshoot.replicated.com/v1beta1",
|
||||
func VersionCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Print the current version and exit",
|
||||
Long: `Print the current version and exit`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
fmt.Printf("Replicated Troubleshoot %s\n", version.Version())
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
const VersionFilename = "version.yaml"
|
||||
|
||||
func writeVersionFile(path string) error {
|
||||
version := troubleshootv1beta2.SupportBundleVersion{
|
||||
ApiVersion: "troubleshoot.sh/v1beta2",
|
||||
Kind: "SupportBundle",
|
||||
Spec: troubleshootv1beta2.SupportBundleVersionSpec{
|
||||
VersionNumber: version.Version(),
|
||||
},
|
||||
}
|
||||
b, err := yaml.Marshal(version)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
|
||||
filename := filepath.Join(path, "version.yaml")
|
||||
filename := filepath.Join(path, VersionFilename)
|
||||
err = ioutil.WriteFile(filename, b, 0644)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
|
||||
return filename, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"github.com/replicatedhq/troubleshoot/cmd/troubleshoot/cli"
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func HomeDir() string {
|
||||
if h := os.Getenv("HOME"); h != "" {
|
||||
return h
|
||||
}
|
||||
return os.Getenv("USERPROFILE") // windows
|
||||
}
|
||||
|
||||
func IsURL(str string) bool {
|
||||
parsed, err := url.ParseRequestURI(str)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return parsed.Scheme != ""
|
||||
}
|
||||
|
||||
func AppName(name string) string {
|
||||
words := strings.Split(strings.Title(strings.Replace(name, "-", " ", -1)), " ")
|
||||
casedWords := []string{}
|
||||
for i, word := range words {
|
||||
if strings.ToLower(word) == "ai" {
|
||||
casedWords = append(casedWords, "AI")
|
||||
} else if strings.ToLower(word) == "io" && i > 0 {
|
||||
casedWords[i-1] += ".io"
|
||||
} else {
|
||||
casedWords = append(casedWords, word)
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(casedWords, " ")
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: HostCollector
|
||||
metadata:
|
||||
name: modules
|
||||
spec:
|
||||
collectors:
|
||||
- cpu: {}
|
||||
- memory: {}
|
||||
# - kernelModules: {}
|
||||
@@ -0,0 +1,9 @@
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: RemoteCollector
|
||||
metadata:
|
||||
name: modules
|
||||
spec:
|
||||
collectors:
|
||||
- cpu: {}
|
||||
- memory: {}
|
||||
# - kernelModules: {}
|
||||
@@ -3,397 +3,44 @@
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.2.4
|
||||
creationTimestamp: null
|
||||
name: analyzerjobs.troubleshoot.replicated.com
|
||||
spec:
|
||||
group: troubleshoot.replicated.com
|
||||
names:
|
||||
kind: AnalyzerJob
|
||||
listKind: AnalyzerJobList
|
||||
plural: analyzerjobs
|
||||
scope: ""
|
||||
singular: analyzerjob
|
||||
scope: Namespaced
|
||||
validation:
|
||||
openAPIV3Schema:
|
||||
description: AnalyzerJob is the Schema for the analyzerjobs API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: AnalyzerJobSpec defines the desired state of AnalyzerJob
|
||||
type: object
|
||||
status:
|
||||
description: AnalyzerJobStatus defines the observed state of AnalyzerJob
|
||||
type: object
|
||||
type: object
|
||||
version: v1beta1
|
||||
versions:
|
||||
- name: v1beta1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: AnalyzerJob is the Schema for the analyzerjobs API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: 'Annotations is an unstructured key value map stored
|
||||
with a resource that may be set by external tools to store and retrieve
|
||||
arbitrary metadata. They are not queryable and should be preserved
|
||||
when modifying objects. More info: http://kubernetes.io/docs/user-guide/annotations'
|
||||
type: object
|
||||
clusterName:
|
||||
description: The name of the cluster which the object belongs to.
|
||||
This is used to distinguish resources with same name and namespace
|
||||
in different clusters. This field is not set anywhere right now
|
||||
and apiserver is going to ignore it if set in create or update request.
|
||||
type: string
|
||||
creationTimestamp:
|
||||
description: "CreationTimestamp is a timestamp representing the server
|
||||
time when this object was created. It is not guaranteed to be set
|
||||
in happens-before order across separate operations. Clients may
|
||||
not set this value. It is represented in RFC3339 form and is in
|
||||
UTC. \n Populated by the system. Read-only. Null for lists. More
|
||||
info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata"
|
||||
format: date-time
|
||||
type: string
|
||||
deletionGracePeriodSeconds:
|
||||
description: Number of seconds allowed for this object to gracefully
|
||||
terminate before it will be removed from the system. Only set when
|
||||
deletionTimestamp is also set. May only be shortened. Read-only.
|
||||
format: int64
|
||||
type: integer
|
||||
deletionTimestamp:
|
||||
description: "DeletionTimestamp is RFC 3339 date and time at which
|
||||
this resource will be deleted. This field is set by the server when
|
||||
a graceful deletion is requested by the user, and is not directly
|
||||
settable by a client. The resource is expected to be deleted (no
|
||||
longer visible from resource lists, and not reachable by name) after
|
||||
the time in this field, once the finalizers list is empty. As long
|
||||
as the finalizers list contains items, deletion is blocked. Once
|
||||
the deletionTimestamp is set, this value may not be unset or be
|
||||
set further into the future, although it may be shortened or the
|
||||
resource may be deleted prior to this time. For example, a user
|
||||
may request that a pod is deleted in 30 seconds. The Kubelet will
|
||||
react by sending a graceful termination signal to the containers
|
||||
in the pod. After that 30 seconds, the Kubelet will send a hard
|
||||
termination signal (SIGKILL) to the container and after cleanup,
|
||||
remove the pod from the API. In the presence of network partitions,
|
||||
this object may still exist after this timestamp, until an administrator
|
||||
or automated process can determine the resource is fully terminated.
|
||||
If not set, graceful deletion of the object has not been requested.
|
||||
\n Populated by the system when a graceful deletion is requested.
|
||||
Read-only. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata"
|
||||
format: date-time
|
||||
type: string
|
||||
finalizers:
|
||||
description: Must be empty before the object is deleted from the registry.
|
||||
Each entry is an identifier for the responsible component that will
|
||||
remove the entry from the list. If the deletionTimestamp of the
|
||||
object is non-nil, entries in this list can only be removed.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
generateName:
|
||||
description: "GenerateName is an optional prefix, used by the server,
|
||||
to generate a unique name ONLY IF the Name field has not been provided.
|
||||
If this field is used, the name returned to the client will be different
|
||||
than the name passed. This value will also be combined with a unique
|
||||
suffix. The provided value has the same validation rules as the
|
||||
Name field, and may be truncated by the length of the suffix required
|
||||
to make the value unique on the server. \n If this field is specified
|
||||
and the generated name exists, the server will NOT return a 409
|
||||
- instead, it will either return 201 Created or 500 with Reason
|
||||
ServerTimeout indicating a unique name could not be found in the
|
||||
time allotted, and the client should retry (optionally after the
|
||||
time indicated in the Retry-After header). \n Applied only if Name
|
||||
is not specified. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#idempotency"
|
||||
type: string
|
||||
generation:
|
||||
description: A sequence number representing a specific generation
|
||||
of the desired state. Populated by the system. Read-only.
|
||||
format: int64
|
||||
type: integer
|
||||
initializers:
|
||||
description: "An initializer is a controller which enforces some system
|
||||
invariant at object creation time. This field is a list of initializers
|
||||
that have not yet acted on this object. If nil or empty, this object
|
||||
has been completely initialized. Otherwise, the object is considered
|
||||
uninitialized and is hidden (in list/watch and get calls) from clients
|
||||
that haven't explicitly asked to observe uninitialized objects.
|
||||
\n When an object is created, the system will populate this list
|
||||
with the current set of initializers. Only privileged users may
|
||||
set or modify this list. Once it is empty, it may not be modified
|
||||
further by any user. \n DEPRECATED - initializers are an alpha field
|
||||
and will be removed in v1.15."
|
||||
properties:
|
||||
pending:
|
||||
description: Pending is a list of initializers that must execute
|
||||
in order before this object is visible. When the last pending
|
||||
initializer is removed, and no failing result is set, the initializers
|
||||
struct will be set to nil and the object is considered as initialized
|
||||
and visible to all clients.
|
||||
items:
|
||||
properties:
|
||||
name:
|
||||
description: name of the process that is responsible for
|
||||
initializing this object.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: array
|
||||
result:
|
||||
description: If result is set with the Failure field, the object
|
||||
will be persisted to storage and then deleted, ensuring that
|
||||
other clients can observe the deletion.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this
|
||||
representation of an object. Servers should convert recognized
|
||||
schemas to the latest internal value, and may reject unrecognized
|
||||
values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources'
|
||||
type: string
|
||||
code:
|
||||
description: Suggested HTTP return code for this status, 0
|
||||
if not set.
|
||||
format: int32
|
||||
type: integer
|
||||
details:
|
||||
description: Extended data associated with the reason. Each
|
||||
reason may define its own extended details. This field is
|
||||
optional and the data returned is not guaranteed to conform
|
||||
to any schema except that defined by the reason type.
|
||||
properties:
|
||||
causes:
|
||||
description: The Causes array includes more details associated
|
||||
with the StatusReason failure. Not all StatusReasons
|
||||
may provide detailed causes.
|
||||
items:
|
||||
properties:
|
||||
field:
|
||||
description: "The field of the resource that has
|
||||
caused this error, as named by its JSON serialization.
|
||||
May include dot and postfix notation for nested
|
||||
attributes. Arrays are zero-indexed. Fields may
|
||||
appear more than once in an array of causes due
|
||||
to fields having multiple errors. Optional. \n
|
||||
Examples: \"name\" - the field \"name\" on the
|
||||
current resource \"items[0].name\" - the field
|
||||
\"name\" on the first array entry in \"items\""
|
||||
type: string
|
||||
message:
|
||||
description: A human-readable description of the
|
||||
cause of the error. This field may be presented
|
||||
as-is to a reader.
|
||||
type: string
|
||||
reason:
|
||||
description: A machine-readable description of the
|
||||
cause of the error. If this value is empty there
|
||||
is no information available.
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
group:
|
||||
description: The group attribute of the resource associated
|
||||
with the status StatusReason.
|
||||
type: string
|
||||
kind:
|
||||
description: 'The kind attribute of the resource associated
|
||||
with the status StatusReason. On some operations may
|
||||
differ from the requested resource Kind. More info:
|
||||
https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
name:
|
||||
description: The name attribute of the resource associated
|
||||
with the status StatusReason (when there is a single
|
||||
name which can be described).
|
||||
type: string
|
||||
retryAfterSeconds:
|
||||
description: If specified, the time in seconds before
|
||||
the operation should be retried. Some errors may indicate
|
||||
the client must take an alternate action - for those
|
||||
errors this field may indicate how long to wait before
|
||||
taking the alternate action.
|
||||
format: int32
|
||||
type: integer
|
||||
uid:
|
||||
description: 'UID of the resource. (when there is a single
|
||||
resource which can be described). More info: http://kubernetes.io/docs/user-guide/identifiers#uids'
|
||||
type: string
|
||||
type: object
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST
|
||||
resource this object represents. Servers may infer this
|
||||
from the endpoint the client submits requests to. Cannot
|
||||
be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
message:
|
||||
description: A human-readable description of the status of
|
||||
this operation.
|
||||
type: string
|
||||
metadata:
|
||||
description: 'Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds'
|
||||
properties:
|
||||
continue:
|
||||
description: continue may be set if the user set a limit
|
||||
on the number of items returned, and indicates that
|
||||
the server has more data available. The value is opaque
|
||||
and may be used to issue another request to the endpoint
|
||||
that served this list to retrieve the next set of available
|
||||
objects. Continuing a consistent list may not be possible
|
||||
if the server configuration has changed or more than
|
||||
a few minutes have passed. The resourceVersion field
|
||||
returned when using this continue value will be identical
|
||||
to the value in the first response, unless you have
|
||||
received this token from an error message.
|
||||
type: string
|
||||
resourceVersion:
|
||||
description: 'String that identifies the server''s internal
|
||||
version of this object that can be used by clients to
|
||||
determine when objects have changed. Value must be treated
|
||||
as opaque by clients and passed unmodified back to the
|
||||
server. Populated by the system. Read-only. More info:
|
||||
https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency'
|
||||
type: string
|
||||
selfLink:
|
||||
description: selfLink is a URL representing this object.
|
||||
Populated by the system. Read-only.
|
||||
type: string
|
||||
type: object
|
||||
reason:
|
||||
description: A machine-readable description of why this operation
|
||||
is in the "Failure" status. If this value is empty there
|
||||
is no information available. A Reason clarifies an HTTP
|
||||
status code but does not override it.
|
||||
type: string
|
||||
status:
|
||||
description: 'Status of the operation. One of: "Success" or
|
||||
"Failure". More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status'
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- pending
|
||||
type: object
|
||||
labels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: 'Map of string keys and values that can be used to organize
|
||||
and categorize (scope and select) objects. May match selectors of
|
||||
replication controllers and services. More info: http://kubernetes.io/docs/user-guide/labels'
|
||||
type: object
|
||||
managedFields:
|
||||
description: "ManagedFields maps workflow-id and version to the set
|
||||
of fields that are managed by that workflow. This is mostly for
|
||||
internal housekeeping, and users typically shouldn't need to set
|
||||
or understand this field. A workflow can be the user's name, a controller's
|
||||
name, or the name of a specific apply path like \"ci-cd\". The set
|
||||
of fields is always in the version that the workflow used when modifying
|
||||
the object. \n This field is alpha and can be changed or removed
|
||||
without notice."
|
||||
items:
|
||||
properties:
|
||||
apiVersion:
|
||||
description: APIVersion defines the version of this resource
|
||||
that this field set applies to. The format is "group/version"
|
||||
just like the top-level APIVersion field. It is necessary
|
||||
to track the version of a field set because it cannot be automatically
|
||||
converted.
|
||||
type: string
|
||||
fields:
|
||||
additionalProperties: true
|
||||
description: Fields identifies a set of fields.
|
||||
type: object
|
||||
manager:
|
||||
description: Manager is an identifier of the workflow managing
|
||||
these fields.
|
||||
type: string
|
||||
operation:
|
||||
description: Operation is the type of operation which lead to
|
||||
this ManagedFieldsEntry being created. The only valid values
|
||||
for this field are 'Apply' and 'Update'.
|
||||
type: string
|
||||
time:
|
||||
description: Time is timestamp of when these fields were set.
|
||||
It should always be empty if Operation is 'Apply'
|
||||
format: date-time
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
name:
|
||||
description: 'Name must be unique within a namespace. Is required
|
||||
when creating resources, although some resources may allow a client
|
||||
to request the generation of an appropriate name automatically.
|
||||
Name is primarily intended for creation idempotence and configuration
|
||||
definition. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names'
|
||||
type: string
|
||||
namespace:
|
||||
description: "Namespace defines the space within each name must be
|
||||
unique. An empty namespace is equivalent to the \"default\" namespace,
|
||||
but \"default\" is the canonical representation. Not all objects
|
||||
are required to be scoped to a namespace - the value of this field
|
||||
for those objects will be empty. \n Must be a DNS_LABEL. Cannot
|
||||
be updated. More info: http://kubernetes.io/docs/user-guide/namespaces"
|
||||
type: string
|
||||
ownerReferences:
|
||||
description: List of objects depended by this object. If ALL objects
|
||||
in the list have been deleted, this object will be garbage collected.
|
||||
If this object is managed by a controller, then an entry in this
|
||||
list will point to this controller, with the controller field set
|
||||
to true. There cannot be more than one managing controller.
|
||||
items:
|
||||
properties:
|
||||
apiVersion:
|
||||
description: API version of the referent.
|
||||
type: string
|
||||
blockOwnerDeletion:
|
||||
description: If true, AND if the owner has the "foregroundDeletion"
|
||||
finalizer, then the owner cannot be deleted from the key-value
|
||||
store until this reference is removed. Defaults to false.
|
||||
To set this field, a user needs "delete" permission of the
|
||||
owner, otherwise 422 (Unprocessable Entity) will be returned.
|
||||
type: boolean
|
||||
controller:
|
||||
description: If true, this reference points to the managing
|
||||
controller.
|
||||
type: boolean
|
||||
kind:
|
||||
description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
name:
|
||||
description: 'Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names'
|
||||
type: string
|
||||
uid:
|
||||
description: 'UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids'
|
||||
type: string
|
||||
required:
|
||||
- apiVersion
|
||||
- kind
|
||||
- name
|
||||
- uid
|
||||
type: object
|
||||
type: array
|
||||
resourceVersion:
|
||||
description: "An opaque value that represents the internal version
|
||||
of this object that can be used by clients to determine when objects
|
||||
have changed. May be used for optimistic concurrency, change detection,
|
||||
and the watch operation on a resource or set of resources. Clients
|
||||
must treat these values as opaque and passed unmodified back to
|
||||
the server. They may only be valid for a particular resource or
|
||||
set of resources. \n Populated by the system. Read-only. Value must
|
||||
be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency"
|
||||
type: string
|
||||
selfLink:
|
||||
description: SelfLink is a URL representing this object. Populated
|
||||
by the system. Read-only.
|
||||
type: string
|
||||
uid:
|
||||
description: "UID is the unique in time and space value for this object.
|
||||
It is typically generated by the server on successful creation of
|
||||
a resource and is not allowed to change on PUT operations. \n Populated
|
||||
by the system. Read-only. More info: http://kubernetes.io/docs/user-guide/identifiers#uids"
|
||||
type: string
|
||||
type: object
|
||||
spec:
|
||||
type: object
|
||||
status:
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
status:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,448 +3,95 @@
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.2.4
|
||||
creationTimestamp: null
|
||||
name: collectorjobs.troubleshoot.replicated.com
|
||||
spec:
|
||||
group: troubleshoot.replicated.com
|
||||
names:
|
||||
kind: CollectorJob
|
||||
listKind: CollectorJobList
|
||||
plural: collectorjobs
|
||||
scope: ""
|
||||
singular: collectorjob
|
||||
scope: Namespaced
|
||||
subresources:
|
||||
status: {}
|
||||
validation:
|
||||
openAPIV3Schema:
|
||||
description: CollectorJob is the Schema for the collectorjobs API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: CollectorJobSpec defines the desired state of CollectorJob
|
||||
properties:
|
||||
collector:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
image:
|
||||
type: string
|
||||
imagePullPolicy:
|
||||
type: string
|
||||
redact:
|
||||
type: boolean
|
||||
required:
|
||||
- collector
|
||||
type: object
|
||||
status:
|
||||
description: CollectorJobStatus defines the observed state of CollectorJob
|
||||
properties:
|
||||
failed:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
isServerReady:
|
||||
type: boolean
|
||||
running:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
serverPodName:
|
||||
type: string
|
||||
serverPodNamespace:
|
||||
type: string
|
||||
serverPodPort:
|
||||
type: integer
|
||||
successful:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- failed
|
||||
- isServerReady
|
||||
- running
|
||||
- serverPodName
|
||||
- serverPodNamespace
|
||||
- serverPodPort
|
||||
- successful
|
||||
type: object
|
||||
type: object
|
||||
version: v1beta1
|
||||
versions:
|
||||
- name: v1beta1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: CollectorJob is the Schema for the collectorjobs API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: 'Annotations is an unstructured key value map stored
|
||||
with a resource that may be set by external tools to store and retrieve
|
||||
arbitrary metadata. They are not queryable and should be preserved
|
||||
when modifying objects. More info: http://kubernetes.io/docs/user-guide/annotations'
|
||||
type: object
|
||||
clusterName:
|
||||
description: The name of the cluster which the object belongs to.
|
||||
This is used to distinguish resources with same name and namespace
|
||||
in different clusters. This field is not set anywhere right now
|
||||
and apiserver is going to ignore it if set in create or update request.
|
||||
type: string
|
||||
creationTimestamp:
|
||||
description: "CreationTimestamp is a timestamp representing the server
|
||||
time when this object was created. It is not guaranteed to be set
|
||||
in happens-before order across separate operations. Clients may
|
||||
not set this value. It is represented in RFC3339 form and is in
|
||||
UTC. \n Populated by the system. Read-only. Null for lists. More
|
||||
info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata"
|
||||
format: date-time
|
||||
type: string
|
||||
deletionGracePeriodSeconds:
|
||||
description: Number of seconds allowed for this object to gracefully
|
||||
terminate before it will be removed from the system. Only set when
|
||||
deletionTimestamp is also set. May only be shortened. Read-only.
|
||||
format: int64
|
||||
type: integer
|
||||
deletionTimestamp:
|
||||
description: "DeletionTimestamp is RFC 3339 date and time at which
|
||||
this resource will be deleted. This field is set by the server when
|
||||
a graceful deletion is requested by the user, and is not directly
|
||||
settable by a client. The resource is expected to be deleted (no
|
||||
longer visible from resource lists, and not reachable by name) after
|
||||
the time in this field, once the finalizers list is empty. As long
|
||||
as the finalizers list contains items, deletion is blocked. Once
|
||||
the deletionTimestamp is set, this value may not be unset or be
|
||||
set further into the future, although it may be shortened or the
|
||||
resource may be deleted prior to this time. For example, a user
|
||||
may request that a pod is deleted in 30 seconds. The Kubelet will
|
||||
react by sending a graceful termination signal to the containers
|
||||
in the pod. After that 30 seconds, the Kubelet will send a hard
|
||||
termination signal (SIGKILL) to the container and after cleanup,
|
||||
remove the pod from the API. In the presence of network partitions,
|
||||
this object may still exist after this timestamp, until an administrator
|
||||
or automated process can determine the resource is fully terminated.
|
||||
If not set, graceful deletion of the object has not been requested.
|
||||
\n Populated by the system when a graceful deletion is requested.
|
||||
Read-only. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata"
|
||||
format: date-time
|
||||
type: string
|
||||
finalizers:
|
||||
description: Must be empty before the object is deleted from the registry.
|
||||
Each entry is an identifier for the responsible component that will
|
||||
remove the entry from the list. If the deletionTimestamp of the
|
||||
object is non-nil, entries in this list can only be removed.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
generateName:
|
||||
description: "GenerateName is an optional prefix, used by the server,
|
||||
to generate a unique name ONLY IF the Name field has not been provided.
|
||||
If this field is used, the name returned to the client will be different
|
||||
than the name passed. This value will also be combined with a unique
|
||||
suffix. The provided value has the same validation rules as the
|
||||
Name field, and may be truncated by the length of the suffix required
|
||||
to make the value unique on the server. \n If this field is specified
|
||||
and the generated name exists, the server will NOT return a 409
|
||||
- instead, it will either return 201 Created or 500 with Reason
|
||||
ServerTimeout indicating a unique name could not be found in the
|
||||
time allotted, and the client should retry (optionally after the
|
||||
time indicated in the Retry-After header). \n Applied only if Name
|
||||
is not specified. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#idempotency"
|
||||
type: string
|
||||
generation:
|
||||
description: A sequence number representing a specific generation
|
||||
of the desired state. Populated by the system. Read-only.
|
||||
format: int64
|
||||
type: integer
|
||||
initializers:
|
||||
description: "An initializer is a controller which enforces some system
|
||||
invariant at object creation time. This field is a list of initializers
|
||||
that have not yet acted on this object. If nil or empty, this object
|
||||
has been completely initialized. Otherwise, the object is considered
|
||||
uninitialized and is hidden (in list/watch and get calls) from clients
|
||||
that haven't explicitly asked to observe uninitialized objects.
|
||||
\n When an object is created, the system will populate this list
|
||||
with the current set of initializers. Only privileged users may
|
||||
set or modify this list. Once it is empty, it may not be modified
|
||||
further by any user. \n DEPRECATED - initializers are an alpha field
|
||||
and will be removed in v1.15."
|
||||
properties:
|
||||
pending:
|
||||
description: Pending is a list of initializers that must execute
|
||||
in order before this object is visible. When the last pending
|
||||
initializer is removed, and no failing result is set, the initializers
|
||||
struct will be set to nil and the object is considered as initialized
|
||||
and visible to all clients.
|
||||
items:
|
||||
properties:
|
||||
name:
|
||||
description: name of the process that is responsible for
|
||||
initializing this object.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: array
|
||||
result:
|
||||
description: If result is set with the Failure field, the object
|
||||
will be persisted to storage and then deleted, ensuring that
|
||||
other clients can observe the deletion.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this
|
||||
representation of an object. Servers should convert recognized
|
||||
schemas to the latest internal value, and may reject unrecognized
|
||||
values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources'
|
||||
type: string
|
||||
code:
|
||||
description: Suggested HTTP return code for this status, 0
|
||||
if not set.
|
||||
format: int32
|
||||
type: integer
|
||||
details:
|
||||
description: Extended data associated with the reason. Each
|
||||
reason may define its own extended details. This field is
|
||||
optional and the data returned is not guaranteed to conform
|
||||
to any schema except that defined by the reason type.
|
||||
properties:
|
||||
causes:
|
||||
description: The Causes array includes more details associated
|
||||
with the StatusReason failure. Not all StatusReasons
|
||||
may provide detailed causes.
|
||||
items:
|
||||
properties:
|
||||
field:
|
||||
description: "The field of the resource that has
|
||||
caused this error, as named by its JSON serialization.
|
||||
May include dot and postfix notation for nested
|
||||
attributes. Arrays are zero-indexed. Fields may
|
||||
appear more than once in an array of causes due
|
||||
to fields having multiple errors. Optional. \n
|
||||
Examples: \"name\" - the field \"name\" on the
|
||||
current resource \"items[0].name\" - the field
|
||||
\"name\" on the first array entry in \"items\""
|
||||
type: string
|
||||
message:
|
||||
description: A human-readable description of the
|
||||
cause of the error. This field may be presented
|
||||
as-is to a reader.
|
||||
type: string
|
||||
reason:
|
||||
description: A machine-readable description of the
|
||||
cause of the error. If this value is empty there
|
||||
is no information available.
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
group:
|
||||
description: The group attribute of the resource associated
|
||||
with the status StatusReason.
|
||||
type: string
|
||||
kind:
|
||||
description: 'The kind attribute of the resource associated
|
||||
with the status StatusReason. On some operations may
|
||||
differ from the requested resource Kind. More info:
|
||||
https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
name:
|
||||
description: The name attribute of the resource associated
|
||||
with the status StatusReason (when there is a single
|
||||
name which can be described).
|
||||
type: string
|
||||
retryAfterSeconds:
|
||||
description: If specified, the time in seconds before
|
||||
the operation should be retried. Some errors may indicate
|
||||
the client must take an alternate action - for those
|
||||
errors this field may indicate how long to wait before
|
||||
taking the alternate action.
|
||||
format: int32
|
||||
type: integer
|
||||
uid:
|
||||
description: 'UID of the resource. (when there is a single
|
||||
resource which can be described). More info: http://kubernetes.io/docs/user-guide/identifiers#uids'
|
||||
type: string
|
||||
type: object
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST
|
||||
resource this object represents. Servers may infer this
|
||||
from the endpoint the client submits requests to. Cannot
|
||||
be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
message:
|
||||
description: A human-readable description of the status of
|
||||
this operation.
|
||||
type: string
|
||||
metadata:
|
||||
description: 'Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds'
|
||||
properties:
|
||||
continue:
|
||||
description: continue may be set if the user set a limit
|
||||
on the number of items returned, and indicates that
|
||||
the server has more data available. The value is opaque
|
||||
and may be used to issue another request to the endpoint
|
||||
that served this list to retrieve the next set of available
|
||||
objects. Continuing a consistent list may not be possible
|
||||
if the server configuration has changed or more than
|
||||
a few minutes have passed. The resourceVersion field
|
||||
returned when using this continue value will be identical
|
||||
to the value in the first response, unless you have
|
||||
received this token from an error message.
|
||||
type: string
|
||||
resourceVersion:
|
||||
description: 'String that identifies the server''s internal
|
||||
version of this object that can be used by clients to
|
||||
determine when objects have changed. Value must be treated
|
||||
as opaque by clients and passed unmodified back to the
|
||||
server. Populated by the system. Read-only. More info:
|
||||
https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency'
|
||||
type: string
|
||||
selfLink:
|
||||
description: selfLink is a URL representing this object.
|
||||
Populated by the system. Read-only.
|
||||
type: string
|
||||
type: object
|
||||
reason:
|
||||
description: A machine-readable description of why this operation
|
||||
is in the "Failure" status. If this value is empty there
|
||||
is no information available. A Reason clarifies an HTTP
|
||||
status code but does not override it.
|
||||
type: string
|
||||
status:
|
||||
description: 'Status of the operation. One of: "Success" or
|
||||
"Failure". More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status'
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- pending
|
||||
type: object
|
||||
labels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: 'Map of string keys and values that can be used to organize
|
||||
and categorize (scope and select) objects. May match selectors of
|
||||
replication controllers and services. More info: http://kubernetes.io/docs/user-guide/labels'
|
||||
type: object
|
||||
managedFields:
|
||||
description: "ManagedFields maps workflow-id and version to the set
|
||||
of fields that are managed by that workflow. This is mostly for
|
||||
internal housekeeping, and users typically shouldn't need to set
|
||||
or understand this field. A workflow can be the user's name, a controller's
|
||||
name, or the name of a specific apply path like \"ci-cd\". The set
|
||||
of fields is always in the version that the workflow used when modifying
|
||||
the object. \n This field is alpha and can be changed or removed
|
||||
without notice."
|
||||
items:
|
||||
properties:
|
||||
apiVersion:
|
||||
description: APIVersion defines the version of this resource
|
||||
that this field set applies to. The format is "group/version"
|
||||
just like the top-level APIVersion field. It is necessary
|
||||
to track the version of a field set because it cannot be automatically
|
||||
converted.
|
||||
type: string
|
||||
fields:
|
||||
additionalProperties: true
|
||||
description: Fields identifies a set of fields.
|
||||
type: object
|
||||
manager:
|
||||
description: Manager is an identifier of the workflow managing
|
||||
these fields.
|
||||
type: string
|
||||
operation:
|
||||
description: Operation is the type of operation which lead to
|
||||
this ManagedFieldsEntry being created. The only valid values
|
||||
for this field are 'Apply' and 'Update'.
|
||||
type: string
|
||||
time:
|
||||
description: Time is timestamp of when these fields were set.
|
||||
It should always be empty if Operation is 'Apply'
|
||||
format: date-time
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
name:
|
||||
description: 'Name must be unique within a namespace. Is required
|
||||
when creating resources, although some resources may allow a client
|
||||
to request the generation of an appropriate name automatically.
|
||||
Name is primarily intended for creation idempotence and configuration
|
||||
definition. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names'
|
||||
type: string
|
||||
namespace:
|
||||
description: "Namespace defines the space within each name must be
|
||||
unique. An empty namespace is equivalent to the \"default\" namespace,
|
||||
but \"default\" is the canonical representation. Not all objects
|
||||
are required to be scoped to a namespace - the value of this field
|
||||
for those objects will be empty. \n Must be a DNS_LABEL. Cannot
|
||||
be updated. More info: http://kubernetes.io/docs/user-guide/namespaces"
|
||||
type: string
|
||||
ownerReferences:
|
||||
description: List of objects depended by this object. If ALL objects
|
||||
in the list have been deleted, this object will be garbage collected.
|
||||
If this object is managed by a controller, then an entry in this
|
||||
list will point to this controller, with the controller field set
|
||||
to true. There cannot be more than one managing controller.
|
||||
items:
|
||||
properties:
|
||||
apiVersion:
|
||||
description: API version of the referent.
|
||||
type: string
|
||||
blockOwnerDeletion:
|
||||
description: If true, AND if the owner has the "foregroundDeletion"
|
||||
finalizer, then the owner cannot be deleted from the key-value
|
||||
store until this reference is removed. Defaults to false.
|
||||
To set this field, a user needs "delete" permission of the
|
||||
owner, otherwise 422 (Unprocessable Entity) will be returned.
|
||||
type: boolean
|
||||
controller:
|
||||
description: If true, this reference points to the managing
|
||||
controller.
|
||||
type: boolean
|
||||
kind:
|
||||
description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
name:
|
||||
description: 'Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names'
|
||||
type: string
|
||||
uid:
|
||||
description: 'UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids'
|
||||
type: string
|
||||
required:
|
||||
- apiVersion
|
||||
- kind
|
||||
- name
|
||||
- uid
|
||||
type: object
|
||||
type: array
|
||||
resourceVersion:
|
||||
description: "An opaque value that represents the internal version
|
||||
of this object that can be used by clients to determine when objects
|
||||
have changed. May be used for optimistic concurrency, change detection,
|
||||
and the watch operation on a resource or set of resources. Clients
|
||||
must treat these values as opaque and passed unmodified back to
|
||||
the server. They may only be valid for a particular resource or
|
||||
set of resources. \n Populated by the system. Read-only. Value must
|
||||
be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency"
|
||||
type: string
|
||||
selfLink:
|
||||
description: SelfLink is a URL representing this object. Populated
|
||||
by the system. Read-only.
|
||||
type: string
|
||||
uid:
|
||||
description: "UID is the unique in time and space value for this object.
|
||||
It is typically generated by the server on successful creation of
|
||||
a resource and is not allowed to change on PUT operations. \n Populated
|
||||
by the system. Read-only. More info: http://kubernetes.io/docs/user-guide/identifiers#uids"
|
||||
type: string
|
||||
type: object
|
||||
spec:
|
||||
properties:
|
||||
collector:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
image:
|
||||
type: string
|
||||
imagePullPolicy:
|
||||
type: string
|
||||
redact:
|
||||
type: boolean
|
||||
required:
|
||||
- collector
|
||||
type: object
|
||||
status:
|
||||
properties:
|
||||
failed:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
isServerReady:
|
||||
type: boolean
|
||||
running:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
serverPodName:
|
||||
type: string
|
||||
serverPodNamespace:
|
||||
type: string
|
||||
serverPodPort:
|
||||
type: integer
|
||||
successful:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- isServerReady
|
||||
- serverPodName
|
||||
- serverPodNamespace
|
||||
- serverPodPort
|
||||
- running
|
||||
- successful
|
||||
- failed
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.7.0
|
||||
creationTimestamp: null
|
||||
name: collectors.troubleshoot.replicated.com
|
||||
spec:
|
||||
group: troubleshoot.replicated.com
|
||||
names:
|
||||
kind: Collector
|
||||
listKind: CollectorList
|
||||
plural: collectors
|
||||
scope: ""
|
||||
singular: collector
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1beta1
|
||||
schema:
|
||||
@@ -20,376 +24,17 @@ spec:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources'
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds'
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: 'Annotations is an unstructured key value map stored
|
||||
with a resource that may be set by external tools to store and retrieve
|
||||
arbitrary metadata. They are not queryable and should be preserved
|
||||
when modifying objects. More info: http://kubernetes.io/docs/user-guide/annotations'
|
||||
type: object
|
||||
clusterName:
|
||||
description: The name of the cluster which the object belongs to.
|
||||
This is used to distinguish resources with same name and namespace
|
||||
in different clusters. This field is not set anywhere right now
|
||||
and apiserver is going to ignore it if set in create or update request.
|
||||
type: string
|
||||
creationTimestamp:
|
||||
description: "CreationTimestamp is a timestamp representing the server
|
||||
time when this object was created. It is not guaranteed to be set
|
||||
in happens-before order across separate operations. Clients may
|
||||
not set this value. It is represented in RFC3339 form and is in
|
||||
UTC. \n Populated by the system. Read-only. Null for lists. More
|
||||
info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata"
|
||||
format: date-time
|
||||
type: string
|
||||
deletionGracePeriodSeconds:
|
||||
description: Number of seconds allowed for this object to gracefully
|
||||
terminate before it will be removed from the system. Only set when
|
||||
deletionTimestamp is also set. May only be shortened. Read-only.
|
||||
format: int64
|
||||
type: integer
|
||||
deletionTimestamp:
|
||||
description: "DeletionTimestamp is RFC 3339 date and time at which
|
||||
this resource will be deleted. This field is set by the server when
|
||||
a graceful deletion is requested by the user, and is not directly
|
||||
settable by a client. The resource is expected to be deleted (no
|
||||
longer visible from resource lists, and not reachable by name) after
|
||||
the time in this field, once the finalizers list is empty. As long
|
||||
as the finalizers list contains items, deletion is blocked. Once
|
||||
the deletionTimestamp is set, this value may not be unset or be
|
||||
set further into the future, although it may be shortened or the
|
||||
resource may be deleted prior to this time. For example, a user
|
||||
may request that a pod is deleted in 30 seconds. The Kubelet will
|
||||
react by sending a graceful termination signal to the containers
|
||||
in the pod. After that 30 seconds, the Kubelet will send a hard
|
||||
termination signal (SIGKILL) to the container and after cleanup,
|
||||
remove the pod from the API. In the presence of network partitions,
|
||||
this object may still exist after this timestamp, until an administrator
|
||||
or automated process can determine the resource is fully terminated.
|
||||
If not set, graceful deletion of the object has not been requested.
|
||||
\n Populated by the system when a graceful deletion is requested.
|
||||
Read-only. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata"
|
||||
format: date-time
|
||||
type: string
|
||||
finalizers:
|
||||
description: Must be empty before the object is deleted from the registry.
|
||||
Each entry is an identifier for the responsible component that will
|
||||
remove the entry from the list. If the deletionTimestamp of the
|
||||
object is non-nil, entries in this list can only be removed.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
generateName:
|
||||
description: "GenerateName is an optional prefix, used by the server,
|
||||
to generate a unique name ONLY IF the Name field has not been provided.
|
||||
If this field is used, the name returned to the client will be different
|
||||
than the name passed. This value will also be combined with a unique
|
||||
suffix. The provided value has the same validation rules as the
|
||||
Name field, and may be truncated by the length of the suffix required
|
||||
to make the value unique on the server. \n If this field is specified
|
||||
and the generated name exists, the server will NOT return a 409
|
||||
- instead, it will either return 201 Created or 500 with Reason
|
||||
ServerTimeout indicating a unique name could not be found in the
|
||||
time allotted, and the client should retry (optionally after the
|
||||
time indicated in the Retry-After header). \n Applied only if Name
|
||||
is not specified. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#idempotency"
|
||||
type: string
|
||||
generation:
|
||||
description: A sequence number representing a specific generation
|
||||
of the desired state. Populated by the system. Read-only.
|
||||
format: int64
|
||||
type: integer
|
||||
initializers:
|
||||
description: "An initializer is a controller which enforces some system
|
||||
invariant at object creation time. This field is a list of initializers
|
||||
that have not yet acted on this object. If nil or empty, this object
|
||||
has been completely initialized. Otherwise, the object is considered
|
||||
uninitialized and is hidden (in list/watch and get calls) from clients
|
||||
that haven't explicitly asked to observe uninitialized objects.
|
||||
\n When an object is created, the system will populate this list
|
||||
with the current set of initializers. Only privileged users may
|
||||
set or modify this list. Once it is empty, it may not be modified
|
||||
further by any user. \n DEPRECATED - initializers are an alpha field
|
||||
and will be removed in v1.15."
|
||||
properties:
|
||||
pending:
|
||||
description: Pending is a list of initializers that must execute
|
||||
in order before this object is visible. When the last pending
|
||||
initializer is removed, and no failing result is set, the initializers
|
||||
struct will be set to nil and the object is considered as initialized
|
||||
and visible to all clients.
|
||||
items:
|
||||
properties:
|
||||
name:
|
||||
description: name of the process that is responsible for
|
||||
initializing this object.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: array
|
||||
result:
|
||||
description: If result is set with the Failure field, the object
|
||||
will be persisted to storage and then deleted, ensuring that
|
||||
other clients can observe the deletion.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this
|
||||
representation of an object. Servers should convert recognized
|
||||
schemas to the latest internal value, and may reject unrecognized
|
||||
values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources'
|
||||
type: string
|
||||
code:
|
||||
description: Suggested HTTP return code for this status, 0
|
||||
if not set.
|
||||
format: int32
|
||||
type: integer
|
||||
details:
|
||||
description: Extended data associated with the reason. Each
|
||||
reason may define its own extended details. This field is
|
||||
optional and the data returned is not guaranteed to conform
|
||||
to any schema except that defined by the reason type.
|
||||
properties:
|
||||
causes:
|
||||
description: The Causes array includes more details associated
|
||||
with the StatusReason failure. Not all StatusReasons
|
||||
may provide detailed causes.
|
||||
items:
|
||||
properties:
|
||||
field:
|
||||
description: "The field of the resource that has
|
||||
caused this error, as named by its JSON serialization.
|
||||
May include dot and postfix notation for nested
|
||||
attributes. Arrays are zero-indexed. Fields may
|
||||
appear more than once in an array of causes due
|
||||
to fields having multiple errors. Optional. \n
|
||||
Examples: \"name\" - the field \"name\" on the
|
||||
current resource \"items[0].name\" - the field
|
||||
\"name\" on the first array entry in \"items\""
|
||||
type: string
|
||||
message:
|
||||
description: A human-readable description of the
|
||||
cause of the error. This field may be presented
|
||||
as-is to a reader.
|
||||
type: string
|
||||
reason:
|
||||
description: A machine-readable description of the
|
||||
cause of the error. If this value is empty there
|
||||
is no information available.
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
group:
|
||||
description: The group attribute of the resource associated
|
||||
with the status StatusReason.
|
||||
type: string
|
||||
kind:
|
||||
description: 'The kind attribute of the resource associated
|
||||
with the status StatusReason. On some operations may
|
||||
differ from the requested resource Kind. More info:
|
||||
https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
name:
|
||||
description: The name attribute of the resource associated
|
||||
with the status StatusReason (when there is a single
|
||||
name which can be described).
|
||||
type: string
|
||||
retryAfterSeconds:
|
||||
description: If specified, the time in seconds before
|
||||
the operation should be retried. Some errors may indicate
|
||||
the client must take an alternate action - for those
|
||||
errors this field may indicate how long to wait before
|
||||
taking the alternate action.
|
||||
format: int32
|
||||
type: integer
|
||||
uid:
|
||||
description: 'UID of the resource. (when there is a single
|
||||
resource which can be described). More info: http://kubernetes.io/docs/user-guide/identifiers#uids'
|
||||
type: string
|
||||
type: object
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST
|
||||
resource this object represents. Servers may infer this
|
||||
from the endpoint the client submits requests to. Cannot
|
||||
be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
message:
|
||||
description: A human-readable description of the status of
|
||||
this operation.
|
||||
type: string
|
||||
metadata:
|
||||
description: 'Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds'
|
||||
properties:
|
||||
continue:
|
||||
description: continue may be set if the user set a limit
|
||||
on the number of items returned, and indicates that
|
||||
the server has more data available. The value is opaque
|
||||
and may be used to issue another request to the endpoint
|
||||
that served this list to retrieve the next set of available
|
||||
objects. Continuing a consistent list may not be possible
|
||||
if the server configuration has changed or more than
|
||||
a few minutes have passed. The resourceVersion field
|
||||
returned when using this continue value will be identical
|
||||
to the value in the first response, unless you have
|
||||
received this token from an error message.
|
||||
type: string
|
||||
resourceVersion:
|
||||
description: 'String that identifies the server''s internal
|
||||
version of this object that can be used by clients to
|
||||
determine when objects have changed. Value must be treated
|
||||
as opaque by clients and passed unmodified back to the
|
||||
server. Populated by the system. Read-only. More info:
|
||||
https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency'
|
||||
type: string
|
||||
selfLink:
|
||||
description: selfLink is a URL representing this object.
|
||||
Populated by the system. Read-only.
|
||||
type: string
|
||||
type: object
|
||||
reason:
|
||||
description: A machine-readable description of why this operation
|
||||
is in the "Failure" status. If this value is empty there
|
||||
is no information available. A Reason clarifies an HTTP
|
||||
status code but does not override it.
|
||||
type: string
|
||||
status:
|
||||
description: 'Status of the operation. One of: "Success" or
|
||||
"Failure". More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status'
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- pending
|
||||
type: object
|
||||
labels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: 'Map of string keys and values that can be used to organize
|
||||
and categorize (scope and select) objects. May match selectors of
|
||||
replication controllers and services. More info: http://kubernetes.io/docs/user-guide/labels'
|
||||
type: object
|
||||
managedFields:
|
||||
description: "ManagedFields maps workflow-id and version to the set
|
||||
of fields that are managed by that workflow. This is mostly for
|
||||
internal housekeeping, and users typically shouldn't need to set
|
||||
or understand this field. A workflow can be the user's name, a controller's
|
||||
name, or the name of a specific apply path like \"ci-cd\". The set
|
||||
of fields is always in the version that the workflow used when modifying
|
||||
the object. \n This field is alpha and can be changed or removed
|
||||
without notice."
|
||||
items:
|
||||
properties:
|
||||
apiVersion:
|
||||
description: APIVersion defines the version of this resource
|
||||
that this field set applies to. The format is "group/version"
|
||||
just like the top-level APIVersion field. It is necessary
|
||||
to track the version of a field set because it cannot be automatically
|
||||
converted.
|
||||
type: string
|
||||
fields:
|
||||
additionalProperties: true
|
||||
description: Fields identifies a set of fields.
|
||||
type: object
|
||||
manager:
|
||||
description: Manager is an identifier of the workflow managing
|
||||
these fields.
|
||||
type: string
|
||||
operation:
|
||||
description: Operation is the type of operation which lead to
|
||||
this ManagedFieldsEntry being created. The only valid values
|
||||
for this field are 'Apply' and 'Update'.
|
||||
type: string
|
||||
time:
|
||||
description: Time is timestamp of when these fields were set.
|
||||
It should always be empty if Operation is 'Apply'
|
||||
format: date-time
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
name:
|
||||
description: 'Name must be unique within a namespace. Is required
|
||||
when creating resources, although some resources may allow a client
|
||||
to request the generation of an appropriate name automatically.
|
||||
Name is primarily intended for creation idempotence and configuration
|
||||
definition. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names'
|
||||
type: string
|
||||
namespace:
|
||||
description: "Namespace defines the space within each name must be
|
||||
unique. An empty namespace is equivalent to the \"default\" namespace,
|
||||
but \"default\" is the canonical representation. Not all objects
|
||||
are required to be scoped to a namespace - the value of this field
|
||||
for those objects will be empty. \n Must be a DNS_LABEL. Cannot
|
||||
be updated. More info: http://kubernetes.io/docs/user-guide/namespaces"
|
||||
type: string
|
||||
ownerReferences:
|
||||
description: List of objects depended by this object. If ALL objects
|
||||
in the list have been deleted, this object will be garbage collected.
|
||||
If this object is managed by a controller, then an entry in this
|
||||
list will point to this controller, with the controller field set
|
||||
to true. There cannot be more than one managing controller.
|
||||
items:
|
||||
properties:
|
||||
apiVersion:
|
||||
description: API version of the referent.
|
||||
type: string
|
||||
blockOwnerDeletion:
|
||||
description: If true, AND if the owner has the "foregroundDeletion"
|
||||
finalizer, then the owner cannot be deleted from the key-value
|
||||
store until this reference is removed. Defaults to false.
|
||||
To set this field, a user needs "delete" permission of the
|
||||
owner, otherwise 422 (Unprocessable Entity) will be returned.
|
||||
type: boolean
|
||||
controller:
|
||||
description: If true, this reference points to the managing
|
||||
controller.
|
||||
type: boolean
|
||||
kind:
|
||||
description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
name:
|
||||
description: 'Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names'
|
||||
type: string
|
||||
uid:
|
||||
description: 'UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids'
|
||||
type: string
|
||||
required:
|
||||
- apiVersion
|
||||
- kind
|
||||
- name
|
||||
- uid
|
||||
type: object
|
||||
type: array
|
||||
resourceVersion:
|
||||
description: "An opaque value that represents the internal version
|
||||
of this object that can be used by clients to determine when objects
|
||||
have changed. May be used for optimistic concurrency, change detection,
|
||||
and the watch operation on a resource or set of resources. Clients
|
||||
must treat these values as opaque and passed unmodified back to
|
||||
the server. They may only be valid for a particular resource or
|
||||
set of resources. \n Populated by the system. Read-only. Value must
|
||||
be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency"
|
||||
type: string
|
||||
selfLink:
|
||||
description: SelfLink is a URL representing this object. Populated
|
||||
by the system. Read-only.
|
||||
type: string
|
||||
uid:
|
||||
description: "UID is the unique in time and space value for this object.
|
||||
It is typically generated by the server on successful creation of
|
||||
a resource and is not allowed to change on PUT operations. \n Populated
|
||||
by the system. Read-only. More info: http://kubernetes.io/docs/user-guide/identifiers#uids"
|
||||
type: string
|
||||
type: object
|
||||
spec:
|
||||
description: CollectorSpec defines the desired state of Collector
|
||||
properties:
|
||||
afterCollection:
|
||||
items:
|
||||
@@ -398,21 +43,27 @@ spec:
|
||||
properties:
|
||||
method:
|
||||
type: string
|
||||
redactUri:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
required:
|
||||
- uri
|
||||
- method
|
||||
- redactUri
|
||||
- uri
|
||||
type: object
|
||||
uploadResultsTo:
|
||||
properties:
|
||||
method:
|
||||
type: string
|
||||
redactUri:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
required:
|
||||
- uri
|
||||
- method
|
||||
- redactUri
|
||||
- uri
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
@@ -420,8 +71,18 @@ spec:
|
||||
items:
|
||||
properties:
|
||||
clusterInfo:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
type: object
|
||||
clusterResources:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
type: object
|
||||
copy:
|
||||
properties:
|
||||
@@ -431,6 +92,10 @@ spec:
|
||||
type: string
|
||||
containerPath:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
selector:
|
||||
@@ -438,9 +103,22 @@ spec:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- selector
|
||||
- namespace
|
||||
- containerPath
|
||||
- namespace
|
||||
- selector
|
||||
type: object
|
||||
data:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
data:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
name:
|
||||
type: string
|
||||
required:
|
||||
- data
|
||||
type: object
|
||||
exec:
|
||||
properties:
|
||||
@@ -456,6 +134,10 @@ spec:
|
||||
type: array
|
||||
containerName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
selector:
|
||||
@@ -465,13 +147,15 @@ spec:
|
||||
timeout:
|
||||
type: string
|
||||
required:
|
||||
- selector
|
||||
- namespace
|
||||
- selector
|
||||
type: object
|
||||
http:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
get:
|
||||
properties:
|
||||
headers:
|
||||
@@ -485,6 +169,8 @@ spec:
|
||||
required:
|
||||
- url
|
||||
type: object
|
||||
name:
|
||||
type: string
|
||||
post:
|
||||
properties:
|
||||
body:
|
||||
@@ -520,6 +206,12 @@ spec:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
containerNames:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
exclude:
|
||||
type: BoolString
|
||||
limits:
|
||||
properties:
|
||||
maxAge:
|
||||
@@ -528,6 +220,8 @@ spec:
|
||||
format: int64
|
||||
type: integer
|
||||
type: object
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
selector:
|
||||
@@ -537,6 +231,39 @@ spec:
|
||||
required:
|
||||
- selector
|
||||
type: object
|
||||
mysql:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
uri:
|
||||
type: string
|
||||
required:
|
||||
- uri
|
||||
type: object
|
||||
postgres:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
uri:
|
||||
type: string
|
||||
required:
|
||||
- uri
|
||||
type: object
|
||||
redis:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
uri:
|
||||
type: string
|
||||
required:
|
||||
- uri
|
||||
type: object
|
||||
run:
|
||||
properties:
|
||||
args:
|
||||
@@ -549,22 +276,39 @@ spec:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
exclude:
|
||||
type: BoolString
|
||||
image:
|
||||
type: string
|
||||
imagePullPolicy:
|
||||
type: string
|
||||
imagePullSecret:
|
||||
properties:
|
||||
data:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
name:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
type: object
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
timeout:
|
||||
type: string
|
||||
required:
|
||||
- namespace
|
||||
- image
|
||||
- namespace
|
||||
type: object
|
||||
secret:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
includeValue:
|
||||
type: boolean
|
||||
key:
|
||||
@@ -580,6 +324,7 @@ spec:
|
||||
type: array
|
||||
type: object
|
||||
status:
|
||||
description: CollectorStatus defines the observed state of Collector
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
|
||||
@@ -3,468 +3,115 @@
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.2.4
|
||||
creationTimestamp: null
|
||||
name: preflightjobs.troubleshoot.replicated.com
|
||||
spec:
|
||||
group: troubleshoot.replicated.com
|
||||
names:
|
||||
kind: PreflightJob
|
||||
listKind: PreflightJobList
|
||||
plural: preflightjobs
|
||||
scope: ""
|
||||
singular: preflightjob
|
||||
scope: Namespaced
|
||||
subresources:
|
||||
status: {}
|
||||
validation:
|
||||
openAPIV3Schema:
|
||||
description: PreflightJob is the Schema for the preflightjobs API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: PreflightJobSpec defines the desired state of PreflightJob
|
||||
properties:
|
||||
collectorImage:
|
||||
type: string
|
||||
collectorImagePullPolicy:
|
||||
type: string
|
||||
imagePullPolicy:
|
||||
type: string
|
||||
preflight:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
preflightImage:
|
||||
type: string
|
||||
required:
|
||||
- preflight
|
||||
type: object
|
||||
status:
|
||||
description: PreflightJobStatus defines the observed state of PreflightJob
|
||||
properties:
|
||||
analyzersFailed:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
analyzersRunning:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
analyzersSuccessful:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
collectorsFailed:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
collectorsRunning:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
collectorsSuccessful:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
isAnalyzersComplete:
|
||||
type: boolean
|
||||
isServerReady:
|
||||
type: boolean
|
||||
serverPodName:
|
||||
type: string
|
||||
serverPodNamespace:
|
||||
type: string
|
||||
serverPodPort:
|
||||
type: integer
|
||||
required:
|
||||
- analyzersFailed
|
||||
- analyzersRunning
|
||||
- analyzersSuccessful
|
||||
- collectorsFailed
|
||||
- collectorsRunning
|
||||
- collectorsSuccessful
|
||||
- isAnalyzersComplete
|
||||
- isServerReady
|
||||
- serverPodName
|
||||
- serverPodNamespace
|
||||
- serverPodPort
|
||||
type: object
|
||||
type: object
|
||||
version: v1beta1
|
||||
versions:
|
||||
- name: v1beta1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: PreflightJob is the Schema for the preflightjobs API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: 'Annotations is an unstructured key value map stored
|
||||
with a resource that may be set by external tools to store and retrieve
|
||||
arbitrary metadata. They are not queryable and should be preserved
|
||||
when modifying objects. More info: http://kubernetes.io/docs/user-guide/annotations'
|
||||
type: object
|
||||
clusterName:
|
||||
description: The name of the cluster which the object belongs to.
|
||||
This is used to distinguish resources with same name and namespace
|
||||
in different clusters. This field is not set anywhere right now
|
||||
and apiserver is going to ignore it if set in create or update request.
|
||||
type: string
|
||||
creationTimestamp:
|
||||
description: "CreationTimestamp is a timestamp representing the server
|
||||
time when this object was created. It is not guaranteed to be set
|
||||
in happens-before order across separate operations. Clients may
|
||||
not set this value. It is represented in RFC3339 form and is in
|
||||
UTC. \n Populated by the system. Read-only. Null for lists. More
|
||||
info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata"
|
||||
format: date-time
|
||||
type: string
|
||||
deletionGracePeriodSeconds:
|
||||
description: Number of seconds allowed for this object to gracefully
|
||||
terminate before it will be removed from the system. Only set when
|
||||
deletionTimestamp is also set. May only be shortened. Read-only.
|
||||
format: int64
|
||||
type: integer
|
||||
deletionTimestamp:
|
||||
description: "DeletionTimestamp is RFC 3339 date and time at which
|
||||
this resource will be deleted. This field is set by the server when
|
||||
a graceful deletion is requested by the user, and is not directly
|
||||
settable by a client. The resource is expected to be deleted (no
|
||||
longer visible from resource lists, and not reachable by name) after
|
||||
the time in this field, once the finalizers list is empty. As long
|
||||
as the finalizers list contains items, deletion is blocked. Once
|
||||
the deletionTimestamp is set, this value may not be unset or be
|
||||
set further into the future, although it may be shortened or the
|
||||
resource may be deleted prior to this time. For example, a user
|
||||
may request that a pod is deleted in 30 seconds. The Kubelet will
|
||||
react by sending a graceful termination signal to the containers
|
||||
in the pod. After that 30 seconds, the Kubelet will send a hard
|
||||
termination signal (SIGKILL) to the container and after cleanup,
|
||||
remove the pod from the API. In the presence of network partitions,
|
||||
this object may still exist after this timestamp, until an administrator
|
||||
or automated process can determine the resource is fully terminated.
|
||||
If not set, graceful deletion of the object has not been requested.
|
||||
\n Populated by the system when a graceful deletion is requested.
|
||||
Read-only. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata"
|
||||
format: date-time
|
||||
type: string
|
||||
finalizers:
|
||||
description: Must be empty before the object is deleted from the registry.
|
||||
Each entry is an identifier for the responsible component that will
|
||||
remove the entry from the list. If the deletionTimestamp of the
|
||||
object is non-nil, entries in this list can only be removed.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
generateName:
|
||||
description: "GenerateName is an optional prefix, used by the server,
|
||||
to generate a unique name ONLY IF the Name field has not been provided.
|
||||
If this field is used, the name returned to the client will be different
|
||||
than the name passed. This value will also be combined with a unique
|
||||
suffix. The provided value has the same validation rules as the
|
||||
Name field, and may be truncated by the length of the suffix required
|
||||
to make the value unique on the server. \n If this field is specified
|
||||
and the generated name exists, the server will NOT return a 409
|
||||
- instead, it will either return 201 Created or 500 with Reason
|
||||
ServerTimeout indicating a unique name could not be found in the
|
||||
time allotted, and the client should retry (optionally after the
|
||||
time indicated in the Retry-After header). \n Applied only if Name
|
||||
is not specified. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#idempotency"
|
||||
type: string
|
||||
generation:
|
||||
description: A sequence number representing a specific generation
|
||||
of the desired state. Populated by the system. Read-only.
|
||||
format: int64
|
||||
type: integer
|
||||
initializers:
|
||||
description: "An initializer is a controller which enforces some system
|
||||
invariant at object creation time. This field is a list of initializers
|
||||
that have not yet acted on this object. If nil or empty, this object
|
||||
has been completely initialized. Otherwise, the object is considered
|
||||
uninitialized and is hidden (in list/watch and get calls) from clients
|
||||
that haven't explicitly asked to observe uninitialized objects.
|
||||
\n When an object is created, the system will populate this list
|
||||
with the current set of initializers. Only privileged users may
|
||||
set or modify this list. Once it is empty, it may not be modified
|
||||
further by any user. \n DEPRECATED - initializers are an alpha field
|
||||
and will be removed in v1.15."
|
||||
properties:
|
||||
pending:
|
||||
description: Pending is a list of initializers that must execute
|
||||
in order before this object is visible. When the last pending
|
||||
initializer is removed, and no failing result is set, the initializers
|
||||
struct will be set to nil and the object is considered as initialized
|
||||
and visible to all clients.
|
||||
items:
|
||||
properties:
|
||||
name:
|
||||
description: name of the process that is responsible for
|
||||
initializing this object.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: array
|
||||
result:
|
||||
description: If result is set with the Failure field, the object
|
||||
will be persisted to storage and then deleted, ensuring that
|
||||
other clients can observe the deletion.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this
|
||||
representation of an object. Servers should convert recognized
|
||||
schemas to the latest internal value, and may reject unrecognized
|
||||
values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources'
|
||||
type: string
|
||||
code:
|
||||
description: Suggested HTTP return code for this status, 0
|
||||
if not set.
|
||||
format: int32
|
||||
type: integer
|
||||
details:
|
||||
description: Extended data associated with the reason. Each
|
||||
reason may define its own extended details. This field is
|
||||
optional and the data returned is not guaranteed to conform
|
||||
to any schema except that defined by the reason type.
|
||||
properties:
|
||||
causes:
|
||||
description: The Causes array includes more details associated
|
||||
with the StatusReason failure. Not all StatusReasons
|
||||
may provide detailed causes.
|
||||
items:
|
||||
properties:
|
||||
field:
|
||||
description: "The field of the resource that has
|
||||
caused this error, as named by its JSON serialization.
|
||||
May include dot and postfix notation for nested
|
||||
attributes. Arrays are zero-indexed. Fields may
|
||||
appear more than once in an array of causes due
|
||||
to fields having multiple errors. Optional. \n
|
||||
Examples: \"name\" - the field \"name\" on the
|
||||
current resource \"items[0].name\" - the field
|
||||
\"name\" on the first array entry in \"items\""
|
||||
type: string
|
||||
message:
|
||||
description: A human-readable description of the
|
||||
cause of the error. This field may be presented
|
||||
as-is to a reader.
|
||||
type: string
|
||||
reason:
|
||||
description: A machine-readable description of the
|
||||
cause of the error. If this value is empty there
|
||||
is no information available.
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
group:
|
||||
description: The group attribute of the resource associated
|
||||
with the status StatusReason.
|
||||
type: string
|
||||
kind:
|
||||
description: 'The kind attribute of the resource associated
|
||||
with the status StatusReason. On some operations may
|
||||
differ from the requested resource Kind. More info:
|
||||
https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
name:
|
||||
description: The name attribute of the resource associated
|
||||
with the status StatusReason (when there is a single
|
||||
name which can be described).
|
||||
type: string
|
||||
retryAfterSeconds:
|
||||
description: If specified, the time in seconds before
|
||||
the operation should be retried. Some errors may indicate
|
||||
the client must take an alternate action - for those
|
||||
errors this field may indicate how long to wait before
|
||||
taking the alternate action.
|
||||
format: int32
|
||||
type: integer
|
||||
uid:
|
||||
description: 'UID of the resource. (when there is a single
|
||||
resource which can be described). More info: http://kubernetes.io/docs/user-guide/identifiers#uids'
|
||||
type: string
|
||||
type: object
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST
|
||||
resource this object represents. Servers may infer this
|
||||
from the endpoint the client submits requests to. Cannot
|
||||
be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
message:
|
||||
description: A human-readable description of the status of
|
||||
this operation.
|
||||
type: string
|
||||
metadata:
|
||||
description: 'Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds'
|
||||
properties:
|
||||
continue:
|
||||
description: continue may be set if the user set a limit
|
||||
on the number of items returned, and indicates that
|
||||
the server has more data available. The value is opaque
|
||||
and may be used to issue another request to the endpoint
|
||||
that served this list to retrieve the next set of available
|
||||
objects. Continuing a consistent list may not be possible
|
||||
if the server configuration has changed or more than
|
||||
a few minutes have passed. The resourceVersion field
|
||||
returned when using this continue value will be identical
|
||||
to the value in the first response, unless you have
|
||||
received this token from an error message.
|
||||
type: string
|
||||
resourceVersion:
|
||||
description: 'String that identifies the server''s internal
|
||||
version of this object that can be used by clients to
|
||||
determine when objects have changed. Value must be treated
|
||||
as opaque by clients and passed unmodified back to the
|
||||
server. Populated by the system. Read-only. More info:
|
||||
https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency'
|
||||
type: string
|
||||
selfLink:
|
||||
description: selfLink is a URL representing this object.
|
||||
Populated by the system. Read-only.
|
||||
type: string
|
||||
type: object
|
||||
reason:
|
||||
description: A machine-readable description of why this operation
|
||||
is in the "Failure" status. If this value is empty there
|
||||
is no information available. A Reason clarifies an HTTP
|
||||
status code but does not override it.
|
||||
type: string
|
||||
status:
|
||||
description: 'Status of the operation. One of: "Success" or
|
||||
"Failure". More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status'
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- pending
|
||||
type: object
|
||||
labels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: 'Map of string keys and values that can be used to organize
|
||||
and categorize (scope and select) objects. May match selectors of
|
||||
replication controllers and services. More info: http://kubernetes.io/docs/user-guide/labels'
|
||||
type: object
|
||||
managedFields:
|
||||
description: "ManagedFields maps workflow-id and version to the set
|
||||
of fields that are managed by that workflow. This is mostly for
|
||||
internal housekeeping, and users typically shouldn't need to set
|
||||
or understand this field. A workflow can be the user's name, a controller's
|
||||
name, or the name of a specific apply path like \"ci-cd\". The set
|
||||
of fields is always in the version that the workflow used when modifying
|
||||
the object. \n This field is alpha and can be changed or removed
|
||||
without notice."
|
||||
items:
|
||||
properties:
|
||||
apiVersion:
|
||||
description: APIVersion defines the version of this resource
|
||||
that this field set applies to. The format is "group/version"
|
||||
just like the top-level APIVersion field. It is necessary
|
||||
to track the version of a field set because it cannot be automatically
|
||||
converted.
|
||||
type: string
|
||||
fields:
|
||||
additionalProperties: true
|
||||
description: Fields identifies a set of fields.
|
||||
type: object
|
||||
manager:
|
||||
description: Manager is an identifier of the workflow managing
|
||||
these fields.
|
||||
type: string
|
||||
operation:
|
||||
description: Operation is the type of operation which lead to
|
||||
this ManagedFieldsEntry being created. The only valid values
|
||||
for this field are 'Apply' and 'Update'.
|
||||
type: string
|
||||
time:
|
||||
description: Time is timestamp of when these fields were set.
|
||||
It should always be empty if Operation is 'Apply'
|
||||
format: date-time
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
name:
|
||||
description: 'Name must be unique within a namespace. Is required
|
||||
when creating resources, although some resources may allow a client
|
||||
to request the generation of an appropriate name automatically.
|
||||
Name is primarily intended for creation idempotence and configuration
|
||||
definition. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names'
|
||||
type: string
|
||||
namespace:
|
||||
description: "Namespace defines the space within each name must be
|
||||
unique. An empty namespace is equivalent to the \"default\" namespace,
|
||||
but \"default\" is the canonical representation. Not all objects
|
||||
are required to be scoped to a namespace - the value of this field
|
||||
for those objects will be empty. \n Must be a DNS_LABEL. Cannot
|
||||
be updated. More info: http://kubernetes.io/docs/user-guide/namespaces"
|
||||
type: string
|
||||
ownerReferences:
|
||||
description: List of objects depended by this object. If ALL objects
|
||||
in the list have been deleted, this object will be garbage collected.
|
||||
If this object is managed by a controller, then an entry in this
|
||||
list will point to this controller, with the controller field set
|
||||
to true. There cannot be more than one managing controller.
|
||||
items:
|
||||
properties:
|
||||
apiVersion:
|
||||
description: API version of the referent.
|
||||
type: string
|
||||
blockOwnerDeletion:
|
||||
description: If true, AND if the owner has the "foregroundDeletion"
|
||||
finalizer, then the owner cannot be deleted from the key-value
|
||||
store until this reference is removed. Defaults to false.
|
||||
To set this field, a user needs "delete" permission of the
|
||||
owner, otherwise 422 (Unprocessable Entity) will be returned.
|
||||
type: boolean
|
||||
controller:
|
||||
description: If true, this reference points to the managing
|
||||
controller.
|
||||
type: boolean
|
||||
kind:
|
||||
description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
name:
|
||||
description: 'Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names'
|
||||
type: string
|
||||
uid:
|
||||
description: 'UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids'
|
||||
type: string
|
||||
required:
|
||||
- apiVersion
|
||||
- kind
|
||||
- name
|
||||
- uid
|
||||
type: object
|
||||
type: array
|
||||
resourceVersion:
|
||||
description: "An opaque value that represents the internal version
|
||||
of this object that can be used by clients to determine when objects
|
||||
have changed. May be used for optimistic concurrency, change detection,
|
||||
and the watch operation on a resource or set of resources. Clients
|
||||
must treat these values as opaque and passed unmodified back to
|
||||
the server. They may only be valid for a particular resource or
|
||||
set of resources. \n Populated by the system. Read-only. Value must
|
||||
be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency"
|
||||
type: string
|
||||
selfLink:
|
||||
description: SelfLink is a URL representing this object. Populated
|
||||
by the system. Read-only.
|
||||
type: string
|
||||
uid:
|
||||
description: "UID is the unique in time and space value for this object.
|
||||
It is typically generated by the server on successful creation of
|
||||
a resource and is not allowed to change on PUT operations. \n Populated
|
||||
by the system. Read-only. More info: http://kubernetes.io/docs/user-guide/identifiers#uids"
|
||||
type: string
|
||||
type: object
|
||||
spec:
|
||||
properties:
|
||||
collectorImage:
|
||||
type: string
|
||||
collectorImagePullPolicy:
|
||||
type: string
|
||||
imagePullPolicy:
|
||||
type: string
|
||||
preflight:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
preflightImage:
|
||||
type: string
|
||||
required:
|
||||
- preflight
|
||||
type: object
|
||||
status:
|
||||
properties:
|
||||
analyzersFailed:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
analyzersRunning:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
analyzersSuccessful:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
collectorsFailed:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
collectorsRunning:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
collectorsSuccessful:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
isAnalyzersComplete:
|
||||
type: boolean
|
||||
isServerReady:
|
||||
type: boolean
|
||||
serverPodName:
|
||||
type: string
|
||||
serverPodNamespace:
|
||||
type: string
|
||||
serverPodPort:
|
||||
type: integer
|
||||
required:
|
||||
- isServerReady
|
||||
- serverPodName
|
||||
- serverPodNamespace
|
||||
- serverPodPort
|
||||
- collectorsRunning
|
||||
- collectorsSuccessful
|
||||
- collectorsFailed
|
||||
- isAnalyzersComplete
|
||||
- analyzersRunning
|
||||
- analyzersSuccessful
|
||||
- analyzersFailed
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,87 @@
|
||||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.7.0
|
||||
creationTimestamp: null
|
||||
name: redactors.troubleshoot.replicated.com
|
||||
spec:
|
||||
group: troubleshoot.replicated.com
|
||||
names:
|
||||
kind: Redactor
|
||||
listKind: RedactorList
|
||||
plural: redactors
|
||||
singular: redactor
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1beta1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: Redactor is the Schema for the redaction API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: RedactorSpec defines the desired state of Redactor
|
||||
properties:
|
||||
redactors:
|
||||
items:
|
||||
properties:
|
||||
fileSelector:
|
||||
properties:
|
||||
file:
|
||||
type: string
|
||||
files:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
name:
|
||||
type: string
|
||||
removals:
|
||||
properties:
|
||||
regex:
|
||||
items:
|
||||
properties:
|
||||
redactor:
|
||||
type: string
|
||||
selector:
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
values:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
yamlPath:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
status:
|
||||
description: RedactorStatus defines the observed state of Redactor
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,521 @@
|
||||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.7.0
|
||||
creationTimestamp: null
|
||||
name: collectors.troubleshoot.sh
|
||||
spec:
|
||||
group: troubleshoot.sh
|
||||
names:
|
||||
kind: Collector
|
||||
listKind: CollectorList
|
||||
plural: collectors
|
||||
singular: collector
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1beta2
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: Collector is the Schema for the collectors API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: CollectorSpec defines the desired state of Collector
|
||||
properties:
|
||||
afterCollection:
|
||||
items:
|
||||
properties:
|
||||
callback:
|
||||
properties:
|
||||
method:
|
||||
type: string
|
||||
redactUri:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
required:
|
||||
- method
|
||||
- redactUri
|
||||
- uri
|
||||
type: object
|
||||
uploadResultsTo:
|
||||
properties:
|
||||
method:
|
||||
type: string
|
||||
redactUri:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
required:
|
||||
- method
|
||||
- redactUri
|
||||
- uri
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
collectors:
|
||||
items:
|
||||
properties:
|
||||
ceph:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
namespace:
|
||||
type: string
|
||||
timeout:
|
||||
type: string
|
||||
required:
|
||||
- namespace
|
||||
type: object
|
||||
clusterInfo:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
type: object
|
||||
clusterResources:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
namespaces:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
collectd:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
hostPath:
|
||||
type: string
|
||||
image:
|
||||
type: string
|
||||
imagePullPolicy:
|
||||
type: string
|
||||
imagePullSecret:
|
||||
properties:
|
||||
data:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
name:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
type: object
|
||||
namespace:
|
||||
type: string
|
||||
timeout:
|
||||
type: string
|
||||
required:
|
||||
- hostPath
|
||||
- image
|
||||
- namespace
|
||||
type: object
|
||||
configMap:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
includeAllData:
|
||||
type: boolean
|
||||
includeValue:
|
||||
type: boolean
|
||||
key:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
selector:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
copy:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
containerName:
|
||||
type: string
|
||||
containerPath:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
extractArchive:
|
||||
type: boolean
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
selector:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- containerPath
|
||||
- namespace
|
||||
- selector
|
||||
type: object
|
||||
copyFromHost:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
extractArchive:
|
||||
type: boolean
|
||||
hostPath:
|
||||
type: string
|
||||
image:
|
||||
type: string
|
||||
imagePullPolicy:
|
||||
type: string
|
||||
imagePullSecret:
|
||||
properties:
|
||||
data:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
name:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
type: object
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
timeout:
|
||||
type: string
|
||||
required:
|
||||
- hostPath
|
||||
- image
|
||||
- namespace
|
||||
type: object
|
||||
data:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
data:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
name:
|
||||
type: string
|
||||
required:
|
||||
- data
|
||||
type: object
|
||||
exec:
|
||||
properties:
|
||||
args:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
collectorName:
|
||||
type: string
|
||||
command:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
containerName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
selector:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
timeout:
|
||||
type: string
|
||||
required:
|
||||
- namespace
|
||||
- selector
|
||||
type: object
|
||||
http:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
get:
|
||||
properties:
|
||||
headers:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
insecureSkipVerify:
|
||||
type: boolean
|
||||
url:
|
||||
type: string
|
||||
required:
|
||||
- url
|
||||
type: object
|
||||
name:
|
||||
type: string
|
||||
post:
|
||||
properties:
|
||||
body:
|
||||
type: string
|
||||
headers:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
insecureSkipVerify:
|
||||
type: boolean
|
||||
url:
|
||||
type: string
|
||||
required:
|
||||
- url
|
||||
type: object
|
||||
put:
|
||||
properties:
|
||||
body:
|
||||
type: string
|
||||
headers:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
insecureSkipVerify:
|
||||
type: boolean
|
||||
url:
|
||||
type: string
|
||||
required:
|
||||
- url
|
||||
type: object
|
||||
type: object
|
||||
logs:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
containerNames:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
exclude:
|
||||
type: BoolString
|
||||
limits:
|
||||
properties:
|
||||
maxAge:
|
||||
type: string
|
||||
maxLines:
|
||||
format: int64
|
||||
type: integer
|
||||
sinceTime:
|
||||
format: date-time
|
||||
type: string
|
||||
type: object
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
selector:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- selector
|
||||
type: object
|
||||
longhorn:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
namespace:
|
||||
type: string
|
||||
timeout:
|
||||
type: string
|
||||
required:
|
||||
- namespace
|
||||
type: object
|
||||
mysql:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
uri:
|
||||
type: string
|
||||
required:
|
||||
- uri
|
||||
type: object
|
||||
postgres:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
uri:
|
||||
type: string
|
||||
required:
|
||||
- uri
|
||||
type: object
|
||||
redis:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
uri:
|
||||
type: string
|
||||
required:
|
||||
- uri
|
||||
type: object
|
||||
registryImages:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
imagePullSecret:
|
||||
properties:
|
||||
data:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
name:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
type: object
|
||||
images:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
namespace:
|
||||
type: string
|
||||
required:
|
||||
- images
|
||||
- namespace
|
||||
type: object
|
||||
run:
|
||||
properties:
|
||||
args:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
collectorName:
|
||||
type: string
|
||||
command:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
exclude:
|
||||
type: BoolString
|
||||
image:
|
||||
type: string
|
||||
imagePullPolicy:
|
||||
type: string
|
||||
imagePullSecret:
|
||||
properties:
|
||||
data:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
name:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
type: object
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
timeout:
|
||||
type: string
|
||||
required:
|
||||
- image
|
||||
- namespace
|
||||
type: object
|
||||
secret:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
includeValue:
|
||||
type: boolean
|
||||
key:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
selector:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
sysctl:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
image:
|
||||
type: string
|
||||
imagePullPolicy:
|
||||
type: string
|
||||
imagePullSecret:
|
||||
properties:
|
||||
data:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
name:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
type: object
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
timeout:
|
||||
type: string
|
||||
required:
|
||||
- image
|
||||
- namespace
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
status:
|
||||
description: CollectorStatus defines the observed state of Collector
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,87 @@
|
||||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.7.0
|
||||
creationTimestamp: null
|
||||
name: redactors.troubleshoot.sh
|
||||
spec:
|
||||
group: troubleshoot.sh
|
||||
names:
|
||||
kind: Redactor
|
||||
listKind: RedactorList
|
||||
plural: redactors
|
||||
singular: redactor
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1beta2
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: Redactor is the Schema for the redaction API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: RedactorSpec defines the desired state of Redactor
|
||||
properties:
|
||||
redactors:
|
||||
items:
|
||||
properties:
|
||||
fileSelector:
|
||||
properties:
|
||||
file:
|
||||
type: string
|
||||
files:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
name:
|
||||
type: string
|
||||
removals:
|
||||
properties:
|
||||
regex:
|
||||
items:
|
||||
properties:
|
||||
redactor:
|
||||
type: string
|
||||
selector:
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
values:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
yamlPath:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
status:
|
||||
description: RedactorStatus defines the observed state of Redactor
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
@@ -0,0 +1,361 @@
|
||||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.7.0
|
||||
creationTimestamp: null
|
||||
name: remotecollectors.troubleshoot.sh
|
||||
spec:
|
||||
group: troubleshoot.sh
|
||||
names:
|
||||
kind: RemoteCollector
|
||||
listKind: RemoteCollectorList
|
||||
plural: remotecollectors
|
||||
singular: remotecollector
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1beta2
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: RemoteCollector is the Schema for the remote collectors API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: RemoteCollectorSpec defines the desired state of the RemoteCollector
|
||||
properties:
|
||||
afterCollection:
|
||||
items:
|
||||
properties:
|
||||
callback:
|
||||
properties:
|
||||
method:
|
||||
type: string
|
||||
redactUri:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
required:
|
||||
- method
|
||||
- redactUri
|
||||
- uri
|
||||
type: object
|
||||
uploadResultsTo:
|
||||
properties:
|
||||
method:
|
||||
type: string
|
||||
redactUri:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
required:
|
||||
- method
|
||||
- redactUri
|
||||
- uri
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
collectors:
|
||||
items:
|
||||
properties:
|
||||
blockDevices:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
type: object
|
||||
certificate:
|
||||
properties:
|
||||
certificatePath:
|
||||
type: string
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
keyPath:
|
||||
type: string
|
||||
required:
|
||||
- certificatePath
|
||||
- keyPath
|
||||
type: object
|
||||
cpu:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
type: object
|
||||
diskUsage:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
path:
|
||||
type: string
|
||||
required:
|
||||
- path
|
||||
type: object
|
||||
filesystemPerformance:
|
||||
description: RemoteFilesystemPerformance benchmarks sequential
|
||||
write latency on a single file. The optional background IOPS
|
||||
feature attempts to mimic real-world conditions by running
|
||||
read and write workloads prior to and during benchmark execution.
|
||||
properties:
|
||||
backgroundIOPSWarmupSeconds:
|
||||
description: How long to run the background IOPS read and
|
||||
write workloads prior to starting the benchmarks.
|
||||
type: integer
|
||||
backgroundReadIOPS:
|
||||
description: The target read IOPS to run while benchmarking.
|
||||
This is a limit and there is no guarantee it will be reached.
|
||||
This is the total IOPS for all background read jobs.
|
||||
type: integer
|
||||
backgroundReadIOPSJobs:
|
||||
description: Number of threads to use for background read
|
||||
IOPS. This should be set high enough to reach the target
|
||||
specified in BackgrounReadIOPS.
|
||||
type: integer
|
||||
backgroundWriteIOPS:
|
||||
description: The target write IOPS to run while benchmarking.
|
||||
This is a limit and there is no guarantee it will be reached.
|
||||
This is the total IOPS for all background write jobs.
|
||||
type: integer
|
||||
backgroundWriteIOPSJobs:
|
||||
description: 'Number of threads to use for background write
|
||||
IOPS. This should be set high enough to reach the target
|
||||
specified in BackgroundWriteIOPS. Example: If BackgroundWriteIOPS
|
||||
is 100 and write latency is 10ms then a single job would
|
||||
barely be able to reach 100 IOPS so this should be at
|
||||
least 2.'
|
||||
type: integer
|
||||
collectorName:
|
||||
type: string
|
||||
datasync:
|
||||
description: Whether to call datasync on the file after
|
||||
each write. Skipped if Sync is also true. Does not apply
|
||||
to background IOPS task.
|
||||
type: boolean
|
||||
directory:
|
||||
description: The directory where the benchmark will create
|
||||
files.
|
||||
type: string
|
||||
enableBackgroundIOPS:
|
||||
description: Enable the background IOPS feature.
|
||||
type: boolean
|
||||
exclude:
|
||||
type: BoolString
|
||||
fileSize:
|
||||
description: The size of the file used in the benchmark.
|
||||
The number of IO operations for the benchmark will be
|
||||
FileSize / OperationSizeBytes. Accepts valid Kubernetes
|
||||
resource units such as Mi.
|
||||
type: string
|
||||
operationSize:
|
||||
description: The size of each write operation performed
|
||||
while benchmarking. This does not apply to the background
|
||||
IOPS feature if enabled, since those must be fixed at
|
||||
4096.
|
||||
format: int64
|
||||
type: integer
|
||||
sync:
|
||||
description: Whether to call sync on the file after each
|
||||
write. Does not apply to background IOPS task.
|
||||
type: boolean
|
||||
timeout:
|
||||
description: Total timeout, including background IOPS setup
|
||||
and warmup if enabled.
|
||||
type: string
|
||||
required:
|
||||
- backgroundIOPSWarmupSeconds
|
||||
- backgroundReadIOPS
|
||||
- backgroundReadIOPSJobs
|
||||
- backgroundWriteIOPS
|
||||
- backgroundWriteIOPSJobs
|
||||
- enableBackgroundIOPS
|
||||
type: object
|
||||
hostServices:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
type: object
|
||||
http:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
get:
|
||||
properties:
|
||||
headers:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
insecureSkipVerify:
|
||||
type: boolean
|
||||
url:
|
||||
type: string
|
||||
required:
|
||||
- url
|
||||
type: object
|
||||
post:
|
||||
properties:
|
||||
body:
|
||||
type: string
|
||||
headers:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
insecureSkipVerify:
|
||||
type: boolean
|
||||
url:
|
||||
type: string
|
||||
required:
|
||||
- url
|
||||
type: object
|
||||
put:
|
||||
properties:
|
||||
body:
|
||||
type: string
|
||||
headers:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
insecureSkipVerify:
|
||||
type: boolean
|
||||
url:
|
||||
type: string
|
||||
required:
|
||||
- url
|
||||
type: object
|
||||
type: object
|
||||
httpLoadBalancer:
|
||||
properties:
|
||||
address:
|
||||
type: string
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
path:
|
||||
type: string
|
||||
port:
|
||||
type: integer
|
||||
timeout:
|
||||
type: string
|
||||
required:
|
||||
- address
|
||||
- path
|
||||
- port
|
||||
type: object
|
||||
ipv4Interfaces:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
type: object
|
||||
kernelModules:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
type: object
|
||||
memory:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
type: object
|
||||
systemPackages:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
type: object
|
||||
tcpConnect:
|
||||
properties:
|
||||
address:
|
||||
type: string
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
timeout:
|
||||
type: string
|
||||
required:
|
||||
- address
|
||||
type: object
|
||||
tcpLoadBalancer:
|
||||
properties:
|
||||
address:
|
||||
type: string
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
port:
|
||||
type: integer
|
||||
timeout:
|
||||
type: string
|
||||
required:
|
||||
- address
|
||||
- port
|
||||
type: object
|
||||
tcpPortStatus:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
interface:
|
||||
type: string
|
||||
port:
|
||||
type: integer
|
||||
required:
|
||||
- port
|
||||
type: object
|
||||
time:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
nodeSelector:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
status:
|
||||
description: CollectorStatus defines the observed state of Collector
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,49 +0,0 @@
|
||||
# Adds namespace to all resources.
|
||||
namespace: troubleshoot-system
|
||||
|
||||
# Value of this field is prepended to the
|
||||
# names of all resources, e.g. a deployment named
|
||||
# "wordpress" becomes "alices-wordpress".
|
||||
# Note that it should also match with the prefix (text before '-') of the namespace
|
||||
# field above.
|
||||
namePrefix: troubleshoot-
|
||||
|
||||
# Labels to add to all resources and selectors.
|
||||
#commonLabels:
|
||||
# someName: someValue
|
||||
|
||||
# Each entry in this list must resolve to an existing
|
||||
# resource definition in YAML. These are the resource
|
||||
# files that kustomize reads, modifies and emits as a
|
||||
# YAML string, with resources separated by document
|
||||
# markers ("---").
|
||||
resources:
|
||||
- ../rbac/rbac_role.yaml
|
||||
- ../rbac/rbac_role_binding.yaml
|
||||
- ../manager/manager.yaml
|
||||
# Comment the following 3 lines if you want to disable
|
||||
# the auth proxy (https://github.com/brancz/kube-rbac-proxy)
|
||||
# which protects your /metrics endpoint.
|
||||
- ../rbac/auth_proxy_service.yaml
|
||||
- ../rbac/auth_proxy_role.yaml
|
||||
- ../rbac/auth_proxy_role_binding.yaml
|
||||
|
||||
patches:
|
||||
- manager_image_patch.yaml
|
||||
# Protect the /metrics endpoint by putting it behind auth.
|
||||
# Only one of manager_auth_proxy_patch.yaml and
|
||||
# manager_prometheus_metrics_patch.yaml should be enabled.
|
||||
- manager_auth_proxy_patch.yaml
|
||||
# If you want your controller-manager to expose the /metrics
|
||||
# endpoint w/o any authn/z, uncomment the following line and
|
||||
# comment manager_auth_proxy_patch.yaml.
|
||||
# Only one of manager_auth_proxy_patch.yaml and
|
||||
# manager_prometheus_metrics_patch.yaml should be enabled.
|
||||
#- manager_prometheus_metrics_patch.yaml
|
||||
|
||||
vars:
|
||||
- name: WEBHOOK_SECRET_NAME
|
||||
objref:
|
||||
kind: Secret
|
||||
name: webhook-server-secret
|
||||
apiVersion: v1
|
||||
@@ -1,24 +0,0 @@
|
||||
# This patch inject a sidecar container which is a HTTP proxy for the controller manager,
|
||||
# it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews.
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: controller-manager
|
||||
namespace: system
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: kube-rbac-proxy
|
||||
image: gcr.io/kubebuilder/kube-rbac-proxy:v0.4.0
|
||||
args:
|
||||
- "--secure-listen-address=0.0.0.0:8443"
|
||||
- "--upstream=http://127.0.0.1:8089/"
|
||||
- "--logtostderr=true"
|
||||
- "--v=10"
|
||||
ports:
|
||||
- containerPort: 8443
|
||||
name: https
|
||||
- name: manager
|
||||
args:
|
||||
- "--metrics-addr=127.0.0.1:8088"
|
||||
@@ -1,12 +0,0 @@
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: controller-manager
|
||||
namespace: system
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
# Change the value of image field below to your controller image URL
|
||||
- image: IMAGE_URL
|
||||
name: manager
|
||||
@@ -1,19 +0,0 @@
|
||||
# This patch enables Prometheus scraping for the manager pod.
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: controller-manager
|
||||
namespace: system
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
prometheus.io/scrape: 'true'
|
||||
spec:
|
||||
containers:
|
||||
# Expose the prometheus metrics on default port
|
||||
- name: manager
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
name: metrics
|
||||
protocol: TCP
|
||||
@@ -1,83 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
labels:
|
||||
control-plane: controller-manager
|
||||
controller-tools.k8s.io: "1.0"
|
||||
name: system
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: controller-manager-service
|
||||
namespace: system
|
||||
labels:
|
||||
control-plane: controller-manager
|
||||
controller-tools.k8s.io: "1.0"
|
||||
spec:
|
||||
selector:
|
||||
control-plane: controller-manager
|
||||
controller-tools.k8s.io: "1.0"
|
||||
ports:
|
||||
- port: 443
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: controller-manager
|
||||
namespace: system
|
||||
labels:
|
||||
control-plane: controller-manager
|
||||
controller-tools.k8s.io: "1.0"
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
control-plane: controller-manager
|
||||
controller-tools.k8s.io: "1.0"
|
||||
serviceName: controller-manager-service
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
control-plane: controller-manager
|
||||
controller-tools.k8s.io: "1.0"
|
||||
spec:
|
||||
containers:
|
||||
- command:
|
||||
- /manager
|
||||
image: controller:latest
|
||||
imagePullPolicy: Always
|
||||
name: manager
|
||||
env:
|
||||
- name: POD_NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
- name: SECRET_NAME
|
||||
value: $(WEBHOOK_SECRET_NAME)
|
||||
resources:
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 30Mi
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 20Mi
|
||||
ports:
|
||||
- containerPort: 9876
|
||||
name: webhook-server
|
||||
protocol: TCP
|
||||
volumeMounts:
|
||||
- mountPath: /tmp/cert
|
||||
name: cert
|
||||
readOnly: true
|
||||
terminationGracePeriodSeconds: 10
|
||||
volumes:
|
||||
- name: cert
|
||||
secret:
|
||||
defaultMode: 420
|
||||
secretName: webhook-server-secret
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: webhook-server-secret
|
||||
namespace: system
|
||||
@@ -1,13 +0,0 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: proxy-role
|
||||
rules:
|
||||
- apiGroups: ["authentication.k8s.io"]
|
||||
resources:
|
||||
- tokenreviews
|
||||
verbs: ["create"]
|
||||
- apiGroups: ["authorization.k8s.io"]
|
||||
resources:
|
||||
- subjectaccessreviews
|
||||
verbs: ["create"]
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user