mirror of
https://github.com/replicatedhq/troubleshoot.git
synced 2026-08-27 00:37:20 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3548b46cfc | ||
|
|
766469b867 | ||
|
|
7cdcd2afa9 | ||
|
|
ca04a67d29 | ||
|
|
2484b3b1eb | ||
|
|
4d23096657 | ||
|
|
ae0cbc7044 | ||
|
|
6fc75138d9 | ||
|
|
01d322584a | ||
|
|
6493064e45 | ||
|
|
00a46b9374 | ||
|
|
6754bd8a59 | ||
|
|
285631446e | ||
|
|
f692635054 | ||
|
|
39606f6d3b | ||
|
|
19a3753265 | ||
|
|
53db32e8d6 | ||
|
|
64d5330ae5 | ||
|
|
b020c76977 | ||
|
|
5991d6b250 | ||
|
|
9a457f7f72 | ||
|
|
c99e34f230 | ||
|
|
a86d9435a8 | ||
|
|
ff4870627e | ||
|
|
0d5f9697eb | ||
|
|
3f289b7449 | ||
|
|
ea7b6c8839 | ||
|
|
4cd8db07c8 | ||
|
|
132d042e79 | ||
|
|
d41d350097 | ||
|
|
dc1687a76a | ||
|
|
0f3f827974 | ||
|
|
71c9345157 | ||
|
|
181fa16b6c | ||
|
|
71d54c0d25 | ||
|
|
ffa242c2f1 | ||
|
|
e481adf017 | ||
|
|
43ef7e8f76 | ||
|
|
61722e0d47 | ||
|
|
6eb9c7096f | ||
|
+3 |
e9fac00516 | ||
|
|
b687656436 | ||
|
|
e02fae86c3 | ||
|
|
ae58bea4df | ||
|
|
96a8b2a713 | ||
|
|
df45d16c2c | ||
|
|
880739d885 | ||
|
|
553b75f8f0 | ||
|
|
d17e5d9a6d | ||
|
|
8fe5bffae4 | ||
|
|
79f8e6efab | ||
|
|
26d176a994 | ||
|
|
cbd648af05 | ||
|
|
a2bddc3f3b | ||
|
|
d85f7798f3 | ||
|
|
7b5b03e16d | ||
|
|
e45cff86e2 | ||
|
|
f3db02a200 | ||
|
|
5b51018541 | ||
|
|
617ddf11d9 | ||
|
|
3419a9b888 | ||
|
|
7b56ed4e51 | ||
|
|
d21e4855e7 | ||
|
|
6cc48f4751 | ||
|
|
546ffde14b | ||
|
|
299258ef87 | ||
|
|
c4f2dc9339 | ||
|
|
93e5ca8eaa | ||
|
|
777224f4c3 | ||
|
|
9f6739fdbe | ||
|
|
661a948d86 | ||
|
|
0bd6b88f04 | ||
|
|
c6f7cb6df0 | ||
|
|
c640a01e9f | ||
|
|
8871e53584 | ||
|
|
e85e91e784 | ||
|
|
752dacd5ed | ||
|
|
49fe355086 | ||
|
|
b8680102e5 | ||
|
|
b1d1b89068 | ||
|
|
100f9a13b6 | ||
|
|
e156b8c215 | ||
|
|
1114902707 | ||
|
|
4fca6aff98 | ||
|
|
f27c64cf85 | ||
|
|
0f6e6335fb | ||
|
|
925e94ad98 | ||
|
|
5bc3c33ee8 | ||
|
|
271e2d2af9 | ||
|
|
48ff918fcd | ||
|
|
dcb91673b5 | ||
|
|
c62e71a578 | ||
|
|
a8da38dd15 | ||
|
|
1756df905f | ||
|
|
2fcdc77cd3 | ||
|
|
ad1a56251f | ||
|
|
cf4eed1110 | ||
|
|
4939559789 | ||
|
|
533d2917a9 | ||
|
|
c8d9864235 | ||
|
|
827c49ca00 | ||
|
|
de0371053a | ||
|
|
2b4bcd2433 | ||
|
|
d9f4dca6b7 | ||
|
|
76922ee59b | ||
|
|
4965941ea9 | ||
|
|
18c4a39826 | ||
|
|
962e2c7d7e | ||
|
|
f1066f5d76 | ||
|
|
70af0ff3d0 | ||
|
|
4601db58f9 | ||
|
|
ddc07e1205 | ||
|
|
251fcc6dec | ||
|
|
75eb0d7f89 | ||
|
|
199efca2ea | ||
|
|
a442ac8fc4 | ||
|
|
a31e2ff1b9 | ||
|
|
87c153cc8c | ||
|
|
8ba5d6f6cb | ||
|
|
a523551da9 | ||
|
|
d73d5c6a3a | ||
|
|
8cb922c3b9 | ||
|
|
94dd7a670c | ||
|
|
0f2892c316 | ||
|
|
df43c9fc21 | ||
|
|
ebeed77287 | ||
|
|
bc6528908f | ||
|
|
e2053a00a2 | ||
|
|
421cccf919 | ||
|
|
be26462c19 | ||
|
|
13534bf7fd | ||
|
|
60b4931295 | ||
|
|
e48fa36eaf | ||
|
|
f2be6f5829 | ||
|
|
cd1511a8fb | ||
|
|
9c77a0e3da | ||
|
|
c8820b3d8e | ||
|
|
cf3a292877 | ||
|
|
a4c4b24056 | ||
|
|
e9f386a840 | ||
|
|
d96b2ded78 | ||
|
|
8c31e61367 | ||
|
|
8e174fb46c | ||
|
|
2a61a8686a | ||
|
|
c85bf9a9a6 | ||
|
|
bfb77ad601 | ||
|
|
c4c66633e5 | ||
|
|
f13a9598b2 | ||
|
|
4fa8411259 | ||
|
|
b693e6650d | ||
|
|
7e3a59cfc0 | ||
|
|
48faf0c144 | ||
|
|
fbbcf87405 | ||
|
|
6530cb364f | ||
|
|
c9b48aa716 | ||
|
|
511671f699 | ||
|
|
7a3bcfc74a | ||
|
|
e88f309391 | ||
|
|
da48621eff | ||
|
|
e51a25dc2f | ||
|
|
7a20aa1934 | ||
|
|
78bcafe489 | ||
|
|
233966d4be | ||
|
|
ebc569fcdb | ||
|
|
0521b0e82b | ||
|
|
34f29f2914 | ||
|
|
1de1d00680 | ||
|
|
92a5f3569f | ||
|
|
495b1a760d | ||
|
|
7bcfd01d43 | ||
|
|
f2ffef80af | ||
|
|
a5244a262c | ||
|
|
18d9a16ceb | ||
|
|
473738d709 | ||
|
|
c34d80c300 | ||
|
|
38c1193ace | ||
|
|
6b87cdb267 | ||
|
|
80cca8a487 | ||
|
|
6e79c169e0 | ||
|
|
7edbec3e45 | ||
|
|
8f8e0dc6e3 | ||
|
|
e2c20cfda4 | ||
|
|
ccb7b1619b | ||
|
|
d5c0dd4857 | ||
|
|
8c5cd5a460 | ||
|
|
9073a14c02 | ||
|
|
3513eeca19 | ||
|
|
34817b67d0 | ||
|
|
388e7ca77d | ||
|
|
d8091909b3 | ||
|
|
9ae416ab23 | ||
|
|
e373c71874 | ||
|
|
e2ac7bf715 | ||
|
|
3d4bd4b601 | ||
|
|
3b2aae1866 | ||
|
|
8a4f539173 | ||
|
|
89b98c197e | ||
|
|
000abae68b | ||
|
|
764f0ac8b6 | ||
|
|
04c7a18da3 | ||
|
|
9b37b98a93 | ||
|
|
32f622a714 | ||
|
|
bcaaa9e59a | ||
|
|
2298ec3030 | ||
|
|
afa2eb552a | ||
|
|
771058980a | ||
|
|
d789119634 | ||
|
|
48beb303be | ||
|
|
e30d279cad | ||
|
|
9c986a74a6 | ||
|
|
e02074941e | ||
|
|
56a68a4fef | ||
|
|
b3e662c988 | ||
|
|
a0fb06f0b9 | ||
|
|
906fa88119 | ||
|
|
eb40b9422f | ||
|
|
f6531b8434 | ||
|
|
e80235f0a8 | ||
|
|
c7b84ad1e5 | ||
|
|
44ae409081 | ||
|
|
d1318ff4fd | ||
|
|
169f3c6ea7 | ||
|
|
7a7507b5e4 | ||
|
|
d5c7522be5 | ||
|
|
f5b6a108a1 | ||
|
|
c4746e766b | ||
|
|
4fadd471e6 | ||
|
|
c397171b79 | ||
|
|
a20f9d9408 | ||
|
|
ebd49e70df | ||
|
|
b66145e514 | ||
|
|
be070c98e1 | ||
|
|
7eecf6c526 | ||
|
|
126346e1fe | ||
|
|
e7fe012f2f | ||
|
|
ec6ec59303 | ||
|
|
e53871b4dc | ||
|
|
93da8b6ac7 | ||
|
|
ad4cfc6d9e | ||
|
|
ad23763d6b | ||
|
|
7120516b13 | ||
|
|
ed34afc51b | ||
|
|
d4e5c0dba4 | ||
|
|
a50263c725 | ||
|
|
818a9bc9a2 | ||
|
|
e98497b78b | ||
|
|
60c2547cc0 | ||
|
|
934bf8ed87 | ||
|
|
05d3e66de9 | ||
|
|
d70ecf7e5f | ||
|
|
7403e39210 | ||
|
|
92382e600f | ||
|
|
b5a56e5239 | ||
|
|
b6f8ac211e | ||
|
|
63510fc89d | ||
|
|
91c387b0ca | ||
|
|
8fcb65d2a6 | ||
|
|
b2c3280623 | ||
|
|
cdf4d730d4 | ||
|
|
ca9bc007e5 | ||
|
|
f082ef43dc | ||
|
|
fb71cd4ef6 | ||
|
|
92dc66f338 | ||
|
|
e3553198d1 | ||
|
|
a4f57ec3c9 | ||
|
|
e5a7409b5e | ||
|
|
2a1108776e | ||
|
|
eeac031e70 | ||
|
|
7e77cb9723 | ||
|
|
1495214261 | ||
|
|
e75a3a07b9 | ||
|
|
f6bffb6982 | ||
|
|
a9e3a6ba1c | ||
|
|
0fb2026192 | ||
|
|
1b4caf3592 | ||
|
|
8dc4ac1732 | ||
|
|
c027ed9c1b | ||
|
|
b11dd5ff73 | ||
|
|
8bdb09ff7d | ||
|
|
b20a8e5321 | ||
|
|
1c9d3510cd | ||
|
|
ec246a62b9 | ||
|
|
f5652df9da | ||
|
|
b716374070 | ||
|
|
1a637f3c4f | ||
|
|
3b3ae4e7bf | ||
|
|
414bf84c68 | ||
|
|
b3ee989cfe | ||
|
|
eea128856c | ||
|
|
4517030473 | ||
|
|
c3773b7f26 | ||
|
|
fa2daa70b1 | ||
|
|
6adc5b3c7f | ||
|
|
a46548c119 | ||
|
|
3a5b65dec4 | ||
|
|
a463403bde | ||
|
|
75bc9d576c | ||
|
|
6a08536556 | ||
|
|
330263aae8 | ||
|
|
052a8656ba | ||
|
|
f1d0e3ce0d | ||
|
|
ed0d252f39 | ||
|
|
56126fb602 | ||
|
|
f58080b604 | ||
|
|
1a488eabc2 | ||
|
|
67cc83fa9a | ||
|
|
a91ef9edfe | ||
|
|
007609565a | ||
|
|
6319d2a264 | ||
|
|
f6afe7e7e9 | ||
|
|
f06201e050 | ||
|
|
661bc3a557 | ||
|
|
114749d5b7 | ||
|
|
4f8c1f3d8b | ||
|
|
14f9ae8f3b | ||
|
|
248b393288 | ||
|
|
6fe887df62 | ||
|
|
65c8980504 | ||
|
|
41d2bae380 | ||
|
|
912e76c97e | ||
|
|
f6e49c95f9 | ||
|
|
85aa5334b0 | ||
|
|
29e817475a | ||
|
|
e0caefc9df | ||
|
|
09eef9019f | ||
|
|
057be29943 | ||
|
|
4ec239e94f | ||
|
|
11b54a2ed3 | ||
|
|
33a92a81e4 | ||
|
|
5f11410677 | ||
|
|
6b076a7d4d | ||
|
|
0070a5fb10 | ||
|
|
be47f06217 | ||
|
|
16e395b90a | ||
|
|
ef1a186f9e | ||
|
|
5ade1cb4b6 | ||
|
|
8b90f2595d | ||
|
|
f98fa5c637 | ||
|
|
f21ba951a5 | ||
|
|
5bae5efb92 | ||
|
|
5568f241a3 | ||
|
|
19f66574c6 | ||
|
|
ae75f325c2 | ||
|
|
28cad0cf4a | ||
|
|
83d7e48999 | ||
|
|
e2d1ce2fb0 | ||
|
|
e6cc4e6c47 | ||
|
|
8c0c83776f | ||
|
|
ae888134a3 | ||
|
|
276cf7ce06 | ||
|
|
7132ee7405 | ||
|
|
817ebaad41 | ||
|
|
9e90e94ced | ||
|
|
fd1594520e | ||
|
|
9b6f8d3aff | ||
|
|
d84a919b33 | ||
|
|
fc745c1144 | ||
|
|
8f38b7c5aa | ||
|
|
0203889171 | ||
|
|
78c93418ca | ||
|
|
c1d303350f | ||
|
|
642d098238 | ||
|
|
532f1583b5 | ||
|
|
ab42cd766f | ||
|
|
050345f123 | ||
|
|
8e7ea022f7 | ||
|
|
c1c772e59f | ||
|
|
a1533d5ec5 | ||
|
|
1820c40c43 | ||
|
|
cab3fc7f4e | ||
|
|
3ff0e82fcc | ||
|
|
40f1f0193a | ||
|
|
2de46a18a5 | ||
|
|
cdbf189955 | ||
|
|
ff5ee2b709 | ||
|
|
d98c01a3b9 | ||
|
|
0996b3b1ad | ||
|
|
b617de3fd0 | ||
|
|
c9c305570b | ||
|
|
601bf75ceb | ||
|
|
fe1137d8ec | ||
|
|
69b34c45b2 | ||
|
|
f1cff2cd74 | ||
|
|
0f2976cac5 | ||
|
|
65570ac121 | ||
|
|
4acd7e4951 | ||
|
|
7b4bfa3af7 | ||
|
|
43a936a0d7 | ||
|
|
27479e856f | ||
|
|
567fa5950c | ||
|
|
b29ef61cc5 | ||
|
|
34c59eb237 | ||
|
|
650916f77c | ||
|
|
7ec008479a | ||
|
|
f02566c712 | ||
|
|
354a996edc | ||
|
|
31d6a0d8fd | ||
|
|
1de5a79855 | ||
|
|
b308f4a2b2 | ||
|
|
33ddee27ed | ||
|
|
6d7bf73aba | ||
|
|
a5fceaf476 | ||
|
|
1e2e7e9aee | ||
|
|
319a3a8d38 | ||
|
|
5e7bd06fcb | ||
|
|
b92037171e | ||
|
|
fc52426a4a | ||
|
|
3a5816cd18 | ||
|
|
7c74f8b755 | ||
|
|
a881877fd8 | ||
|
|
c044cc6021 | ||
|
|
009829c3ff | ||
|
|
4404849e76 | ||
|
|
374a65522d | ||
|
|
5da84663b1 | ||
|
|
0bbf2dff48 | ||
|
|
156965fd10 | ||
|
|
3da43b18c9 | ||
|
|
78314a5fc4 | ||
|
|
9792289ac1 | ||
|
|
6018068349 | ||
|
|
7f829bb3ca | ||
|
|
d3cf4fc7f4 | ||
|
|
e85e9cd464 | ||
|
|
a0a6a5bf19 | ||
|
|
750ebc1277 | ||
|
|
6f997a47a3 | ||
|
|
af1afd09ca | ||
|
|
74b4802b46 | ||
|
|
c31b803092 | ||
|
|
84b40804b5 | ||
|
|
3d6841c11e | ||
|
|
8adf58b717 | ||
|
|
44c07c2a00 | ||
|
|
9179dc8fec | ||
|
|
a9df4552b4 | ||
|
|
c5505bc017 | ||
|
|
3ceffbc4b2 | ||
|
|
5967105ad2 | ||
|
|
7eb6263dc1 | ||
|
|
249e52d3da | ||
|
|
6b5b9297ba | ||
|
|
8c15bb1fae | ||
|
|
d2d19a2490 | ||
|
|
5c269e2aaf | ||
|
|
30fb4e2108 | ||
|
|
24096b0272 | ||
|
|
cccc9f23a1 | ||
|
|
ff4353817f | ||
|
|
c2136fcea0 | ||
|
|
9898d3fbb2 | ||
|
|
5ec3524bde | ||
|
|
a471ad5e74 | ||
|
|
7b30e283ea | ||
|
|
8c62aadcfc | ||
|
|
e7f2685ed8 | ||
|
|
3b1ba08a6b | ||
|
|
17fe3db79f | ||
|
|
9ec9806b62 | ||
|
|
061ce1d2eb | ||
|
|
0f56baf4dc | ||
|
|
d6a17951d3 | ||
|
|
9f527ee6a5 | ||
|
|
2c9a37a4f1 | ||
|
|
e6e9df1773 | ||
|
|
755220cd4c | ||
|
|
7289134757 | ||
|
|
63362d32ee | ||
|
|
6cdcb36127 | ||
|
|
dfe5538132 | ||
|
|
42902405cd | ||
|
|
2516924a92 | ||
|
|
ce4165f69e | ||
|
|
648f9b8d35 | ||
|
|
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 |
@@ -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,4 @@
|
||||
version: 3
|
||||
|
||||
project:
|
||||
id: github.com/replicatedhq/troubleshoot
|
||||
@@ -0,0 +1,17 @@
|
||||
# Lines starting with '#' are comments.
|
||||
# Each line is a file pattern followed by one or more owners.
|
||||
|
||||
# More details are here: https://help.github.com/articles/about-codeowners/
|
||||
|
||||
# The '*' pattern is global owners.
|
||||
|
||||
# Order is important. The last matching pattern has the most precedence.
|
||||
# The folders are ordered as follows:
|
||||
|
||||
# In each subsection folders are ordered first by depth, then alphabetically.
|
||||
# This should make it easy to add new rules without breaking existing ones.
|
||||
|
||||
## RULES
|
||||
|
||||
* @replicatedhq/troubleshoot
|
||||
*.md @replicatedhq/cre
|
||||
@@ -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,28 @@
|
||||
---
|
||||
name: Feature Epic
|
||||
about: Create a tracking issue for a change that is larger than a single task
|
||||
labels: 'epic'
|
||||
|
||||
---
|
||||
|
||||
# Design Proposal
|
||||
|
||||
Link to the [proposal](https://github.com/replicatedhq/troubleshoot/tree/main/design/template.md)
|
||||
|
||||
# Definition of done
|
||||
|
||||
Describe what specific goals can measure if this overall task is considered completed. Things to consider are documentation, high level description of the feature working, and tests.
|
||||
|
||||
* [ ]
|
||||
|
||||
# Subtasks
|
||||
|
||||
Create a list of the smaller tasks to implement this design. Task can start as simple descriptions but should be converted to issues before work is started.
|
||||
|
||||
## Started
|
||||
|
||||
* [ ]
|
||||
|
||||
## Planned
|
||||
|
||||
* [ ]
|
||||
@@ -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 problem. Mention any 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,21 @@
|
||||
## Description, Motivation and Context
|
||||
|
||||
Please include a summary of the change or what problem it solves. Please also include relevant motivation and context.
|
||||
|
||||
<!--- If it relates to an open issue, please link to the issue here.
|
||||
e.g.
|
||||
Fixes: #414
|
||||
-->
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] New and existing tests pass locally with introduced changes.
|
||||
- [ ] Tests for the changes have been added (for bug fixes / features)
|
||||
- [ ] The commit message(s) are informative and highlight any breaking changes
|
||||
- [ ] Any documentation required has been added/updated. For changes to https://troubleshoot.sh/ create a PR [here](https://github.com/replicatedhq/troubleshoot.sh/pulls)
|
||||
|
||||
## Does this PR introduce a breaking change?
|
||||
- [ ] Yes
|
||||
- [ ] No
|
||||
|
||||
<!-- If this PR contains a breaking change, please describe the impact and migration path for existing applications below. -->
|
||||
@@ -0,0 +1,27 @@
|
||||
# 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"
|
||||
|
||||
# Maintain dependencies for GitHub Actions
|
||||
- package-ecosystem: "github-actions"
|
||||
# Workflow files stored in the
|
||||
# default location of `.github/workflows`
|
||||
directory: "/"
|
||||
labels:
|
||||
- "dependencies"
|
||||
- "github-actions"
|
||||
- "type::chore"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
@@ -0,0 +1,320 @@
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
branches:
|
||||
- main
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
tags:
|
||||
- "v*.*.*"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
fail_if_pull_request_is_draft:
|
||||
if: github.event.pull_request.draft == true
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Fails in order to indicate that pull request needs to be marked as ready to review and unit tests workflow needs to pass.
|
||||
run: exit 1
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: "1.19"
|
||||
|
||||
- name: setup env
|
||||
run: |
|
||||
echo "GOPATH=$(go env GOPATH)" >> $GITHUB_ENV
|
||||
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
|
||||
shell: bash
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- run: make test
|
||||
|
||||
test-integration:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: "1.19"
|
||||
|
||||
- name: setup env
|
||||
run: |
|
||||
echo "GOPATH=$(go env GOPATH)" >> $GITHUB_ENV
|
||||
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
|
||||
shell: bash
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- uses: replicatedhq/action-k3s@main
|
||||
id: k3s
|
||||
with:
|
||||
version: v1.23.6-k3s1
|
||||
|
||||
- run: make test-integration
|
||||
|
||||
ensure-schemas-are-generated:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: "1.19"
|
||||
- name: setup env
|
||||
run: |
|
||||
echo "GOPATH=$(go env GOPATH)" >> $GITHUB_ENV
|
||||
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
|
||||
shell: bash
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- run: make check-schemas
|
||||
|
||||
compile-preflight:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: "1.19"
|
||||
- name: setup env
|
||||
run: |
|
||||
echo "GOPATH=$(go env GOPATH)" >> $GITHUB_ENV
|
||||
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
|
||||
shell: bash
|
||||
- uses: actions/checkout@v3
|
||||
- run: make generate preflight
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: preflight
|
||||
path: bin/preflight
|
||||
|
||||
validate-preflight:
|
||||
runs-on: ubuntu-latest
|
||||
needs: compile-preflight
|
||||
steps:
|
||||
- uses: replicatedhq/action-k3s@main
|
||||
id: k3s
|
||||
with:
|
||||
version: v1.23.6-k3s1
|
||||
- name: Download preflight binary
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: preflight
|
||||
path: bin/
|
||||
- run: chmod +x bin/preflight
|
||||
- run: |
|
||||
set +e
|
||||
./bin/preflight --interactive=false --format=json https://preflight.replicated.com > result.json
|
||||
EXIT_CODE=$?
|
||||
cat result.json
|
||||
|
||||
EXIT_STATUS=0
|
||||
if [ $EXIT_CODE -ne 3 ]; then
|
||||
echo "Expected exit code of 3 (some checks failed), got $EXIT_CODE"
|
||||
EXIT_STATUS=1
|
||||
fi
|
||||
|
||||
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
|
||||
|
||||
validate-preflight-e2e:
|
||||
runs-on: ubuntu-latest
|
||||
needs: compile-preflight
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: replicatedhq/action-k3s@main
|
||||
id: k3s
|
||||
with:
|
||||
version: v1.23.6-k3s1
|
||||
- name: Download preflight binary
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: preflight
|
||||
path: bin/
|
||||
- run: chmod +x bin/preflight
|
||||
- run: make preflight-e2e-test
|
||||
|
||||
compile-supportbundle:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: "1.19"
|
||||
- name: setup env
|
||||
run: |
|
||||
echo "GOPATH=$(go env GOPATH)" >> $GITHUB_ENV
|
||||
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
|
||||
shell: bash
|
||||
- uses: actions/checkout@v3
|
||||
- run: make generate support-bundle
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: support-bundle
|
||||
path: bin/support-bundle
|
||||
|
||||
validate-supportbundle:
|
||||
runs-on: ubuntu-latest
|
||||
needs: compile-supportbundle
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: replicatedhq/action-k3s@main
|
||||
id: k3s
|
||||
with:
|
||||
version: v1.23.6-k3s1
|
||||
- name: Download support-bundle binary
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: support-bundle
|
||||
path: bin/
|
||||
- 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
|
||||
|
||||
validate-supportbundle-e2e:
|
||||
runs-on: ubuntu-latest
|
||||
needs: compile-supportbundle
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: replicatedhq/action-k3s@main
|
||||
id: k3s
|
||||
with:
|
||||
version: v1.23.6-k3s1
|
||||
- name: Download support bundle binary
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: support-bundle
|
||||
path: bin/
|
||||
- run: chmod +x bin/support-bundle
|
||||
- run: make support-bundle-e2e-test
|
||||
|
||||
compile-collect:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: "1.19"
|
||||
- name: setup env
|
||||
run: |
|
||||
echo "GOPATH=$(go env GOPATH)" >> $GITHUB_ENV
|
||||
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
|
||||
shell: bash
|
||||
- uses: actions/checkout@v3
|
||||
- run: make generate collect
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: collect
|
||||
path: bin/collect
|
||||
|
||||
goreleaser-test:
|
||||
runs-on: ubuntu-latest
|
||||
if: startsWith(github.ref, 'refs/tags/v') != true
|
||||
strategy:
|
||||
matrix:
|
||||
goarch: [amd64, arm64]
|
||||
goos: [darwin, linux, windows]
|
||||
include:
|
||||
- goarch: arm
|
||||
goos: linux
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: "1.19"
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v4
|
||||
with:
|
||||
version: "v0.183.0"
|
||||
args: build --rm-dist --snapshot --config deploy/.goreleaser.yaml --single-target
|
||||
env:
|
||||
GOARCH: ${{ matrix.goarch }}
|
||||
GOOS: ${{ matrix.goos }}
|
||||
|
||||
goreleaser:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- validate-preflight
|
||||
- validate-preflight-e2e
|
||||
- validate-supportbundle
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: azure/docker-login@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USER }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: "1.19"
|
||||
|
||||
- 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@v4
|
||||
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.46
|
||||
with:
|
||||
krew_template_file: deploy/krew/preflight.yaml
|
||||
- name: Update new support-bundle version in krew-index
|
||||
uses: rajatjindal/krew-release-bot@v0.0.46
|
||||
with:
|
||||
krew_template_file: deploy/krew/support-bundle.yaml
|
||||
@@ -0,0 +1,27 @@
|
||||
name: Scan vulnerabilities
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
scan_troubleshoot_files_systems:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Run Trivy vulnerability scanner in repo mode
|
||||
uses: aquasecurity/trivy-action@master
|
||||
with:
|
||||
scan-type: 'fs'
|
||||
ignore-unfixed: true
|
||||
format: 'sarif'
|
||||
output: 'trivy-results.sarif'
|
||||
severity: 'HIGH,CRITICAL'
|
||||
|
||||
- name: Upload Trivy scan results to GitHub Security tab
|
||||
uses: github/codeql-action/upload-sarif@v2
|
||||
with:
|
||||
sarif_file: 'trivy-results.sarif'
|
||||
@@ -0,0 +1,49 @@
|
||||
name: FOSSA license scan
|
||||
|
||||
on:
|
||||
pull_request_target: # this is safe as these scans do not execute provided code
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- go.sum
|
||||
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
fossa-scan-pr:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'pull_request'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: refs/pull/${{ github.event.number }}/merge
|
||||
- name: "Install FOSSA"
|
||||
uses: replicatedhq/action-fossa/install@main
|
||||
- name: "Run FOSSA Scan"
|
||||
uses: replicatedhq/action-fossa/scan@main
|
||||
with:
|
||||
api-key: ${{ secrets.FOSSA_API_KEY }}
|
||||
diff: true
|
||||
diff-ref: ${{ github.event.pull_request.base.sha }}
|
||||
debug: true
|
||||
|
||||
fossa-scan-merge:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 2
|
||||
- id: previous
|
||||
run: echo "sha=$(git rev-parse HEAD~1)" >> "${GITHUB_OUTPUT}"
|
||||
- name: "Install FOSSA"
|
||||
uses: replicatedhq/action-fossa/install@main
|
||||
- name: "Run FOSSA Scan"
|
||||
uses: replicatedhq/action-fossa/scan@main
|
||||
with:
|
||||
api-key: ${{ secrets.FOSSA_API_KEY }}
|
||||
diff: true
|
||||
diff-ref: ${{ steps.previous.outputs.sha }}
|
||||
debug: true
|
||||
+15
@@ -6,6 +6,7 @@
|
||||
*.so
|
||||
*.dylib
|
||||
bin
|
||||
.DS_Store
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
@@ -27,3 +28,17 @@ vendor
|
||||
*~
|
||||
|
||||
dist
|
||||
try.sh
|
||||
|
||||
.vscode/
|
||||
workspace.*
|
||||
|
||||
cosign.key
|
||||
sbom/
|
||||
|
||||
# Ignore local pre-commit config
|
||||
.pre-commit-config.yaml
|
||||
|
||||
# Ignore generated support bundles
|
||||
*.tar.gz
|
||||
!testdata/supportbundle/*.tar.gz
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
# https://golangci-lint.run/usage/configuration/#config-file
|
||||
|
||||
run:
|
||||
allow-parallel-runners: true
|
||||
timeout: 30s
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- gocritic
|
||||
- gocyclo
|
||||
- gofmt
|
||||
- gosec
|
||||
@@ -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.
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
# 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/main/CODE_OF_CONDUCT.md) prior to contributing. There are a number of ways to participate in Troubleshoot as outlined below:
|
||||
|
||||
# Community
|
||||
|
||||
For discussions about developing Troubleshoot, there's an [#app-troubleshoot channel in Kubernetes Slack](https://kubernetes.slack.com/channels/app-troubleshoot), plus IRC using [Libera](ircs://irc.libera.chat:6697/#troubleshoot) (#troubleshoot).
|
||||
|
||||
There are [community meetings](https://calendar.google.com/calendar/u/0?cid=Y19mMGx1aGhiZGtscGllOGo5dWpicXMwNnN1a0Bncm91cC5jYWxlbmRhci5nb29nbGUuY29t) on a regular basis, with a shared calendar and [public notes](https://hackmd.io/yZbotEHdTg6TfRZBzb8Tcg)
|
||||
|
||||
## 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.
|
||||
|
||||
## Design Principles
|
||||
|
||||
When implementing a new feature please review the [design principles](./docs/design/design-principles.md) to help guide the approach.
|
||||
|
||||
## Development Environment
|
||||
|
||||
To get started we recommend:
|
||||
|
||||
1. Go (v1.19 or later)
|
||||
2. A Kubernetes cluster (we recommend <https://k3d.io/>. This requires Docker v20.10.5 or later)
|
||||
3. Fork and clone the repo to $GOPATH/src/github.com/replicatedhq/
|
||||
4. Run `make support-bundle preflight` to generate binaries
|
||||
5. Run `make run-troubleshoot` to generate a support bundle with the `sample-troubleshoot.yaml` in the root of the repo
|
||||
|
||||
> Note: recent versions of Go support easy cross-compilation. For example, to cross-compile a Linux binary from MacOS:
|
||||
> `GOOS=linux GOARCH=amd64 make support-bundle preflight`
|
||||
|
||||
6. Install [golangci-lint] linter and run `make lint` to execute additional code linters.
|
||||
|
||||
### Testing
|
||||
|
||||
To run the tests locally run the following:
|
||||
|
||||
```bash
|
||||
make test
|
||||
make test RUN=TestClusterResources_Merge
|
||||
```
|
||||
|
||||
Additionally, e2e tests can be run with:
|
||||
|
||||
```bash
|
||||
make support-bundle preflight e2e-test
|
||||
```
|
||||
|
||||
A running Kubernetes cluster as well as `jq` are required to run e2e tests.
|
||||
|
||||
### Profiling
|
||||
|
||||
You are able to collect CPU & memory runtime properties and store the data for analysis in a file. To do so, pass in the file paths using `--cpuprofile` and `--memprofile` flags in the CLI. Once you have your data collected, you can analyse it using [pprof visualization tool](https://github.com/google/pprof/blob/main/doc/README.md). Here is how
|
||||
|
||||
Run support bundle and with CPU & memory profile flags
|
||||
```sh
|
||||
./bin/support-bundle examples/support-bundle/sample-supportbundle.yaml --cpuprofile=cpu.prof --memprofile=mem.prof
|
||||
```
|
||||
|
||||
Visualize using [pprof](https://github.com/google/pprof/blob/main/doc/README.md)
|
||||
```sh
|
||||
go tool pprof -http=":8000" cpu.prof
|
||||
|
||||
go tool pprof -http=":8001" mem.prof
|
||||
```
|
||||
|
||||
More on profiling please visit https://go.dev/doc/diagnostics#profiling
|
||||
|
||||
## Contribution workflow
|
||||
|
||||
This is a rough outline of how to prepare a contribution:
|
||||
|
||||
- Create a fork of this repo.
|
||||
- Create a topic branch from where you want to base your work (branched from `main` is a safe choice).
|
||||
- Make commits of logical units.
|
||||
- When your changes are ready to merge, squash your history to 1 commit.
|
||||
- For example, if you want to squash your last 3 commits and write a new commit message:
|
||||
```
|
||||
git reset --soft HEAD~3 &&
|
||||
git commit
|
||||
```
|
||||
|
||||
- If you want to keep the previous commit messages and concatenate them all into a new commit, you can do something like this instead:
|
||||
```
|
||||
git reset --soft HEAD~3 &&
|
||||
git commit --edit -m"$(git log --format=%B --reverse HEAD..HEAD@{1})"
|
||||
```
|
||||
- Push your changes to a topic branch in your fork of the repository.
|
||||
- Submit a pull request to the original repository. It will be reviewed in a timely manner.
|
||||
|
||||
### Pull Requests
|
||||
|
||||
A pull request should address a single issue, feature or bug. For example, lets say you've written code that fixes two issues. That's great! However, you should submit two small pull requests, one for each issue as opposed to combining them into a single larger pull request. In general the size of the pull request should be kept small in order to make it easy for a reviewer to understand, and to minimize risks from integrating many changes at the same time. For example, if you are working on a large feature you should break it into several smaller PRs by implementing the feature as changes to several packages and submitting a separate pull request for each one. Squash commit history when preparing your PR so it merges as 1 commit.
|
||||
|
||||
Code submitted in pull requests must be properly documented, formatted and tested in order to be approved and merged. The following guidelines describe the things a reviewer will look for when they evaluate your pull request. Here's a tip. If your reviewer doesn't understand what the code is doing, they won't approve the pull request. Strive to make code clear and well documented. If possible, request a reviewer that has some context on the PR.
|
||||
|
||||
### Commit messages
|
||||
|
||||
Commit messages should follow the general guidelines:
|
||||
|
||||
- Breaking changes should be highlighted in the heading of the commit message.
|
||||
- Commits should be clear about their purpose (and a single commit per thing that changed)
|
||||
- Messages should be descriptive:
|
||||
- First line, 50 chars or less, as a heading/title that people can find
|
||||
- Then a paragraph explaining things
|
||||
- Consider a footer with links to which bugs they fix etc, bearing in mind that Github does some of this magic already
|
||||
@@ -1,116 +1,222 @@
|
||||
|
||||
# Image URL to use all building/pushing image targets
|
||||
IMG ?= controller:latest
|
||||
export GO111MODULE=on
|
||||
export GOPROXY=https://proxy.golang.org
|
||||
|
||||
all: test manager
|
||||
SHELL := 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"`
|
||||
RUN?=""
|
||||
|
||||
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
|
||||
BUILDPATHS = ./pkg/... ./cmd/... ./internal/...
|
||||
TESTFLAGS ?= -v -coverprofile cover.out
|
||||
|
||||
all: test support-bundle preflight collect analyze
|
||||
|
||||
.PHONY: ffi
|
||||
ffi: fmt vet
|
||||
go build -o bin/troubleshoot.so -buildmode=c-shared ffi/main.go
|
||||
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: test
|
||||
test: generate fmt vet
|
||||
if [ -n $(RUN) ]; then \
|
||||
go test ${BUILDFLAGS} ${BUILDPATHS} ${TESTFLAGS} -run $(RUN); \
|
||||
else \
|
||||
go test ${BUILDFLAGS} ${BUILDPATHS} ${TESTFLAGS}; \
|
||||
fi
|
||||
|
||||
.PHONY: manager
|
||||
manager: generate fmt vet
|
||||
go build -o bin/manager github.com/replicatedhq/troubleshoot/cmd/manager
|
||||
# Go tests that require a K8s instance
|
||||
# TODOLATER: merge with test, so we get unified coverage reports? it'll add 21~sec to the test job though...
|
||||
.PHONY: test-integration
|
||||
test-integration:
|
||||
go test -v --tags "integration exclude_graphdriver_devicemapper exclude_graphdriver_btrfs" ${BUILDPATHS}
|
||||
|
||||
.PHONY: preflight-e2e-test
|
||||
preflight-e2e-test:
|
||||
./test/validate-preflight-e2e.sh
|
||||
|
||||
.PHONY: support-bundle-e2e-test
|
||||
support-bundle-e2e-test:
|
||||
./test/validate-support-bundle-e2e.sh
|
||||
|
||||
.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
|
||||
support-bundle:
|
||||
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
|
||||
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: analyze
|
||||
analyze:
|
||||
go build ${BUILDFLAGS} ${LDFLAGS} -o bin/analyze github.com/replicatedhq/troubleshoot/cmd/analyze
|
||||
|
||||
.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: collect
|
||||
collect:
|
||||
go build ${BUILDFLAGS} ${LDFLAGS} -o bin/collect github.com/replicatedhq/troubleshoot/cmd/collect
|
||||
|
||||
.PHONY: fmt
|
||||
fmt:
|
||||
go fmt ./pkg/... ./cmd/...
|
||||
go fmt ${BUILDPATHS}
|
||||
|
||||
.PHONY: vet
|
||||
vet:
|
||||
go vet ./pkg/... ./cmd/...
|
||||
go vet ${BUILDFLAGS} ${BUILDPATHS}
|
||||
|
||||
.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-base=./../../../ \
|
||||
--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
|
||||
|
||||
check-schemas: generate schemas
|
||||
@if [ -n "$(shell git status --short)" ]; then \
|
||||
echo -e "\033[31mThe git repo is dirty :( Ensure all generated files are committed e.g CRD schema files\033[0;m"; \
|
||||
git status --short; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
.PHONY: schemas
|
||||
schemas: fmt vet openapischema
|
||||
go build ${LDFLAGS} -o bin/schemagen github.com/replicatedhq/troubleshoot/cmd/schemagen
|
||||
./bin/schemagen --output-dir ./schemas
|
||||
|
||||
.PHONY: docs
|
||||
docs: fmt vet
|
||||
go build ${LDFLAGS} -o bin/docsgen github.com/replicatedhq/troubleshoot/cmd/docsgen
|
||||
./bin/docsgen
|
||||
|
||||
# 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.11.2
|
||||
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
|
||||
ifeq (, $(shell which client-gen 2>/dev/null))
|
||||
go install k8s.io/code-generator/cmd/client-gen@v0.26.1
|
||||
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
|
||||
|
||||
.PHONY: scan
|
||||
scan:
|
||||
trivy fs \
|
||||
--security-checks vuln \
|
||||
--exit-code=1 \
|
||||
--severity="HIGH,CRITICAL" \
|
||||
--ignore-unfixed \
|
||||
./
|
||||
|
||||
.PHONY: lint
|
||||
lint: fmt vet
|
||||
golangci-lint run --new -c .golangci.yaml ${BUILDPATHS}
|
||||
|
||||
.PHONY: lint-and-fix
|
||||
lint-and-fix: fmt vet
|
||||
golangci-lint run --new --fix -c .golangci.yaml ${BUILDPATHS}
|
||||
|
||||
@@ -1,23 +1,67 @@
|
||||
# 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).
|
||||
|
||||
To know more about troubleshoot, please visit: https://troubleshoot.sh/
|
||||
|
||||
## 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, where https://preflight.replicated.com provides an **example** preflight spec:
|
||||
|
||||
```
|
||||
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/).
|
||||
**NOTE** this is an example. Do **not** use to validate real scenarios.
|
||||
|
||||
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, where https://support-bundle.replicated.com provides an **example** support bundle spec:
|
||||
|
||||
```
|
||||
kubectl support-bundle https://support-bundle.replicated.com
|
||||
```
|
||||
|
||||
**NOTE** this is an example. Do **not** use to validate real scenarios.
|
||||
|
||||
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/).
|
||||
|
||||
And see our other tool [sbctl](https://github.com/replicatedhq/sbctl) that makes it easier to interact with support bundles using `kubectl` commands you already know
|
||||
|
||||
# Community
|
||||
|
||||
For questions about using Troubleshoot, how to contribute and engaging with the project in any other way, please refer to the following resources and channels.
|
||||
|
||||
- [Replicated Community](https://help.replicated.com/community) forum
|
||||
- [#app-troubleshoot channel in Kubernetes Slack](https://kubernetes.slack.com/channels/app-troubleshoot)
|
||||
- [#Community meetings calendar](https://calendar.google.com/calendar/u/0?cid=Y19mMGx1aGhiZGtscGllOGo5dWpicXMwNnN1a0Bncm91cC5jYWxlbmRhci5nb29nbGUuY29t). This happen monthly but dates may change and would be kept upto date in the calendar.
|
||||
|
||||
# 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.
|
||||
```
|
||||
$ cosign verify-blob --key key.pub --signature troubleshoot-sbom.tgz.sig troubleshoot-sbom.tgz
|
||||
Verified OK
|
||||
```
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/replicatedhq/troubleshoot/cmd/util"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/logger"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
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) {
|
||||
v := viper.GetViper()
|
||||
v.BindPFlags(cmd.Flags())
|
||||
|
||||
logger.SetupLogger(v)
|
||||
|
||||
if err := util.StartProfiling(); err != nil {
|
||||
klog.Errorf("Failed to start profiling: %v", err)
|
||||
}
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
v := viper.GetViper()
|
||||
|
||||
return runAnalyzers(v, args[0])
|
||||
},
|
||||
PostRun: func(cmd *cobra.Command, args []string) {
|
||||
if err := util.StopProfiling(); err != nil {
|
||||
klog.Errorf("Failed to stop profiling: %v", err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
cobra.OnInitialize(initConfig)
|
||||
|
||||
cmd.Flags().String("analyzers", "", "filename or url of the analyzers to use")
|
||||
cmd.Flags().Bool("debug", false, "enable debug logging")
|
||||
|
||||
viper.BindPFlags(cmd.Flags())
|
||||
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
|
||||
|
||||
// Initialize klog flags
|
||||
logger.InitKlogFlags(cmd)
|
||||
|
||||
k8sutil.AddFlags(cmd.Flags())
|
||||
|
||||
// CPU and memory profiling flags
|
||||
util.AddProfilingFlags(cmd)
|
||||
|
||||
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,85 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/replicatedhq/troubleshoot/cmd/util"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/logger"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
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) {
|
||||
v := viper.GetViper()
|
||||
v.BindPFlags(cmd.Flags())
|
||||
|
||||
logger.SetupLogger(v)
|
||||
|
||||
if err := util.StartProfiling(); err != nil {
|
||||
klog.Errorf("Failed to start profiling: %v", err)
|
||||
}
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
v := viper.GetViper()
|
||||
|
||||
return runCollect(v, args[0])
|
||||
},
|
||||
PostRun: func(cmd *cobra.Command, args []string) {
|
||||
if err := util.StopProfiling(); err != nil {
|
||||
klog.Errorf("Failed to stop profiling: %v", err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
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")
|
||||
cmd.Flags().Bool("debug", false, "enable debug logging")
|
||||
|
||||
// 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())
|
||||
|
||||
// Initialize klog flags
|
||||
logger.InitKlogFlags(cmd)
|
||||
|
||||
// CPU and memory profiling flags
|
||||
util.AddProfilingFlags(cmd)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func InitAndExecute() {
|
||||
if err := RootCmd().Execute(); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func initConfig() {
|
||||
viper.SetEnvPrefix("TROUBLESHOOT")
|
||||
viper.AutomaticEnv()
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
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
|
||||
|
||||
redactors, err := supportbundle.GetRedactorsFromURIs(v.GetStringSlice("redactors"))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get redactors")
|
||||
}
|
||||
|
||||
additionalRedactors := &troubleshootv1beta2.Redactor{
|
||||
Spec: troubleshootv1beta2.RedactorSpec{
|
||||
Redactors: 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()
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
preflightcli "github.com/replicatedhq/troubleshoot/cmd/preflight/cli"
|
||||
troubleshootcli "github.com/replicatedhq/troubleshoot/cmd/troubleshoot/cli"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/spf13/cobra/doc"
|
||||
)
|
||||
|
||||
func RootCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "docsgen",
|
||||
Short: "Generate markdown docs for the commands in this project",
|
||||
}
|
||||
preflight := preflightcli.RootCmd()
|
||||
troubleshoot := troubleshootcli.RootCmd()
|
||||
commands := []*cobra.Command{preflight, troubleshoot}
|
||||
|
||||
for _, command := range commands {
|
||||
err := doc.GenMarkdownTree(command, "./docs")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func InitAndExecute() {
|
||||
if err := RootCmd().Execute(); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/replicatedhq/troubleshoot/cmd/docsgen/cli"
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/replicatedhq/troubleshoot/pkg/oci"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func OciFetchCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "oci-fetch [URI]",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Short: "Fetch a preflight from an OCI registry and print it to standard out",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
uri := args[0]
|
||||
data, err := oci.PullPreflightFromOCI(uri)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(data))
|
||||
return nil
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
@@ -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(KubernetesConfigFlags)
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
+64
-37
@@ -1,63 +1,103 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
"github.com/replicatedhq/troubleshoot/cmd/util"
|
||||
"github.com/replicatedhq/troubleshoot/internal/traces"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/constants"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/logger"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/preflight"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/types"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
)
|
||||
|
||||
var (
|
||||
KubernetesConfigFlags *genericclioptions.ConfigFlags
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
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,
|
||||
SilenceErrors: true,
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
viper.BindPFlags(cmd.Flags())
|
||||
v := viper.GetViper()
|
||||
v.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
|
||||
v.BindPFlags(cmd.Flags())
|
||||
|
||||
logger.SetupLogger(v)
|
||||
|
||||
if err := util.StartProfiling(); err != nil {
|
||||
klog.Errorf("Failed to start profiling: %v", err)
|
||||
}
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
v := viper.GetViper()
|
||||
|
||||
if len(args) == 0 {
|
||||
return runPreflightsCRD(v)
|
||||
closer, err := traces.ConfigureTracing("preflight")
|
||||
if err != nil {
|
||||
// Do not fail running preflights if tracing fails
|
||||
klog.Errorf("Failed to initialize open tracing provider: %v", err)
|
||||
} else {
|
||||
defer closer()
|
||||
}
|
||||
|
||||
return runPreflightsNoCRD(v, args[0])
|
||||
err = preflight.RunPreflights(v.GetBool("interactive"), v.GetString("output"), v.GetString("format"), args)
|
||||
if v.GetBool("debug") || v.IsSet("v") {
|
||||
fmt.Printf("\n%s", traces.GetExporterInstance().GetSummary())
|
||||
}
|
||||
|
||||
return err
|
||||
},
|
||||
PostRun: func(cmd *cobra.Command, args []string) {
|
||||
if err := util.StopProfiling(); err != nil {
|
||||
klog.Errorf("Failed to stop profiling: %v", err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
cobra.OnInitialize(initConfig)
|
||||
|
||||
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("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.AddCommand(VersionCmd())
|
||||
cmd.AddCommand(OciFetchCmd())
|
||||
preflight.AddFlags(cmd.PersistentFlags())
|
||||
|
||||
cmd.Flags().String("serviceaccount", "", "name of the service account to use. if not provided, one will be created")
|
||||
k8sutil.AddFlags(cmd.Flags())
|
||||
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
|
||||
// Initialize klog flags
|
||||
logger.InitKlogFlags(cmd)
|
||||
|
||||
KubernetesConfigFlags = genericclioptions.NewConfigFlags(false)
|
||||
KubernetesConfigFlags.AddFlags(cmd.Flags())
|
||||
// CPU and memory profiling flags
|
||||
util.AddProfilingFlags(cmd)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func InitAndExecute() {
|
||||
if err := RootCmd().Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
cmd := RootCmd()
|
||||
err := cmd.Execute()
|
||||
|
||||
if err != nil {
|
||||
var exitErr types.ExitError
|
||||
if errors.As(err, &exitErr) {
|
||||
// We need to do this, there's situations where we need the non-zero exit code (which comes as part of the custom error struct)
|
||||
// but there's no actual error, just an exit code.
|
||||
// If there's also an error to output (eg. invalid format etc) then print it as well
|
||||
if exitErr.ExitStatus() != constants.EXIT_CODE_FAIL && exitErr.ExitStatus() != constants.EXIT_CODE_WARN {
|
||||
cmd.PrintErrln("Error:", err.Error())
|
||||
}
|
||||
|
||||
os.Exit(exitErr.ExitStatus())
|
||||
}
|
||||
|
||||
// Fallback, should almost never be used (the above Exit() should handle almost all situations
|
||||
cmd.PrintErrln("Error:", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -66,16 +106,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)
|
||||
}
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
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(KubernetesConfigFlags)
|
||||
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)
|
||||
}
|
||||
|
||||
config, err := KubernetesConfigFlags.ToRESTConfig()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to convert kube flags to rest config")
|
||||
}
|
||||
|
||||
stopChan, err := k8sutil.PortForward(config, 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,201 +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)
|
||||
|
||||
config, err := KubernetesConfigFlags.ToRESTConfig()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to convert kube flags to rest config")
|
||||
}
|
||||
|
||||
// Run preflights collectors synchronously
|
||||
for _, desiredCollector := range desiredCollectors {
|
||||
collector := collect.Collector{
|
||||
Redact: true,
|
||||
Collect: desiredCollector,
|
||||
ClientConfig: config,
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
analyzerunner "github.com/replicatedhq/troubleshoot/pkg/analyze"
|
||||
)
|
||||
|
||||
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{},
|
||||
}
|
||||
for _, analyzeResult := range analyzeResults {
|
||||
uploadPreflightResult := &UploadPreflightResult{
|
||||
IsFail: analyzeResult.IsFail,
|
||||
IsWarn: analyzeResult.IsWarn,
|
||||
IsPass: analyzeResult.IsPass,
|
||||
Title: analyzeResult.Title,
|
||||
Message: analyzeResult.Message,
|
||||
URI: analyzeResult.URI,
|
||||
}
|
||||
|
||||
uploadPreflightResults.Results = append(uploadPreflightResults.Results, uploadPreflightResult)
|
||||
}
|
||||
|
||||
b, err := json.Marshal(uploadPreflightResults)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", uri, bytes.NewBuffer(b))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
client := http.DefaultClient
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode > 290 {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootclientv1beta1 "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta1"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
)
|
||||
|
||||
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(configFlags *genericclioptions.ConfigFlags) (*troubleshootclientv1beta1.TroubleshootV1beta1Client, error) {
|
||||
config, err := configFlags.ToRESTConfig()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to convert kube flags to rest config")
|
||||
}
|
||||
|
||||
troubleshootClient, err := troubleshootclientv1beta1.NewForConfig(config)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create troubleshoot client")
|
||||
}
|
||||
|
||||
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,13 +1,15 @@
|
||||
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"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"gopkg.in/yaml.v2"
|
||||
@@ -15,20 +17,23 @@ 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("output", cmd.Flags().Lookup("output"))
|
||||
viper.BindPFlag("quiet", cmd.Flags().Lookup("quiet"))
|
||||
viper.BindPFlags(cmd.Flags())
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
v := viper.GetViper()
|
||||
|
||||
logger.SetQuiet(v.GetBool("quiet"))
|
||||
specPath := args[0]
|
||||
analyzerSpec, err := downloadAnalyzerSpec(specPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result, err := analyzer.DownloadAndAnalyze(context.TODO(), v.GetString("url"))
|
||||
result, err := analyzer.DownloadAndAnalyze(v.GetString("bundle"), analyzerSpec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -60,14 +65,52 @@ 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")
|
||||
cmd.Flags().Bool("quiet", false, "enable/disable error messaging and only show parseable output")
|
||||
|
||||
viper.BindPFlags(cmd.Flags())
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
+47
-59
@@ -5,27 +5,27 @@ import (
|
||||
"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"
|
||||
)
|
||||
|
||||
var (
|
||||
selectedResult = 0
|
||||
table = widgets.NewTable()
|
||||
isShowingSaved = false
|
||||
)
|
||||
|
||||
func showInteractiveResults(preflightName string, analyzeResults []*analyzerunner.AnalyzeResult) error {
|
||||
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(preflightName, analyzeResults)
|
||||
drawUI(supportBundleName, analyzeResults)
|
||||
|
||||
uiEvents := ui.PollEvents()
|
||||
for {
|
||||
@@ -38,12 +38,12 @@ func showInteractiveResults(preflightName string, analyzeResults []*analyzerunne
|
||||
if isShowingSaved == true {
|
||||
isShowingSaved = false
|
||||
ui.Clear()
|
||||
drawUI(preflightName, analyzeResults)
|
||||
drawUI(supportBundleName, analyzeResults)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
case "s":
|
||||
filename, err := save(preflightName, analyzeResults)
|
||||
filename, err := save(analyzeResults)
|
||||
if err != nil {
|
||||
// show
|
||||
} else {
|
||||
@@ -52,49 +52,53 @@ func showInteractiveResults(preflightName string, analyzeResults []*analyzerunne
|
||||
time.Sleep(time.Second * 5)
|
||||
isShowingSaved = false
|
||||
ui.Clear()
|
||||
drawUI(preflightName, analyzeResults)
|
||||
drawUI(supportBundleName, analyzeResults)
|
||||
}()
|
||||
}
|
||||
case "<Resize>":
|
||||
ui.Clear()
|
||||
drawUI(preflightName, analyzeResults)
|
||||
drawUI(supportBundleName, analyzeResults)
|
||||
case "<Down>":
|
||||
if selectedResult < len(analyzeResults)-1 {
|
||||
selectedResult++
|
||||
} else {
|
||||
selectedResult = 0
|
||||
table.SelectedRow = 0
|
||||
}
|
||||
table.ScrollDown()
|
||||
ui.Clear()
|
||||
drawUI(preflightName, analyzeResults)
|
||||
drawUI(supportBundleName, analyzeResults)
|
||||
case "<Up>":
|
||||
if selectedResult > 0 {
|
||||
selectedResult--
|
||||
} else {
|
||||
selectedResult = len(analyzeResults) - 1
|
||||
table.SelectedRow = len(analyzeResults)
|
||||
}
|
||||
table.ScrollUp()
|
||||
ui.Clear()
|
||||
drawUI(preflightName, analyzeResults)
|
||||
drawUI(supportBundleName, analyzeResults)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func drawUI(preflightName string, analyzeResults []*analyzerunner.AnalyzeResult) {
|
||||
func drawUI(supportBundleName string, analyzeResults []*analyzerunner.AnalyzeResult) {
|
||||
drawGrid(analyzeResults)
|
||||
drawHeader(preflightName)
|
||||
drawHeader(supportBundleName)
|
||||
drawFooter()
|
||||
}
|
||||
|
||||
func drawGrid(analyzeResults []*analyzerunner.AnalyzeResult) {
|
||||
drawPreflightTable(analyzeResults)
|
||||
drawAnalyzersTable(analyzeResults)
|
||||
drawDetails(analyzeResults[selectedResult])
|
||||
}
|
||||
|
||||
func drawHeader(preflightName string) {
|
||||
func drawHeader(supportBundleName string) {
|
||||
termWidth, _ := ui.TerminalDimensions()
|
||||
|
||||
title := widgets.NewParagraph()
|
||||
title.Text = fmt.Sprintf("%s Preflight Checks", appName(preflightName))
|
||||
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
|
||||
@@ -123,10 +127,9 @@ func drawFooter() {
|
||||
ui.Render(instructions)
|
||||
}
|
||||
|
||||
func drawPreflightTable(analyzeResults []*analyzerunner.AnalyzeResult) {
|
||||
func drawAnalyzersTable(analyzeResults []*analyzerunner.AnalyzeResult) {
|
||||
termWidth, termHeight := ui.TerminalDimensions()
|
||||
|
||||
table := widgets.NewTable()
|
||||
table.SetRect(0, 3, termWidth/2, termHeight-6)
|
||||
table.FillRow = true
|
||||
table.Border = true
|
||||
@@ -201,10 +204,11 @@ func drawDetails(analysisResult *analyzerunner.AnalyzeResult) {
|
||||
uri := widgets.NewParagraph()
|
||||
uri.Text = fmt.Sprintf("For more information: %s", analysisResult.URI)
|
||||
uri.Border = false
|
||||
height = estimateNumberOfLines(uri.Text, termWidth/2)
|
||||
// For long urls that lead to wrapping text, make the rectangle bigger by
|
||||
// increasing the calculated height by 2
|
||||
height = estimateNumberOfLines(uri.Text, termWidth/2) + 2
|
||||
uri.SetRect(termWidth/2, currentTop, termWidth, currentTop+height)
|
||||
ui.Render(uri)
|
||||
currentTop = currentTop + height + 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,14 +217,33 @@ 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 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 := fmt.Sprintf("%s Preflight Checks\n\n", appName(preflightName))
|
||||
results := ""
|
||||
for _, analyzeResult := range analyzeResults {
|
||||
result := ""
|
||||
|
||||
@@ -250,38 +273,3 @@ func save(preflightName string, analyzeResults []*analyzerunner.AnalyzeResult) (
|
||||
|
||||
return filename, nil
|
||||
}
|
||||
|
||||
func showSaved(filename string) {
|
||||
termWidth, termHeight := ui.TerminalDimensions()
|
||||
|
||||
savedMessage := widgets.NewParagraph()
|
||||
savedMessage.Text = fmt.Sprintf("Preflight 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 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,154 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/mholt/archiver"
|
||||
"github.com/pkg/errors"
|
||||
"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(KubernetesConfigFlags)
|
||||
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))
|
||||
}
|
||||
|
||||
filename, err := findFileName("support-bundle", "tar.gz")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "find file name")
|
||||
}
|
||||
|
||||
if err := tarGz.Archive(paths, filename); 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
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
analyzer "github.com/replicatedhq/troubleshoot/pkg/analyze"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/supportbundle"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func Redact() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "redact [urls...]",
|
||||
Args: cobra.MinimumNArgs(1), // TODO
|
||||
Short: "Redact information from a generated support bundle archive",
|
||||
Long: `Redaction is the process of masking sensitive information from collected data in a support bundle.
|
||||
This is done using rules defined in the list of redactor manifests provided in the [urls...] command line
|
||||
argument. Default built in redactors will also be run, but these would have been run when the support
|
||||
bundle was generated. After redaction, the support bundle is archived once more. The resulting file will
|
||||
be stored in the current directory in the path provided by the --output flag.
|
||||
|
||||
The [urls...] argument is a list of either oci://.., http://.., https://.. or local paths to yaml files.
|
||||
|
||||
For more information on redactors visit https://troubleshoot.sh/docs/redact/
|
||||
`,
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
return viper.BindPFlags(cmd.Flags())
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
v := viper.GetViper()
|
||||
|
||||
// 1. Decode redactors from provided URLs
|
||||
redactors, err := supportbundle.GetRedactorsFromURIs(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 2. Download the bundle and extract it
|
||||
tmpDir, bundleDir, err := analyzer.DownloadAndExtractSupportBundle(v.GetString("bundle"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
// 3. Represent bundle as a CollectorResult
|
||||
collectorResult, err := collect.CollectorResultFromBundle(bundleDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 4. Perform redaction on the bundle
|
||||
err = collect.RedactResult(bundleDir, collectorResult, redactors)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to redact support bundle")
|
||||
}
|
||||
|
||||
// 5. Compress the bundle once more after redacting
|
||||
output := v.GetString("output")
|
||||
if output == "" {
|
||||
output = fmt.Sprintf("redacted-support-bundle-%s.tar.gz", time.Now().Format("2006-01-02T15_04_05"))
|
||||
}
|
||||
err = collectorResult.ArchiveSupportBundle(bundleDir, output)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create support bundle archive")
|
||||
}
|
||||
fmt.Println("Redacted support bundle:", output)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().String("bundle", "", "file path of the support bundle archive to redact")
|
||||
cmd.MarkFlagRequired("bundle")
|
||||
cmd.Flags().BoolP("quiet", "q", false, "enable/disable error messaging and only show parseable output")
|
||||
cmd.Flags().StringP("output", "o", "", "file path of where to save the redacted support bundle archive (default \"redacted-support-bundle-YYYY-MM-DDTHH_MM_SS.tar.gz\")")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
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"))
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
v := viper.GetViper()
|
||||
|
||||
troubleshootClient, err := createTroubleshootK8sClient(KubernetesConfigFlags)
|
||||
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)
|
||||
|
||||
config, err := KubernetesConfigFlags.ToRESTConfig()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to convert kube flags to rest config")
|
||||
}
|
||||
|
||||
stopChan, err := k8sutil.PortForward(config, 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")
|
||||
|
||||
viper.BindPFlags(cmd.Flags())
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -5,63 +5,97 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
"github.com/replicatedhq/troubleshoot/cmd/util"
|
||||
"github.com/replicatedhq/troubleshoot/internal/traces"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/logger"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
)
|
||||
|
||||
var (
|
||||
KubernetesConfigFlags *genericclioptions.ConfigFlags
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
func RootCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "troubleshoot [url]",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Short: "Generate and manage support bundles",
|
||||
Use: "support-bundle [urls...]",
|
||||
Args: cobra.MinimumNArgs(0),
|
||||
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.`,
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
viper.BindPFlags(cmd.Flags())
|
||||
from a server that can be used to assist when troubleshooting a Kubernetes cluster.`,
|
||||
SilenceUsage: true,
|
||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||
v := viper.GetViper()
|
||||
v.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
|
||||
v.BindPFlags(cmd.Flags())
|
||||
|
||||
logger.SetupLogger(v)
|
||||
|
||||
if err := util.StartProfiling(); err != nil {
|
||||
klog.Errorf("Failed to start profiling: %v", err)
|
||||
}
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
v := viper.GetViper()
|
||||
|
||||
logger.SetQuiet(v.GetBool("quiet"))
|
||||
|
||||
if len(args) == 0 {
|
||||
return runTroubleshootCRD(v)
|
||||
closer, err := traces.ConfigureTracing("support-bundle")
|
||||
if err != nil {
|
||||
// Do not fail running support-bundle if tracing fails
|
||||
klog.Errorf("Failed to initialize open tracing provider: %v", err)
|
||||
} else {
|
||||
defer closer()
|
||||
}
|
||||
|
||||
return runTroubleshootNoCRD(v, args[0])
|
||||
err = runTroubleshoot(v, args)
|
||||
if v.GetBool("debug") || v.IsSet("v") {
|
||||
fmt.Printf("\n%s", traces.GetExporterInstance().GetSummary())
|
||||
}
|
||||
|
||||
return err
|
||||
},
|
||||
PersistentPostRun: func(cmd *cobra.Command, args []string) {
|
||||
if err := util.StopProfiling(); err != nil {
|
||||
klog.Errorf("Failed to stop profiling: %v", err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
cobra.OnInitialize(initConfig)
|
||||
|
||||
cmd.AddCommand(Analyze())
|
||||
cmd.AddCommand(Redact())
|
||||
cmd.AddCommand(VersionCmd())
|
||||
|
||||
cmd.Flags().String("collectors", "", "name of the collectors to use")
|
||||
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().StringSliceP("selector", "l", []string{"troubleshoot.io/kind=support-bundle"}, "selector to filter on for loading additional support bundle specs found in secrets within the cluster")
|
||||
cmd.Flags().Bool("load-cluster-specs", false, "enable/disable loading additional troubleshoot specs found within the cluster. required when no specs are provided on the command line")
|
||||
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")
|
||||
cmd.Flags().Bool("debug", false, "enable debug logging. This is equivalent to --v=0")
|
||||
|
||||
cmd.Flags().String("serviceaccount", "", "name of the service account to use. if not provided, one will be created")
|
||||
viper.BindPFlags(cmd.Flags())
|
||||
// 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.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
|
||||
// `no-uri` references the `followURI` functionality where we can use an upstream spec when creating a support bundle
|
||||
// This flag makes sure we can also disable this and fall back to the default spec.
|
||||
cmd.Flags().Bool("no-uri", false, "When this flag is used, Troubleshoot does not attempt to retrieve the bundle referenced by the uri: field in the spec.`")
|
||||
|
||||
KubernetesConfigFlags = genericclioptions.NewConfigFlags(false)
|
||||
KubernetesConfigFlags.AddFlags(cmd.Flags())
|
||||
k8sutil.AddFlags(cmd.Flags())
|
||||
|
||||
// Initialize klog flags
|
||||
logger.InitKlogFlags(cmd)
|
||||
|
||||
// CPU and memory profiling flags
|
||||
util.AddProfilingFlags(cmd)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func InitAndExecute() {
|
||||
if err := RootCmd().Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -70,16 +104,3 @@ func initConfig() {
|
||||
viper.SetEnvPrefix("TROUBLESHOOT")
|
||||
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,466 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
cursor "github.com/ahmetalpbalkan/go-cursor"
|
||||
"github.com/fatih/color"
|
||||
"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/httputil"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/specs"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/supportbundle"
|
||||
"github.com/spf13/viper"
|
||||
spin "github.com/tj/go-spin"
|
||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
func runTroubleshoot(v *viper.Viper, arg []string) error {
|
||||
if !v.GetBool("load-cluster-specs") && len(arg) < 1 {
|
||||
return errors.New("flag load-cluster-specs must be set if no specs are provided on the command line")
|
||||
}
|
||||
|
||||
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},
|
||||
})
|
||||
}
|
||||
|
||||
var mainBundle *troubleshootv1beta2.SupportBundle
|
||||
|
||||
troubleshootclientsetscheme.AddToScheme(scheme.Scheme)
|
||||
additionalRedactors := &troubleshootv1beta2.Redactor{}
|
||||
|
||||
// Defining `v` below will render using `v` in reference to Viper unusable.
|
||||
// Therefore refactoring `v` to `val` will make sure we can still use it.
|
||||
for _, val := range arg {
|
||||
|
||||
collectorContent, err := supportbundle.LoadSupportBundleSpec(val)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to load support bundle spec")
|
||||
}
|
||||
multidocs := strings.Split(string(collectorContent), "\n---\n")
|
||||
// Referencing `ParseSupportBundle with a secondary arg of `no-uri`
|
||||
// Will make sure we can enable or disable the use of the `Spec.uri` field for an upstream spec.
|
||||
// This change will not have an impact on KOTS' usage of `ParseSupportBundle`
|
||||
// As Kots uses `load.go` directly.
|
||||
supportBundle, err := supportbundle.ParseSupportBundle([]byte(multidocs[0]), !v.GetBool("no-uri"))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to parse support bundle spec")
|
||||
}
|
||||
|
||||
mainBundle = supportbundle.ConcatSpec(mainBundle, supportBundle)
|
||||
|
||||
parsedRedactors, err := supportbundle.ParseRedactorsFromDocs(multidocs)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to parse redactors from doc")
|
||||
}
|
||||
additionalRedactors.Spec.Redactors = append(additionalRedactors.Spec.Redactors, parsedRedactors...)
|
||||
}
|
||||
|
||||
if v.GetBool("load-cluster-specs") {
|
||||
sbFromCluster, redactorsFromCluster, err := loadClusterSpecs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if sbFromCluster == nil {
|
||||
return errors.New("no specs found in cluster")
|
||||
}
|
||||
mainBundle = supportbundle.ConcatSpec(mainBundle, sbFromCluster)
|
||||
additionalRedactors.Spec.Redactors = append(additionalRedactors.Spec.Redactors, redactorsFromCluster.Spec.Redactors...)
|
||||
}
|
||||
|
||||
if mainBundle == nil {
|
||||
return errors.New("no support bundle specs provided to run")
|
||||
} else if mainBundle.Spec.Collectors == nil && mainBundle.Spec.HostCollectors == nil {
|
||||
return errors.New("no collectors specified in support bundle")
|
||||
}
|
||||
|
||||
redactors, err := supportbundle.GetRedactorsFromURIs(v.GetStringSlice("redactors"))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get redactors")
|
||||
}
|
||||
additionalRedactors.Spec.Redactors = append(additionalRedactors.Spec.Redactors, redactors...)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
collectorCB := func(c chan interface{}, msg string) { c <- msg }
|
||||
progressChan := make(chan interface{})
|
||||
isProgressChanClosed := false
|
||||
defer func() {
|
||||
if !isProgressChanClosed {
|
||||
close(progressChan)
|
||||
}
|
||||
wg.Wait()
|
||||
}()
|
||||
|
||||
if !interactive {
|
||||
// TODO (dans): custom warning handler to capture warning in `analysisOutput`
|
||||
restConfig.WarningHandler = rest.NoWarnings{}
|
||||
|
||||
// TODO (dans): maybe log to file
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for msg := range progressChan {
|
||||
klog.Infof("Collecting support bundle: %v", msg)
|
||||
}
|
||||
}()
|
||||
} else {
|
||||
s := spin.New()
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
currentDir := ""
|
||||
for {
|
||||
select {
|
||||
case msg, ok := <-progressChan:
|
||||
if !ok {
|
||||
fmt.Printf("\r%s\r", cursor.ClearEntireLine())
|
||||
return
|
||||
}
|
||||
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 <-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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
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(&mainBundle.Spec, additionalRedactors, createOpts)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to run collect and analyze process")
|
||||
}
|
||||
|
||||
close(progressChan) // this removes the spinner in interactive mode
|
||||
isProgressChanClosed = true
|
||||
|
||||
if len(response.AnalyzerResults) > 0 {
|
||||
if interactive {
|
||||
if err := showInteractiveResults(mainBundle.Name, response.AnalyzerResults); err != nil {
|
||||
interactive = false
|
||||
}
|
||||
} else {
|
||||
nonInteractiveOutput.Analysis = response.AnalyzerResults
|
||||
}
|
||||
}
|
||||
|
||||
if !response.FileUploaded {
|
||||
if appName := mainBundle.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("\n%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
|
||||
}
|
||||
|
||||
// loadClusterSpecs loads the support bundle and redactor specs from the cluster
|
||||
// based on troubleshoot.io/kind=support-bundle label selector. We search for secrets
|
||||
// and configmaps with the label selector and parse the data as a support bundle. If the
|
||||
// user does not have sufficient permissions to list & read secrets and configmaps from
|
||||
// all namespaces, we will fallback to trying each namespace individually, and eventually
|
||||
// default to the configured kubeconfig namespace.
|
||||
func loadClusterSpecs() (*troubleshootv1beta2.SupportBundle, *troubleshootv1beta2.Redactor, error) {
|
||||
var parsedBundle *troubleshootv1beta2.SupportBundle
|
||||
redactors := &troubleshootv1beta2.Redactor{}
|
||||
|
||||
v := viper.GetViper() // It's singleton, so we can use it anywhere
|
||||
|
||||
klog.Info("Discover troubleshoot specs from cluster")
|
||||
|
||||
labelSelector := strings.Join(v.GetStringSlice("selector"), ",")
|
||||
|
||||
parsedSelector, err := labels.Parse(labelSelector)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "unable to parse selector")
|
||||
}
|
||||
|
||||
config, err := k8sutil.GetRESTConfig()
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "failed to convert kube flags to rest config")
|
||||
}
|
||||
|
||||
client, err := kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "failed to convert create k8s client")
|
||||
}
|
||||
|
||||
// List of namespaces we want to search for secrets and configmaps with support bundle specs
|
||||
namespaces := []string{}
|
||||
ctx := context.Background()
|
||||
|
||||
if v.GetString("namespace") != "" {
|
||||
// Just progress with the namespace provided
|
||||
namespaces = []string{v.GetString("namespace")}
|
||||
} else {
|
||||
// Check if I can read secrets and configmaps in all namespaces
|
||||
ican, err := k8sutil.CanIListAndGetAllSecretsAndConfigMaps(ctx, client)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "failed to check if I can read secrets and configmaps")
|
||||
}
|
||||
klog.V(1).Infof("Can I read any secrets and configmaps: %v", ican)
|
||||
|
||||
if ican {
|
||||
// I can read secrets and configmaps in all namespaces
|
||||
// No need to iterate over all namespaces
|
||||
namespaces = []string{""}
|
||||
} else {
|
||||
// Get list of all namespaces and try to find specs from each namespace
|
||||
nsList, err := client.CoreV1().Namespaces().List(ctx, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
if k8serrors.IsForbidden(err) {
|
||||
kubeconfig := k8sutil.GetKubeconfig()
|
||||
ns, _, err := kubeconfig.Namespace()
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "failed to get namespace from kubeconfig")
|
||||
}
|
||||
// If we are not allowed to list namespaces, just use the default namespace
|
||||
// configured in the kubeconfig
|
||||
namespaces = []string{ns}
|
||||
} else {
|
||||
return nil, nil, errors.Wrap(err, "failed to list namespaces")
|
||||
}
|
||||
}
|
||||
|
||||
for _, ns := range nsList.Items {
|
||||
namespaces = append(namespaces, ns.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var bundlesFromCluster []string
|
||||
|
||||
// Search cluster for support bundle specs
|
||||
klog.V(1).Infof("Search support bundle specs from [%q] namespaces using %q selector", strings.Join(namespaces, ", "), parsedSelector.String())
|
||||
for _, ns := range namespaces {
|
||||
bundlesFromSecrets, err := specs.LoadFromSecretMatchingLabel(client, parsedSelector.String(), ns, specs.SupportBundleKey)
|
||||
if err != nil {
|
||||
if !k8serrors.IsForbidden(err) {
|
||||
klog.Errorf("failed to load support bundle spec from secrets: %s", err)
|
||||
} else {
|
||||
klog.Warningf("Reading secrets from %q namespace forbidden", ns)
|
||||
}
|
||||
}
|
||||
bundlesFromCluster = append(bundlesFromCluster, bundlesFromSecrets...)
|
||||
|
||||
bundlesFromConfigMaps, err := specs.LoadFromConfigMapMatchingLabel(client, parsedSelector.String(), ns, specs.SupportBundleKey)
|
||||
if err != nil {
|
||||
if !k8serrors.IsForbidden(err) {
|
||||
klog.Errorf("failed to load support bundle spec from configmap: %s", err)
|
||||
} else {
|
||||
klog.Warningf("Reading configmaps from %q namespace forbidden", ns)
|
||||
}
|
||||
}
|
||||
bundlesFromCluster = append(bundlesFromCluster, bundlesFromConfigMaps...)
|
||||
}
|
||||
|
||||
for _, bundle := range bundlesFromCluster {
|
||||
multidocs := strings.Split(string(bundle), "\n---\n")
|
||||
parsedBundle, err = supportbundle.ParseSupportBundleFromDoc([]byte(multidocs[0]))
|
||||
if err != nil {
|
||||
klog.Errorf("failed to parse support bundle spec: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
parsedRedactors, err := supportbundle.ParseRedactorsFromDocs(multidocs)
|
||||
if err != nil {
|
||||
klog.Errorf("failed to parse redactors from doc: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
redactors.Spec.Redactors = append(redactors.Spec.Redactors, parsedRedactors...)
|
||||
}
|
||||
|
||||
var redactorsFromCluster []string
|
||||
|
||||
// Search cluster for redactor specs
|
||||
klog.V(1).Infof("Search redactor specs from [%q] namespaces using %q selector", strings.Join(namespaces, ", "), parsedSelector.String())
|
||||
for _, ns := range namespaces {
|
||||
redactorsFromSecrets, err := specs.LoadFromSecretMatchingLabel(client, parsedSelector.String(), ns, specs.RedactorKey)
|
||||
if err != nil {
|
||||
if !k8serrors.IsForbidden(err) {
|
||||
klog.Errorf("failed to load support bundle spec from secrets: %s", err)
|
||||
} else {
|
||||
klog.Warningf("Reading secrets from %q namespace forbidden", ns)
|
||||
}
|
||||
}
|
||||
redactorsFromCluster = append(redactorsFromCluster, redactorsFromSecrets...)
|
||||
|
||||
redactorsFromConfigMaps, err := specs.LoadFromConfigMapMatchingLabel(client, parsedSelector.String(), ns, specs.RedactorKey)
|
||||
if err != nil {
|
||||
if !k8serrors.IsForbidden(err) {
|
||||
klog.Errorf("failed to load support bundle spec from configmap: %s", err)
|
||||
} else {
|
||||
klog.Warningf("Reading configmaps from %q namespace forbidden", ns)
|
||||
}
|
||||
}
|
||||
redactorsFromCluster = append(redactorsFromCluster, redactorsFromConfigMaps...)
|
||||
}
|
||||
|
||||
for _, redactor := range redactorsFromCluster {
|
||||
multidocs := strings.Split(string(redactor), "\n---\n")
|
||||
parsedRedactors, err := supportbundle.ParseRedactorsFromDocs(multidocs)
|
||||
if err != nil {
|
||||
klog.Errorf("failed to parse redactors from doc: %s", err)
|
||||
}
|
||||
|
||||
redactors.Spec.Redactors = append(redactors.Spec.Redactors, parsedRedactors...)
|
||||
}
|
||||
|
||||
return parsedBundle, redactors, nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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,106 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
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(KubernetesConfigFlags)
|
||||
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)
|
||||
}
|
||||
|
||||
config, err := KubernetesConfigFlags.ToRESTConfig()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to convert kube flags to rest config")
|
||||
}
|
||||
|
||||
stopChan, err := k8sutil.PortForward(config, 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,325 +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\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() {
|
||||
finishedCh <- true
|
||||
}()
|
||||
|
||||
archivePath, err := runCollectors(v, collector, progressChan)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "run collectors")
|
||||
}
|
||||
|
||||
fmt.Printf("\r%s\r", 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("\nA 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{}
|
||||
|
||||
config, err := KubernetesConfigFlags.ToRESTConfig()
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to convert kube flags to rest config")
|
||||
}
|
||||
|
||||
// Run preflights collectors synchronously
|
||||
for _, desiredCollector := range desiredCollectors {
|
||||
collector := collect.Collector{
|
||||
Redact: true,
|
||||
Collect: desiredCollector,
|
||||
ClientConfig: config,
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
filename, err := findFileName("support-bundle", "tar.gz")
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "find file name")
|
||||
}
|
||||
|
||||
if err := tarGz.Archive(paths, filename); err != nil {
|
||||
return "", errors.Wrap(err, "create archive")
|
||||
}
|
||||
|
||||
return filename, 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,57 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootclientv1beta1 "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta1"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
)
|
||||
|
||||
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(configFlags *genericclioptions.ConfigFlags) (*troubleshootclientv1beta1.TroubleshootV1beta1Client, error) {
|
||||
config, err := configFlags.ToRESTConfig()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to convert kube flags to rest config")
|
||||
}
|
||||
|
||||
troubleshootClient, err := troubleshootclientv1beta1.NewForConfig(config)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create troubleshoot client")
|
||||
}
|
||||
|
||||
return troubleshootClient, nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,22 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
"gopkg.in/yaml.v2"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"fmt"
|
||||
|
||||
"github.com/replicatedhq/troubleshoot/pkg/version"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func writeVersionFile(path string) (string, error) {
|
||||
version := troubleshootv1beta1.SupportBundleVersion{
|
||||
ApiVersion: "troubleshoot.replicated.com/v1beta1",
|
||||
Kind: "SupportBundle",
|
||||
}
|
||||
b, err := yaml.Marshal(version)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
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())
|
||||
|
||||
filename := filepath.Join(path, "version.yaml")
|
||||
err = ioutil.WriteFile(filename, b, 0644)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
return filename, nil
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -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,83 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var (
|
||||
cpuProfileFile *os.File
|
||||
)
|
||||
|
||||
// StartProfiling starts profiling CPU and memory usage if either --cpuprofile or
|
||||
// --memprofile flags were set and bound to viper configurations respectively.
|
||||
func StartProfiling() error {
|
||||
v := viper.GetViper()
|
||||
if v.GetString("cpuprofile") != "" {
|
||||
var err error
|
||||
cpuProfileFile, err = os.Create(v.GetString("cpuprofile"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create CPU profile: %w", err)
|
||||
}
|
||||
if err := pprof.StartCPUProfile(cpuProfileFile); err != nil {
|
||||
cpuProfileFile.Close()
|
||||
cpuProfileFile = nil
|
||||
return fmt.Errorf("could not start CPU profile: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StopProfiling stops profiling CPU and memory usage and writes the results to
|
||||
// the files specified by --cpuprofile and --memprofile flags respectively.
|
||||
func StopProfiling() error {
|
||||
v := viper.GetViper()
|
||||
errs := []string{}
|
||||
|
||||
// Stop CPU profiling if it was started
|
||||
if cpuProfileFile != nil {
|
||||
pprof.StopCPUProfile()
|
||||
err := cpuProfileFile.Close()
|
||||
if err != nil {
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
cpuProfileFile = nil
|
||||
}
|
||||
|
||||
if v.GetString("memprofile") != "" {
|
||||
f, err := os.Create(v.GetString("memprofile"))
|
||||
if err != nil {
|
||||
errs = append(errs, err.Error())
|
||||
goto BREAK_FROM_MEMPROFILE_IF
|
||||
}
|
||||
runtime.GC() // get up-to-date statistics
|
||||
if err := pprof.WriteHeapProfile(f); err != nil {
|
||||
errs = append(errs, err.Error())
|
||||
goto BREAK_FROM_MEMPROFILE_IF
|
||||
}
|
||||
err = f.Close()
|
||||
if err != nil {
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
BREAK_FROM_MEMPROFILE_IF:
|
||||
if len(errs) > 0 {
|
||||
return fmt.Errorf("errors while stopping profiling: [%s]", strings.Join(errs, ", "))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddProfilingFlags adds the --cpuprofile and --memprofile flags to the given command.
|
||||
func AddProfilingFlags(cmd *cobra.Command) {
|
||||
// Persistent flags to make available to subcommands
|
||||
cmd.PersistentFlags().String("cpuprofile", "", "File path to write cpu profiling data")
|
||||
cmd.PersistentFlags().String("memprofile", "", "File path to write memory profiling data")
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
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(cases.Title(language.English).String(strings.ReplaceAll(name, "-", " ")), " ")
|
||||
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, " ")
|
||||
}
|
||||
@@ -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,19 @@
|
||||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.11.2
|
||||
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 +23,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 +42,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 +70,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 +91,10 @@ spec:
|
||||
type: string
|
||||
containerPath:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
selector:
|
||||
@@ -438,9 +102,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 +133,10 @@ spec:
|
||||
type: array
|
||||
containerName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
selector:
|
||||
@@ -465,13 +146,15 @@ spec:
|
||||
timeout:
|
||||
type: string
|
||||
required:
|
||||
- selector
|
||||
- namespace
|
||||
- selector
|
||||
type: object
|
||||
http:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
get:
|
||||
properties:
|
||||
headers:
|
||||
@@ -485,6 +168,8 @@ spec:
|
||||
required:
|
||||
- url
|
||||
type: object
|
||||
name:
|
||||
type: string
|
||||
post:
|
||||
properties:
|
||||
body:
|
||||
@@ -520,6 +205,12 @@ spec:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
containerNames:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
exclude:
|
||||
type: BoolString
|
||||
limits:
|
||||
properties:
|
||||
maxAge:
|
||||
@@ -528,6 +219,8 @@ spec:
|
||||
format: int64
|
||||
type: integer
|
||||
type: object
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
selector:
|
||||
@@ -537,6 +230,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 +275,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,13 +323,8 @@ spec:
|
||||
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: []
|
||||
|
||||
@@ -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,80 @@
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.11.2
|
||||
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
|
||||
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
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,80 @@
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.11.2
|
||||
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
|
||||
@@ -0,0 +1,381 @@
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.11.2
|
||||
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
|
||||
subnetAvailable:
|
||||
properties:
|
||||
CIDRRangeAlloc:
|
||||
type: string
|
||||
collectorName:
|
||||
type: string
|
||||
desiredCIDR:
|
||||
type: integer
|
||||
exclude:
|
||||
type: BoolString
|
||||
required:
|
||||
- CIDRRangeAlloc
|
||||
- desiredCIDR
|
||||
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
|
||||
udpPortStatus:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
interface:
|
||||
type: string
|
||||
port:
|
||||
type: integer
|
||||
required:
|
||||
- port
|
||||
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
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user