mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-14 18:40:55 +00:00
Compare commits
1799 Commits
v1.0.9
...
revert-197
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53db9b8de4 | ||
|
|
2fc224c2ff | ||
|
|
3b904108bd | ||
|
|
6fbab07df5 | ||
|
|
b5dba5b4da | ||
|
|
75e9079baa | ||
|
|
7e205b46b8 | ||
|
|
a599d781ae | ||
|
|
c6c099a9d1 | ||
|
|
a9244f759e | ||
|
|
60e2375521 | ||
|
|
e0ba78f292 | ||
|
|
322f368cdf | ||
|
|
ec3668fd34 | ||
|
|
735c249bc4 | ||
|
|
7c8e2f3948 | ||
|
|
b8a13b6335 | ||
|
|
425cc04a34 | ||
|
|
2252b3cc8b | ||
|
|
943693e8e4 | ||
|
|
05829497d8 | ||
|
|
87cf1231c3 | ||
|
|
1cae8f9858 | ||
|
|
129206a3c8 | ||
|
|
7bdb5d3cfa | ||
|
|
6309af43a7 | ||
|
|
ac1f4fec4a | ||
|
|
792caeac82 | ||
|
|
bb03507d98 | ||
|
|
4fc859b62e | ||
|
|
7cdb157a55 | ||
|
|
50ce2a66a3 | ||
|
|
dae03a9618 | ||
|
|
6dd48142cf | ||
|
|
3f1b100edc | ||
|
|
61c5afdf83 | ||
|
|
71e75283de | ||
|
|
98d2be5550 | ||
|
|
634c780d1b | ||
|
|
400140a401 | ||
|
|
e80bd10374 | ||
|
|
9242269b5b | ||
|
|
7e847dc907 | ||
|
|
6dbed7c16c | ||
|
|
1a3f622207 | ||
|
|
1a28e493cd | ||
|
|
d9239104aa | ||
|
|
f5805c64b2 | ||
|
|
f299b28693 | ||
|
|
ee678d6ce9 | ||
|
|
d5b44461b9 | ||
|
|
57059b74f5 | ||
|
|
cd0b06c4fd | ||
|
|
057893a254 | ||
|
|
1e14cc93c2 | ||
|
|
54e8aab374 | ||
|
|
4312ab3f4d | ||
|
|
6484259632 | ||
|
|
92dcc02ff1 | ||
|
|
fe6dffff2e | ||
|
|
e1205698e6 | ||
|
|
a6535c4be2 | ||
|
|
a28c353eab | ||
|
|
4791bf8e0b | ||
|
|
4025603396 | ||
|
|
c2dc7ae789 | ||
|
|
484ad36529 | ||
|
|
0e251ed96e | ||
|
|
afd24c6faf | ||
|
|
6b7236a206 | ||
|
|
f65cac96d6 | ||
|
|
0d79219e46 | ||
|
|
e4635b456a | ||
|
|
1b3b8522f9 | ||
|
|
4098d6fdfb | ||
|
|
4ba69b27f8 | ||
|
|
bffc0d7071 | ||
|
|
54b1549586 | ||
|
|
3803541bfd | ||
|
|
2102ddab7c | ||
|
|
f186a0d6bb | ||
|
|
b0f1f66d9a | ||
|
|
8c037ba109 | ||
|
|
5e5e3b19f5 | ||
|
|
ad94f2c0eb | ||
|
|
b333115314 | ||
|
|
50c9916eae | ||
|
|
e1b18d125d | ||
|
|
8d72bf6e5c | ||
|
|
a4c69d9cde | ||
|
|
ecb9b9efa4 | ||
|
|
2eacbeaa87 | ||
|
|
46be801a48 | ||
|
|
0bace652c3 | ||
|
|
f0779cd02f | ||
|
|
105a8e57c8 | ||
|
|
0a8d7c2be2 | ||
|
|
20d995c87e | ||
|
|
3703450931 | ||
|
|
95ad5d05b3 | ||
|
|
d4fa006ccb | ||
|
|
b2814c5310 | ||
|
|
c00cf120ff | ||
|
|
decca7fcd3 | ||
|
|
9914017f60 | ||
|
|
a077482e99 | ||
|
|
849e0f357a | ||
|
|
df7688002a | ||
|
|
e811da14d8 | ||
|
|
9e5af98a94 | ||
|
|
f2c7066088 | ||
|
|
3f5dc1e80a | ||
|
|
04bfb3e94d | ||
|
|
567bc834c9 | ||
|
|
49f8b2bee8 | ||
|
|
b710a76818 | ||
|
|
823c59666b | ||
|
|
e14c441e77 | ||
|
|
de4d50386d | ||
|
|
969a1e8f37 | ||
|
|
184735fa89 | ||
|
|
caa71a73fe | ||
|
|
2625502ddb | ||
|
|
7c1b477ff6 | ||
|
|
d25b6ff3dc | ||
|
|
68a196dbe5 | ||
|
|
c7600bc079 | ||
|
|
79884240ae | ||
|
|
3880b8df1d | ||
|
|
8aaa3f56f5 | ||
|
|
4a7ade6421 | ||
|
|
e9ab3da164 | ||
|
|
dff30b926e | ||
|
|
fd5db20a48 | ||
|
|
8e99dbdaf0 | ||
|
|
861c86a70a | ||
|
|
0a19a5dbd9 | ||
|
|
382c330f5b | ||
|
|
ce935448c1 | ||
|
|
49287a0f8f | ||
|
|
1433ea4faa | ||
|
|
daa14ae4bd | ||
|
|
46af583a57 | ||
|
|
f33069fc15 | ||
|
|
cf5d3e73c0 | ||
|
|
4f8eaacc9b | ||
|
|
47d6d498d6 | ||
|
|
1f85ce454d | ||
|
|
fc8c45cff5 | ||
|
|
d98af3f06a | ||
|
|
d942e6fa59 | ||
|
|
fdd2cc5004 | ||
|
|
59b1c81e7e | ||
|
|
ae0658869a | ||
|
|
5491202d69 | ||
|
|
c3716ff3e0 | ||
|
|
8e7c53b9e7 | ||
|
|
cbfe314778 | ||
|
|
2d39d64d3a | ||
|
|
cd84b65972 | ||
|
|
fd7574cd61 | ||
|
|
d28ef820ea | ||
|
|
e5c314a3ea | ||
|
|
189f65dab9 | ||
|
|
9942a9278f | ||
|
|
44db041682 | ||
|
|
a49a764705 | ||
|
|
680a0812c6 | ||
|
|
50c63d0021 | ||
|
|
37e167e8d1 | ||
|
|
038c070626 | ||
|
|
8bb8637213 | ||
|
|
7ae21cb933 | ||
|
|
8fb64ffb0c | ||
|
|
f5a7227e26 | ||
|
|
8a2b3cd1d9 | ||
|
|
61133f3e2e | ||
|
|
bf339173c8 | ||
|
|
e46108ada0 | ||
|
|
9e16c8ca50 | ||
|
|
763cb4e925 | ||
|
|
f0153997e1 | ||
|
|
6d141f2ad0 | ||
|
|
89e7b76d48 | ||
|
|
b7855dc959 | ||
|
|
6485a7cf3e | ||
|
|
fc92f4acd0 | ||
|
|
52a5e6ec99 | ||
|
|
752ddd087b | ||
|
|
2b22bfc16b | ||
|
|
7ce1f7e95a | ||
|
|
c722d4cd17 | ||
|
|
1a9d62617e | ||
|
|
dc66de6bf3 | ||
|
|
cb64e19da3 | ||
|
|
9bba761a14 | ||
|
|
f3e735153f | ||
|
|
31c5e89b1f | ||
|
|
a2b84fce86 | ||
|
|
eccef3bb0d | ||
|
|
a489f30183 | ||
|
|
7eaaedf9f6 | ||
|
|
d1b33e7468 | ||
|
|
3b2988bda8 | ||
|
|
84b75afae4 | ||
|
|
73fb32c85a | ||
|
|
5876a8cce0 | ||
|
|
c9ee7f3787 | ||
|
|
23f9f819eb | ||
|
|
7fe518b0c6 | ||
|
|
7baabf7a97 | ||
|
|
4a65ea8056 | ||
|
|
598854440a | ||
|
|
9f2163ae54 | ||
|
|
ee8598dcbd | ||
|
|
f8a25cc2b3 | ||
|
|
80dc481f9c | ||
|
|
be4d6f77b2 | ||
|
|
2a35bbffe4 | ||
|
|
59a70525fa | ||
|
|
ee4f404ae8 | ||
|
|
752ca4b37c | ||
|
|
964a5082b1 | ||
|
|
81d809f15d | ||
|
|
5f80480068 | ||
|
|
416e1fcc1a | ||
|
|
bf8af8efba | ||
|
|
980209e87c | ||
|
|
3345464b25 | ||
|
|
2a5f513bc3 | ||
|
|
f531ac065d | ||
|
|
b240092058 | ||
|
|
f0ca8f9c1a | ||
|
|
5d4e45c24d | ||
|
|
04516803f2 | ||
|
|
56d57c3088 | ||
|
|
b6d760dc6f | ||
|
|
c856f800d0 | ||
|
|
b86bea9749 | ||
|
|
675faaced2 | ||
|
|
248d0d57d6 | ||
|
|
a7cff1c75b | ||
|
|
96c2873cc0 | ||
|
|
ff60138efd | ||
|
|
672153f021 | ||
|
|
d81913de42 | ||
|
|
3ec583a3df | ||
|
|
14d4059f46 | ||
|
|
99701a1932 | ||
|
|
d8e5891498 | ||
|
|
53b5e0f602 | ||
|
|
e5382c59a2 | ||
|
|
bf119bf5b7 | ||
|
|
277f565f2e | ||
|
|
8019cb0405 | ||
|
|
362454413a | ||
|
|
562aaadf2c | ||
|
|
c868f01f82 | ||
|
|
83db25d6c4 | ||
|
|
e6306f60f4 | ||
|
|
dd3ff341e0 | ||
|
|
5381daf95a | ||
|
|
fd588879e8 | ||
|
|
03dfb03a16 | ||
|
|
927d0d0c63 | ||
|
|
0924269e76 | ||
|
|
2326362fd2 | ||
|
|
3da0afdda0 | ||
|
|
85d34531a2 | ||
|
|
4db30d16a6 | ||
|
|
a49507c79e | ||
|
|
7f1e93c6f5 | ||
|
|
7b71263583 | ||
|
|
75b8103215 | ||
|
|
2fa944b1cd | ||
|
|
aa09e3f3f9 | ||
|
|
8aaac77397 | ||
|
|
9255c991f4 | ||
|
|
d08140d3f7 | ||
|
|
f69d2d2e69 | ||
|
|
d0b874a643 | ||
|
|
b52c6a77f4 | ||
|
|
0a84a0d525 | ||
|
|
12a26e1cbc | ||
|
|
1a7cb3137e | ||
|
|
2605c124cc | ||
|
|
95f4d9d2bb | ||
|
|
7164f840aa | ||
|
|
c0d1bde30c | ||
|
|
75a761112c | ||
|
|
73f94c1981 | ||
|
|
003b3eccc2 | ||
|
|
df25f3a93a | ||
|
|
4448bcf22c | ||
|
|
5eef5f2777 | ||
|
|
52466ffe83 | ||
|
|
4542e4a122 | ||
|
|
875385609e | ||
|
|
24f3d7556f | ||
|
|
ffeae451ab | ||
|
|
c99278c67b | ||
|
|
867b795158 | ||
|
|
301e529a4a | ||
|
|
563564a198 | ||
|
|
bfa5c59dc6 | ||
|
|
f202ce34eb | ||
|
|
912a9c3baa | ||
|
|
b473faccca | ||
|
|
1bbd8b2c43 | ||
|
|
1be5292eb6 | ||
|
|
a8ece4b5f7 | ||
|
|
a6a5976304 | ||
|
|
c91e2283db | ||
|
|
2c9635967a | ||
|
|
2dd148af24 | ||
|
|
bae6418ca2 | ||
|
|
1db0248748 | ||
|
|
387afef19b | ||
|
|
9e47e585d4 | ||
|
|
1f8696865f | ||
|
|
87b6a4d6bc | ||
|
|
5fe0b9e1b2 | ||
|
|
9a25cb96b7 | ||
|
|
0f81cf52e7 | ||
|
|
eb75c039e5 | ||
|
|
0f0c6618a0 | ||
|
|
3e4354dfb8 | ||
|
|
06e10cc084 | ||
|
|
97f2f33460 | ||
|
|
382981fb43 | ||
|
|
519dacc10f | ||
|
|
02c0c2692f | ||
|
|
6df4efd145 | ||
|
|
4dab6268da | ||
|
|
4150026ec7 | ||
|
|
10cd82ce31 | ||
|
|
d0ce4fcf15 | ||
|
|
d54ff23560 | ||
|
|
8e4609a850 | ||
|
|
eabb476461 | ||
|
|
74255f6bad | ||
|
|
3dfe62fe55 | ||
|
|
ad9b869ddb | ||
|
|
5c4c19bf19 | ||
|
|
1f86a0ca5d | ||
|
|
db630a9d07 | ||
|
|
388dd13a5f | ||
|
|
67c9d469c4 | ||
|
|
28a55bbd9c | ||
|
|
bdacb941ab | ||
|
|
d87ad523de | ||
|
|
ec36993d42 | ||
|
|
6b5aebac22 | ||
|
|
883714e2e5 | ||
|
|
8b0f4bf714 | ||
|
|
e83b97ea1f | ||
|
|
48dfcf5a3e | ||
|
|
cb4498594b | ||
|
|
b0bd4b5410 | ||
|
|
dbf8a0fde4 | ||
|
|
3db1111f8e | ||
|
|
e482ffa3f9 | ||
|
|
2b1749778f | ||
|
|
6af51a1bfe | ||
|
|
e0b46acf2f | ||
|
|
2e33a69388 | ||
|
|
018698ec85 | ||
|
|
0029a8ce32 | ||
|
|
04d5494246 | ||
|
|
41a8bd208d | ||
|
|
6bb470dbd0 | ||
|
|
9fa0391ce9 | ||
|
|
bda22d08cc | ||
|
|
09faaa1b2c | ||
|
|
9a65634df6 | ||
|
|
300383959d | ||
|
|
ab17d8dd74 | ||
|
|
c5ba2ced3b | ||
|
|
4103580834 | ||
|
|
d36e3f015d | ||
|
|
975e071394 | ||
|
|
6542af600d | ||
|
|
9e6e928725 | ||
|
|
ad24ba2234 | ||
|
|
c7fd1dee8b | ||
|
|
606eb8a74c | ||
|
|
2340c98f6a | ||
|
|
a7607c20e3 | ||
|
|
0b5cf74945 | ||
|
|
0687699d27 | ||
|
|
357126fc4e | ||
|
|
0aeb53a2b3 | ||
|
|
efd7c8e3f7 | ||
|
|
152ee44b92 | ||
|
|
588297f1f9 | ||
|
|
6b81ae9a93 | ||
|
|
077c7b2d20 | ||
|
|
1a4330a7cb | ||
|
|
d08690a6aa | ||
|
|
ba925e833d | ||
|
|
4c15c42447 | ||
|
|
690e01c2ba | ||
|
|
133d8bff5e | ||
|
|
079c3307c1 | ||
|
|
e0070f7ec5 | ||
|
|
a45eca7e22 | ||
|
|
79c5f8a977 | ||
|
|
69d1699963 | ||
|
|
b6b8f4396f | ||
|
|
9ca5284e4f | ||
|
|
335292fe8a | ||
|
|
cf9edbfb3c | ||
|
|
364a2342df | ||
|
|
3301913178 | ||
|
|
ac3c53557e | ||
|
|
8bf98dd255 | ||
|
|
f2368201d9 | ||
|
|
f44574cf43 | ||
|
|
3054d69dd9 | ||
|
|
739c2a5bac | ||
|
|
a780bd6465 | ||
|
|
4716cb026f | ||
|
|
a186144a78 | ||
|
|
2ea4762d0f | ||
|
|
eb268fa722 | ||
|
|
3b3a1309e4 | ||
|
|
72db6dde9e | ||
|
|
6d30bc5c35 | ||
|
|
773629e544 | ||
|
|
cd50bf4e1e | ||
|
|
ff59e9b52f | ||
|
|
9a954d4f0b | ||
|
|
5c63c20390 | ||
|
|
a84f8d65db | ||
|
|
6e2335ec3d | ||
|
|
4cf6630fc0 | ||
|
|
d239d52217 | ||
|
|
c260105212 | ||
|
|
44cdf98a47 | ||
|
|
92390eabe4 | ||
|
|
7b8fa51ec5 | ||
|
|
af8e17a1ed | ||
|
|
e2eeb90639 | ||
|
|
1704977a4b | ||
|
|
7050c6a7b6 | ||
|
|
02f9b98b5a | ||
|
|
ce7ebe3299 | ||
|
|
3928ada0e5 | ||
|
|
0a8faced8f | ||
|
|
3c06debf98 | ||
|
|
fcee91eafd | ||
|
|
b0b3a705f4 | ||
|
|
67c8fbcc3c | ||
|
|
9b50b78ec8 | ||
|
|
943a1b0195 | ||
|
|
f77143cd34 | ||
|
|
b7d2ba2376 | ||
|
|
c28a0eb83d | ||
|
|
f7e5b5138b | ||
|
|
61149cbf21 | ||
|
|
310d516030 | ||
|
|
a9e3fe155b | ||
|
|
24837bad40 | ||
|
|
9686cc9861 | ||
|
|
a4784ee5ec | ||
|
|
3063560e77 | ||
|
|
bff0604bee | ||
|
|
9f8faa7d7e | ||
|
|
335077eade | ||
|
|
491baa74cb | ||
|
|
2f2d078669 | ||
|
|
b7bcb90057 | ||
|
|
35dc15b16b | ||
|
|
83f70877c8 | ||
|
|
98cd31b820 | ||
|
|
5416ae7365 | ||
|
|
644f2ddcdc | ||
|
|
46524d3b6a | ||
|
|
4f014d0262 | ||
|
|
0cf2057fc5 | ||
|
|
3f08e1546c | ||
|
|
10619fb0f7 | ||
|
|
c88e8cc057 | ||
|
|
327a3f5300 | ||
|
|
fddde81f9c | ||
|
|
22d07ed37d | ||
|
|
dee1c425da | ||
|
|
951d15bf17 | ||
|
|
0f82d2932c | ||
|
|
e2d7a06e9f | ||
|
|
286b9c1aed | ||
|
|
f54d4a5837 | ||
|
|
d9031fb2c9 | ||
|
|
3af5a8afea | ||
|
|
e2fd33c54a | ||
|
|
c90e0a4080 | ||
|
|
5de000ee3d | ||
|
|
c28b82510c | ||
|
|
fda3ba8af9 | ||
|
|
fd1356e5d8 | ||
|
|
a62f1364fe | ||
|
|
d1240bcc63 | ||
|
|
1c24fe7d16 | ||
|
|
e5c8b5ec8f | ||
|
|
180429774a | ||
|
|
586bba0b31 | ||
|
|
2ce138ab3a | ||
|
|
5e99ad000e | ||
|
|
0f0e740c21 | ||
|
|
33600189bc | ||
|
|
07d2500ee3 | ||
|
|
de6eb14867 | ||
|
|
85b71a31e3 | ||
|
|
16e7638220 | ||
|
|
04c23b2085 | ||
|
|
24db94dd0d | ||
|
|
5f862ba17c | ||
|
|
18ba3ee91b | ||
|
|
4e9d42fae7 | ||
|
|
52e57dab7f | ||
|
|
ba464a5e11 | ||
|
|
2734085fb0 | ||
|
|
d21ff7cfe6 | ||
|
|
3a15f450a9 | ||
|
|
bb77e7491a | ||
|
|
aa82240b4c | ||
|
|
cac7b46ebd | ||
|
|
367d0e042c | ||
|
|
d851305c33 | ||
|
|
0c52bd71ba | ||
|
|
41a008e9a3 | ||
|
|
2fadb4dd59 | ||
|
|
a88ee3f93c | ||
|
|
237848a80b | ||
|
|
9e3b837093 | ||
|
|
c4eca908ac | ||
|
|
72d9b4cbca | ||
|
|
19d94110b1 | ||
|
|
1756765dbc | ||
|
|
b306f8511c | ||
|
|
9778f867b5 | ||
|
|
a69ebf2b11 | ||
|
|
cf9c81f908 | ||
|
|
b95164b9a8 | ||
|
|
0551338958 | ||
|
|
40b7ad23ea | ||
|
|
ce66ceeed6 | ||
|
|
4e45af6265 | ||
|
|
07a9454215 | ||
|
|
9f5a936236 | ||
|
|
e6770e5f1e | ||
|
|
705b4ab212 | ||
|
|
2cb964ab8e | ||
|
|
949b10bf93 | ||
|
|
0115fbc3da | ||
|
|
cc4341c546 | ||
|
|
d0caea0ce1 | ||
|
|
f2ac5a2d0d | ||
|
|
78d14d0d75 | ||
|
|
d5034af5ca | ||
|
|
40ed9e6a44 | ||
|
|
c1d20546ec | ||
|
|
3cf6b8ec4d | ||
|
|
3aee7a9081 | ||
|
|
abefa2b155 | ||
|
|
5d800f0b0a | ||
|
|
4eb2d5bcc2 | ||
|
|
988af1ff61 | ||
|
|
1617183ea4 | ||
|
|
ee72746481 | ||
|
|
c9e7dc3bfa | ||
|
|
07e0e46ac7 | ||
|
|
404d2d631a | ||
|
|
baa0296a12 | ||
|
|
0f665ac153 | ||
|
|
14b0a65091 | ||
|
|
2d58f8b81c | ||
|
|
9a43ca53cc | ||
|
|
5372fc6f6c | ||
|
|
86bc344057 | ||
|
|
a014f7d414 | ||
|
|
9a94bcb854 | ||
|
|
07634ef098 | ||
|
|
995f88d60c | ||
|
|
5caba59073 | ||
|
|
334a64676f | ||
|
|
08963ba503 | ||
|
|
326fb689be | ||
|
|
970ce67c34 | ||
|
|
98d1893057 | ||
|
|
d89b448c74 | ||
|
|
17bf9d325b | ||
|
|
a99aff1d1c | ||
|
|
a694ac7b63 | ||
|
|
b5b11ef6e9 | ||
|
|
fa1af6f51e | ||
|
|
9288dec02a | ||
|
|
1a45dd0b4f | ||
|
|
592c5acf5a | ||
|
|
ac9424fa3e | ||
|
|
79fbe7c4cc | ||
|
|
f69d526fa3 | ||
|
|
07a95a60f6 | ||
|
|
032b385711 | ||
|
|
810629596a | ||
|
|
b82a8fd316 | ||
|
|
2d0c22d6a4 | ||
|
|
aa342deff7 | ||
|
|
10786ec0a7 | ||
|
|
7c7056877b | ||
|
|
e8933d9789 | ||
|
|
9d7b65446f | ||
|
|
7a0946a922 | ||
|
|
def4f04572 | ||
|
|
2f2408f1cd | ||
|
|
3b9bcc48a0 | ||
|
|
d0429ff43b | ||
|
|
33deefc307 | ||
|
|
9b3de82b45 | ||
|
|
d217074fbf | ||
|
|
1d90ba7c7b | ||
|
|
eeeb4c36a1 | ||
|
|
b1faa989f4 | ||
|
|
d8250c9ee2 | ||
|
|
c950046659 | ||
|
|
0c32691e9e | ||
|
|
88b1d62740 | ||
|
|
aec8206695 | ||
|
|
20c2b53a46 | ||
|
|
274b5c3b4e | ||
|
|
b1fdaa2311 | ||
|
|
a3103f1e62 | ||
|
|
74ed0b30e5 | ||
|
|
b5d5e70bdc | ||
|
|
2e82985380 | ||
|
|
55941f5769 | ||
|
|
32be1cf4c2 | ||
|
|
2050afdeb4 | ||
|
|
7e71009283 | ||
|
|
72d26c6ad5 | ||
|
|
e011f3be4f | ||
|
|
f725bfc165 | ||
|
|
94ac55f17b | ||
|
|
dd5b3c1e2e | ||
|
|
e898c5221b | ||
|
|
1237ae43b4 | ||
|
|
281f932814 | ||
|
|
cd0187e948 | ||
|
|
9516880042 | ||
|
|
4cb883863f | ||
|
|
9e226001e3 | ||
|
|
9ee35c9afb | ||
|
|
e455acc14b | ||
|
|
6a3c2b2893 | ||
|
|
f59d7998d2 | ||
|
|
77b63f96d1 | ||
|
|
6fcb78403f | ||
|
|
f87edc8c67 | ||
|
|
6a4150d199 | ||
|
|
143c5dd21d | ||
|
|
ed920afb2e | ||
|
|
2677f4c4e7 | ||
|
|
a081534938 | ||
|
|
4ebad27d7a | ||
|
|
716a7307b2 | ||
|
|
ed91bce275 | ||
|
|
c2d6f09ef3 | ||
|
|
119ff5af73 | ||
|
|
2e7ad48b44 | ||
|
|
6ead3b7b1f | ||
|
|
31262cccbe | ||
|
|
93cedbaa51 | ||
|
|
6e13acfac3 | ||
|
|
2e6dd481e0 | ||
|
|
a66808a10d | ||
|
|
a4e1ba0593 | ||
|
|
73660af10c | ||
|
|
84519c236b | ||
|
|
aedb362565 | ||
|
|
6918931728 | ||
|
|
3f1b2bb744 | ||
|
|
33ad02a6b4 | ||
|
|
bfd6e086de | ||
|
|
a9f58383d8 | ||
|
|
aabbbf05ef | ||
|
|
40c613d0cd | ||
|
|
eca5b8796f | ||
|
|
aa2bf7ed08 | ||
|
|
351df67e39 | ||
|
|
8a8698ccdd | ||
|
|
66fa2de073 | ||
|
|
3ace96d7a4 | ||
|
|
2b44ba200f | ||
|
|
4b67a6de12 | ||
|
|
33bd221a98 | ||
|
|
594a06d35b | ||
|
|
e541ff3999 | ||
|
|
9ea184c04a | ||
|
|
993993c6cd | ||
|
|
35b39763dd | ||
|
|
2c1dda5436 | ||
|
|
653123975c | ||
|
|
fb8b314a29 | ||
|
|
5cf3f4e275 | ||
|
|
766500508c | ||
|
|
423a8a6e0d | ||
|
|
7783a76b8f | ||
|
|
2b6a406dc7 | ||
|
|
bc303c4629 | ||
|
|
00360f381c | ||
|
|
fa834f9541 | ||
|
|
a2767cab2a | ||
|
|
24c173a49b | ||
|
|
d3d4908f95 | ||
|
|
be1d5478dc | ||
|
|
d3022ccd65 | ||
|
|
fe45157b26 | ||
|
|
df779fd720 | ||
|
|
e0d388c6f7 | ||
|
|
62edcae233 | ||
|
|
ac6918d70f | ||
|
|
ca41674df3 | ||
|
|
c02b4f3a11 | ||
|
|
64341a81fa | ||
|
|
fe8ba8e44b | ||
|
|
54f1952195 | ||
|
|
44b62a8ebc | ||
|
|
8e9c08ea61 | ||
|
|
c464fb0a81 | ||
|
|
9481e3fba6 | ||
|
|
0e5206a251 | ||
|
|
96c5b4aa3e | ||
|
|
6c44da52a2 | ||
|
|
694cf23df8 | ||
|
|
e66656aa7f | ||
|
|
eaae7af5fe | ||
|
|
2de052ecd8 | ||
|
|
6cf8b9e2b8 | ||
|
|
f9fe138114 | ||
|
|
78c9729252 | ||
|
|
2a2a889c37 | ||
|
|
34287e511f | ||
|
|
e6fffc8ba4 | ||
|
|
86f221611e | ||
|
|
b4d6e89fa2 | ||
|
|
adbb6228a5 | ||
|
|
5937bd0259 | ||
|
|
eeafd43513 | ||
|
|
a68f95b65f | ||
|
|
ed3c29be12 | ||
|
|
3d2e956b19 | ||
|
|
dd9d1f95e9 | ||
|
|
a279c08f7d | ||
|
|
a798109161 | ||
|
|
5dfa929906 | ||
|
|
e904f612f3 | ||
|
|
bafd6b5423 | ||
|
|
963913f9ef | ||
|
|
46905588ac | ||
|
|
5426888df4 | ||
|
|
35481ec6d9 | ||
|
|
6c92c30e94 | ||
|
|
02f6b3ec98 | ||
|
|
a9848f2738 | ||
|
|
b4038a6cd2 | ||
|
|
95f3303493 | ||
|
|
2faf4a491b | ||
|
|
e646bba1ff | ||
|
|
99a21b0a3c | ||
|
|
e7a22b6bc5 | ||
|
|
d783bbc0bc | ||
|
|
b7405f3872 | ||
|
|
abc419b5f9 | ||
|
|
336378b114 | ||
|
|
29959551da | ||
|
|
fc78917191 | ||
|
|
ffd95ef5a9 | ||
|
|
230090d790 | ||
|
|
8fa3861ba3 | ||
|
|
69c90e3427 | ||
|
|
5a73f345fd | ||
|
|
0e62d759f0 | ||
|
|
b2967d2f77 | ||
|
|
c23039c07a | ||
|
|
5747c417c4 | ||
|
|
8c53d77111 | ||
|
|
01667cabde | ||
|
|
f649b62629 | ||
|
|
3a4d025b5c | ||
|
|
c2cc93a009 | ||
|
|
af29855802 | ||
|
|
99eb08eb1e | ||
|
|
d3f8c0d87f | ||
|
|
0bec7b996b | ||
|
|
dd5674fe6b | ||
|
|
33159c26df | ||
|
|
afc7dbebe5 | ||
|
|
f363acf839 | ||
|
|
96d5a7401d | ||
|
|
403fa20546 | ||
|
|
ba4d7ddca8 | ||
|
|
5116e2f210 | ||
|
|
9e0f198227 | ||
|
|
30b378a924 | ||
|
|
3a843f1eca | ||
|
|
9b40f8ab47 | ||
|
|
dc6dcd8150 | ||
|
|
3cb6c7f1f4 | ||
|
|
7632839bc8 | ||
|
|
c3ea109b59 | ||
|
|
579995dc8a | ||
|
|
b43bd5440d | ||
|
|
c4d899f7f3 | ||
|
|
7998ee7036 | ||
|
|
878960d7b1 | ||
|
|
ed0cfc685b | ||
|
|
b0a7345123 | ||
|
|
580963ea76 | ||
|
|
0707deae95 | ||
|
|
fb44880b8c | ||
|
|
e5ebca6604 | ||
|
|
f5fc9acb84 | ||
|
|
28d1bad3cb | ||
|
|
6f74419628 | ||
|
|
8121467c1e | ||
|
|
a85f297f31 | ||
|
|
76a7816aeb | ||
|
|
7872405379 | ||
|
|
6c17a3409f | ||
|
|
f1dbab9dee | ||
|
|
bfafbbf47f | ||
|
|
08d7c35da7 | ||
|
|
f12704f6c1 | ||
|
|
0edab60b30 | ||
|
|
3c05e2d664 | ||
|
|
aa2313c282 | ||
|
|
eeed1954fb | ||
|
|
cd00ce7ab1 | ||
|
|
145d07363f | ||
|
|
33fff655db | ||
|
|
31ab347da2 | ||
|
|
7a48b2ba8e | ||
|
|
876f2a8236 | ||
|
|
095333ffb1 | ||
|
|
0d8d9e2f2b | ||
|
|
9bff2e8883 | ||
|
|
120ba6b870 | ||
|
|
483188ba89 | ||
|
|
672bda0c9c | ||
|
|
49b32473ca | ||
|
|
08400d77a6 | ||
|
|
c912baeb3a | ||
|
|
433733eb0e | ||
|
|
f996ac82c7 | ||
|
|
efcb7cc5a5 | ||
|
|
bf7b57537b | ||
|
|
6b597f8711 | ||
|
|
088739900f | ||
|
|
3bf13f83d3 | ||
|
|
c64a72f1f9 | ||
|
|
8b60b456ac | ||
|
|
e0bac6ad19 | ||
|
|
d841d1bb36 | ||
|
|
0d87cd6ba1 | ||
|
|
28ad36b02c | ||
|
|
cad8a7bd3f | ||
|
|
60a990d660 | ||
|
|
cb3751cea6 | ||
|
|
5ad012e6d9 | ||
|
|
8a454de8f9 | ||
|
|
57b18b7caa | ||
|
|
701d2c9597 | ||
|
|
e7e844bc95 | ||
|
|
0fe95a2f74 | ||
|
|
eb93d8c389 | ||
|
|
8b373ab587 | ||
|
|
c352003f3e | ||
|
|
79d0de7000 | ||
|
|
a32d5ce7ab | ||
|
|
5de0673db1 | ||
|
|
c2b0b6f781 | ||
|
|
116b37813a | ||
|
|
27f0d29734 | ||
|
|
f62af4ebf3 | ||
|
|
faa6d0fd0a | ||
|
|
0554da9d6e | ||
|
|
fa1fd9fbd7 | ||
|
|
3dffc30e83 | ||
|
|
2126b6cf23 | ||
|
|
2b052fdd55 | ||
|
|
58faa762cb | ||
|
|
349cfff1cb | ||
|
|
558be8b923 | ||
|
|
233b3613ae | ||
|
|
615a41d6be | ||
|
|
0ceefcf39d | ||
|
|
16ae64a722 | ||
|
|
3f239fb4a5 | ||
|
|
a60d99fdc9 | ||
|
|
dd0334536b | ||
|
|
3cef37bdb2 | ||
|
|
ac27e94dff | ||
|
|
0877aa7e0b | ||
|
|
07e5a544fe | ||
|
|
60c04a5f33 | ||
|
|
b9b9fb1dd2 | ||
|
|
e1233a0fbc | ||
|
|
cc8203032c | ||
|
|
7117961234 | ||
|
|
d410252cf8 | ||
|
|
4235c57657 | ||
|
|
e34c1ce192 | ||
|
|
4d399ad89c | ||
|
|
9d6ab24388 | ||
|
|
ee9f35d451 | ||
|
|
45c11ec733 | ||
|
|
0519df4ad5 | ||
|
|
55585d8da5 | ||
|
|
b8b49c3124 | ||
|
|
a41471d895 | ||
|
|
877e9ecf64 | ||
|
|
150985bb9c | ||
|
|
039f7669df | ||
|
|
6caa042b05 | ||
|
|
cc0fffc67b | ||
|
|
50d40ef941 | ||
|
|
69d40bd740 | ||
|
|
4272611593 | ||
|
|
74f5e74b89 | ||
|
|
2bba0a6aa3 | ||
|
|
762d3143eb | ||
|
|
7f22e25dfe | ||
|
|
41c162a65f | ||
|
|
ca521946a5 | ||
|
|
b0e53d2b39 | ||
|
|
5c93722db8 | ||
|
|
d34c82c905 | ||
|
|
f11d083b0a | ||
|
|
f1a5a7703c | ||
|
|
9cc2c90a4b | ||
|
|
bc31fa9120 | ||
|
|
7a67645558 | ||
|
|
b0f59358d9 | ||
|
|
0e6c7d8af7 | ||
|
|
9c20085ca9 | ||
|
|
d48a52055a | ||
|
|
dc433e12fb | ||
|
|
1740ca6a16 | ||
|
|
2ae8ca1d63 | ||
|
|
674cd89ac9 | ||
|
|
6ed70add4a | ||
|
|
ae5ebccec7 | ||
|
|
19c8e23425 | ||
|
|
b878cd050d | ||
|
|
a7df00c07a | ||
|
|
3127f1adc6 | ||
|
|
a722cca80a | ||
|
|
0ffd78eab6 | ||
|
|
694c868048 | ||
|
|
2da2006e2a | ||
|
|
0bc83ca065 | ||
|
|
ab2643ef14 | ||
|
|
297812ec11 | ||
|
|
158f754f18 | ||
|
|
da3504105e | ||
|
|
d3f50695b4 | ||
|
|
5a9a6ab0f6 | ||
|
|
b86e78b7a9 | ||
|
|
b1cdf581d0 | ||
|
|
8bf20527be | ||
|
|
3eedc40595 | ||
|
|
93db0ef3e9 | ||
|
|
6922dbbc70 | ||
|
|
f1b9b27a15 | ||
|
|
a755558beb | ||
|
|
b8423d0f5f | ||
|
|
42ef4fbcc1 | ||
|
|
69c11780eb | ||
|
|
c925b43090 | ||
|
|
a5b97cbd9b | ||
|
|
bcb844663f | ||
|
|
0905ee293c | ||
|
|
3325852aab | ||
|
|
c437d99c5f | ||
|
|
cacafc63e8 | ||
|
|
b08f3383b8 | ||
|
|
2eccf67b1c | ||
|
|
293c8bef54 | ||
|
|
00c7ae0542 | ||
|
|
cd656faadf | ||
|
|
056b95ffa9 | ||
|
|
d211df1e73 | ||
|
|
934e036b99 | ||
|
|
9fc86f92fa | ||
|
|
49c6bd4141 | ||
|
|
24011cf2a5 | ||
|
|
83b284dfde | ||
|
|
7c9181317f | ||
|
|
01b410fe9c | ||
|
|
56ac98468d | ||
|
|
658ebeaa21 | ||
|
|
59aa898533 | ||
|
|
c88f87cee2 | ||
|
|
cc663bb08c | ||
|
|
63d647df18 | ||
|
|
e3a46cb6ce | ||
|
|
4556eb3a0c | ||
|
|
26ed9b7c58 | ||
|
|
8ff0b5423d | ||
|
|
0fbced95a8 | ||
|
|
66b816aabc | ||
|
|
af67c893d8 | ||
|
|
71f44d646f | ||
|
|
9edecffcc8 | ||
|
|
d2c93065d5 | ||
|
|
7dd02c1766 | ||
|
|
93a97950e7 | ||
|
|
f17698a8ea | ||
|
|
2cb9f81bab | ||
|
|
ed03818e20 | ||
|
|
cc531af665 | ||
|
|
0dbe78149d | ||
|
|
4bc31f4b2a | ||
|
|
a5253adb9c | ||
|
|
ae3700a193 | ||
|
|
a56604154d | ||
|
|
000f81b21c | ||
|
|
a9583fc6ec | ||
|
|
57eecd7497 | ||
|
|
6803cc4788 | ||
|
|
2796e54540 | ||
|
|
06c23b7742 | ||
|
|
7ce6181bce | ||
|
|
ec31bcbe62 | ||
|
|
0b555e1b2c | ||
|
|
ed21e77fb1 | ||
|
|
3f8b1fe05b | ||
|
|
8d4b6452d4 | ||
|
|
05e3dead7b | ||
|
|
3a01a63a01 | ||
|
|
624aa5290e | ||
|
|
8d9897d5a5 | ||
|
|
16a9975e84 | ||
|
|
755dd3d024 | ||
|
|
ba49fd4c18 | ||
|
|
08b6f6f4e4 | ||
|
|
3128b25236 | ||
|
|
5e054c9d31 | ||
|
|
4bb4a85037 | ||
|
|
b24d813f0f | ||
|
|
7e12918f75 | ||
|
|
11bb176a3f | ||
|
|
fcc3082231 | ||
|
|
49d94f5318 | ||
|
|
fa23026b80 | ||
|
|
0fa2d9c32c | ||
|
|
15a77fd2bb | ||
|
|
9c36ac28fa | ||
|
|
e1e622d985 | ||
|
|
3e86ebc3cf | ||
|
|
6d309b52a5 | ||
|
|
52faa01ecf | ||
|
|
a79c888e0c | ||
|
|
449175e3a6 | ||
|
|
2fce1a6d25 | ||
|
|
798b61c8ef | ||
|
|
84efd367d2 | ||
|
|
d9b0c4c84c | ||
|
|
c9300edead | ||
|
|
4502e8fffb | ||
|
|
51d82bece3 | ||
|
|
0e4f9acb6e | ||
|
|
aa2d8b20cd | ||
|
|
c63ebbdfc4 | ||
|
|
c094780aae | ||
|
|
4162dbc2d8 | ||
|
|
c250f75d1d | ||
|
|
af57fc3ece | ||
|
|
985abd1456 | ||
|
|
0375137296 | ||
|
|
e4956c5500 | ||
|
|
53377cdddc | ||
|
|
81c98c855f | ||
|
|
115a0bc560 | ||
|
|
2744e058b6 | ||
|
|
b6139f74de | ||
|
|
d925939795 | ||
|
|
d4842ebd90 | ||
|
|
5000a2e503 | ||
|
|
109988d105 | ||
|
|
b07bea40f7 | ||
|
|
e287f615f4 | ||
|
|
d2103dbf39 | ||
|
|
7a54d998d4 | ||
|
|
3168b2a1ed | ||
|
|
e0d2fa5701 | ||
|
|
4b4c799129 | ||
|
|
b2c8752211 | ||
|
|
4e9436eb80 | ||
|
|
8c133ef048 | ||
|
|
142879ec30 | ||
|
|
c4f79eff51 | ||
|
|
1dd448e65c | ||
|
|
ab3fed06c7 | ||
|
|
b4dbac1b84 | ||
|
|
e1b59c93de | ||
|
|
0adfd2751e | ||
|
|
fd2248e7c2 | ||
|
|
dd75392d98 | ||
|
|
af2b101fe2 | ||
|
|
62cef3de98 | ||
|
|
03e518f0ea | ||
|
|
7765bdd967 | ||
|
|
cd19d4262b | ||
|
|
4812ddff9f | ||
|
|
df52b51f67 | ||
|
|
a2e4f6cf68 | ||
|
|
ee728d58f5 | ||
|
|
6be6ade6d7 | ||
|
|
d4305ab9da | ||
|
|
ca478016c9 | ||
|
|
a7a2589e81 | ||
|
|
02e4f7305d | ||
|
|
f777ba8aa9 | ||
|
|
7e9eaf41c9 | ||
|
|
bb9b3163ee | ||
|
|
e6d1de0d72 | ||
|
|
e2a660c787 | ||
|
|
c9a5c03eaa | ||
|
|
c2eda0a172 | ||
|
|
c470982ce5 | ||
|
|
68f6b0be6e | ||
|
|
02f379536c | ||
|
|
e239d5f909 | ||
|
|
47c965481f | ||
|
|
3af0f9776f | ||
|
|
c116a28e67 | ||
|
|
6a10654618 | ||
|
|
a42b0bd574 | ||
|
|
404884e295 | ||
|
|
e17d303392 | ||
|
|
e4205c125c | ||
|
|
fdee15e523 | ||
|
|
c9d903cc36 | ||
|
|
e5a0a12ffd | ||
|
|
78cdff6d09 | ||
|
|
a64baed428 | ||
|
|
d8f3bffe63 | ||
|
|
a09b42b364 | ||
|
|
fe67bcdb8b | ||
|
|
7dc1eae40f | ||
|
|
e13896496e | ||
|
|
f864c912ad | ||
|
|
b28aaae66b | ||
|
|
fb872be04a | ||
|
|
8f413f523c | ||
|
|
89243aed37 | ||
|
|
f212deab4d | ||
|
|
79906d73d0 | ||
|
|
d4e3cd31a4 | ||
|
|
f621543d9c | ||
|
|
e801b3a75d | ||
|
|
5f93266e2c | ||
|
|
9b6f8f0c74 | ||
|
|
a352ff3923 | ||
|
|
812ae77257 | ||
|
|
b4efc833c7 | ||
|
|
5e33ac4a09 | ||
|
|
72f565d55d | ||
|
|
b92ee25696 | ||
|
|
a2d4423630 | ||
|
|
897d434673 | ||
|
|
0df5883853 | ||
|
|
84c5e44345 | ||
|
|
aafc23a615 | ||
|
|
49bd56d012 | ||
|
|
45901219b7 | ||
|
|
6ba6f305cc | ||
|
|
5653ae69e4 | ||
|
|
31534fe47d | ||
|
|
3a85fcd365 | ||
|
|
f9c631e9ee | ||
|
|
621bb7c6c5 | ||
|
|
9590eaf342 | ||
|
|
939de0cdbe | ||
|
|
c8be17c91f | ||
|
|
c6476d16e7 | ||
|
|
14668f794d | ||
|
|
efcf8757b0 | ||
|
|
ac9f2ded6e | ||
|
|
c836de5ca8 | ||
|
|
2aa7e30aff | ||
|
|
c724cb7178 | ||
|
|
858c7493df | ||
|
|
8b433b0ff9 | ||
|
|
5614649d14 | ||
|
|
9a4cb6c991 | ||
|
|
2a090e9118 | ||
|
|
7fa02ce5b3 | ||
|
|
44ac9a9f44 | ||
|
|
95e4cc1aec | ||
|
|
fa4dc14c97 | ||
|
|
69f59bfb2a | ||
|
|
eb2bdc3105 | ||
|
|
e079c20ceb | ||
|
|
27324c8236 | ||
|
|
2e71a3b862 | ||
|
|
06acd3caa9 | ||
|
|
4df576869f | ||
|
|
61d46c26b8 | ||
|
|
8eee69bd8f | ||
|
|
e4159d9411 | ||
|
|
7ab4d284ee | ||
|
|
3e6ee23a17 | ||
|
|
faaf600276 | ||
|
|
ca6228b526 | ||
|
|
16924d7913 | ||
|
|
540e4023da | ||
|
|
2ecb2e3c80 | ||
|
|
2675bf4b73 | ||
|
|
01df12cf3c | ||
|
|
7295a9b32e | ||
|
|
607eb13a52 | ||
|
|
2d70526eab | ||
|
|
e29261033f | ||
|
|
9390860288 | ||
|
|
c6764ab31f | ||
|
|
2825888ffd | ||
|
|
34e8de3fc8 | ||
|
|
86f0f9a435 | ||
|
|
03ad8efcba | ||
|
|
bcc7412ef2 | ||
|
|
c1e2b27c60 | ||
|
|
8c5d4128e0 | ||
|
|
f2295acfdd | ||
|
|
529db0493b | ||
|
|
a8c476f7c0 | ||
|
|
f38d0c690c | ||
|
|
e42933ec54 | ||
|
|
ad7ca6977b | ||
|
|
0045d7b716 | ||
|
|
64bd069290 | ||
|
|
70def86613 | ||
|
|
cff5349426 | ||
|
|
54d1c557b2 | ||
|
|
a889f97fd1 | ||
|
|
9ffe20a18b | ||
|
|
9bd456c6df | ||
|
|
a43dffdeda | ||
|
|
61cf67fb95 | ||
|
|
09f2157a92 | ||
|
|
ca4aea173c | ||
|
|
76d370a8f2 | ||
|
|
f4364eb990 | ||
|
|
865348695f | ||
|
|
2ec8189c1c | ||
|
|
a5dfc65440 | ||
|
|
45302f0790 | ||
|
|
1afc6c775b | ||
|
|
ca4d5ed42b | ||
|
|
cd9572e0bb | ||
|
|
ac3ea4d6f3 | ||
|
|
030824b196 | ||
|
|
cfb0c5efad | ||
|
|
b67d713bc0 | ||
|
|
0ac48f60a5 | ||
|
|
445f739234 | ||
|
|
fc8063f752 | ||
|
|
d5abe39d53 | ||
|
|
520acc7d97 | ||
|
|
ae0510f648 | ||
|
|
0f50be877c | ||
|
|
5b18c4de0c | ||
|
|
72fd31fd20 | ||
|
|
256ffdb932 | ||
|
|
8991bcb399 | ||
|
|
185ae510e8 | ||
|
|
40303cb329 | ||
|
|
38ec207609 | ||
|
|
1545e07dd6 | ||
|
|
f123380917 | ||
|
|
b4fc1e4357 | ||
|
|
76a3179868 | ||
|
|
c9bf70fd4b | ||
|
|
9a85071085 | ||
|
|
a6f41bb96d | ||
|
|
3f2acc90aa | ||
|
|
aba9f7d1e5 | ||
|
|
3b8c5ee96d | ||
|
|
a5bb5479fb | ||
|
|
3c58c9d132 | ||
|
|
1b1f91580e | ||
|
|
644dc4b9a7 | ||
|
|
96707645e2 | ||
|
|
b878e5f10d | ||
|
|
a914570240 | ||
|
|
b3d2ab29e9 | ||
|
|
3ff5c793e3 | ||
|
|
c752660aa6 | ||
|
|
efded10e26 | ||
|
|
8767495b5a | ||
|
|
403ede788c | ||
|
|
c444f93eb5 | ||
|
|
ed146f656e | ||
|
|
bcb697eb0b | ||
|
|
3ac66049c7 | ||
|
|
7a1a231041 | ||
|
|
748c88c276 | ||
|
|
6f4b104c9e | ||
|
|
867201a075 | ||
|
|
2545ea1019 | ||
|
|
5be42092af | ||
|
|
50c076eb3f | ||
|
|
fb9e00bf33 | ||
|
|
b9007fcc29 | ||
|
|
f6e01cfda7 | ||
|
|
9203478a8a | ||
|
|
28cb6daec7 | ||
|
|
e191ff53dd | ||
|
|
177297c0ef | ||
|
|
e5d730e1fe | ||
|
|
ba43ecbcb7 | ||
|
|
ee68a9c450 | ||
|
|
175c754f61 | ||
|
|
e8eed838b5 | ||
|
|
e9a3f9f5f6 | ||
|
|
826affb8dd | ||
|
|
4937b1c75e | ||
|
|
ffc16d51e0 | ||
|
|
1623f1e4c0 | ||
|
|
b32e041bfe | ||
|
|
38029d1836 | ||
|
|
b2dd74ab97 | ||
|
|
16fe7ced6a | ||
|
|
cb4af7a9d4 | ||
|
|
7493732176 | ||
|
|
2cf8371add | ||
|
|
a575c24a24 | ||
|
|
9e8d06e7ce | ||
|
|
4f1a2350ce | ||
|
|
cefb64b6a9 | ||
|
|
440d036176 | ||
|
|
53f0deec8f | ||
|
|
3c495e3b23 | ||
|
|
deaf0779a1 | ||
|
|
fd7a353df6 | ||
|
|
927b497feb | ||
|
|
237c54f47e | ||
|
|
8c23db47a7 | ||
|
|
7971ac1cb8 | ||
|
|
2490e605c3 | ||
|
|
21a0cba43e | ||
|
|
42d9287985 | ||
|
|
4848987a1f | ||
|
|
53a22cbe9b | ||
|
|
c3700e0c88 | ||
|
|
58d9a51040 | ||
|
|
8f395ad86f | ||
|
|
99391157ec | ||
|
|
99406d4412 | ||
|
|
c1dea6676f | ||
|
|
afa4664511 | ||
|
|
267eec5509 | ||
|
|
9764eb2f83 | ||
|
|
9a12b55139 | ||
|
|
f8cffef977 | ||
|
|
822420e4ab | ||
|
|
b60fca05bd | ||
|
|
1a35071672 | ||
|
|
bfc3655bad | ||
|
|
2c0c0c9497 | ||
|
|
46bd38e89d | ||
|
|
9fc4d388ce | ||
|
|
2965134f89 | ||
|
|
3a7c8a03f4 | ||
|
|
449b1b68e0 | ||
|
|
dd59eb38d0 | ||
|
|
a8465c95e1 | ||
|
|
df2f67b191 | ||
|
|
7764dee59d | ||
|
|
6465a36176 | ||
|
|
103c1b3a4f | ||
|
|
29cbec37b8 | ||
|
|
2c9e4507a7 | ||
|
|
d5d5c076a7 | ||
|
|
f09bbff35c | ||
|
|
2627e2507b | ||
|
|
1bd7afe6e7 | ||
|
|
e6c1b14108 | ||
|
|
7130e3ff1d | ||
|
|
abf538d80d | ||
|
|
f311ba8d4f | ||
|
|
3e85c4589b | ||
|
|
d0cf047381 | ||
|
|
31091a8df2 | ||
|
|
e207ae4c01 | ||
|
|
3011f18047 | ||
|
|
388d5c2d7c | ||
|
|
5c4719651e | ||
|
|
f850ca63f4 | ||
|
|
65886f1258 | ||
|
|
942e36e19f | ||
|
|
7b82154c4c | ||
|
|
284efc709c | ||
|
|
56965a0046 | ||
|
|
18f6328284 | ||
|
|
e7be999bc9 | ||
|
|
c06b95077d | ||
|
|
c4da063934 | ||
|
|
62d3200e4f | ||
|
|
9a419824ae | ||
|
|
a94eab0398 | ||
|
|
01b8ab8524 | ||
|
|
fa552d7773 | ||
|
|
7acbd4d3e0 | ||
|
|
9811123e2e | ||
|
|
f7cd44be42 | ||
|
|
9a4692e6ee | ||
|
|
3d0e29075d | ||
|
|
0f571b9120 | ||
|
|
3a44508d6f | ||
|
|
5294355c98 | ||
|
|
559efd6477 | ||
|
|
a59577c08d | ||
|
|
4f429d6b86 | ||
|
|
5e7ddc8616 | ||
|
|
a6f6514412 | ||
|
|
31ee38b1a1 | ||
|
|
46c7d6d39a | ||
|
|
78cbff16ef | ||
|
|
28cefb3bd1 | ||
|
|
e666630d36 | ||
|
|
ed2ad860c6 | ||
|
|
a341c24b2a | ||
|
|
0101d6e393 | ||
|
|
e429d8ca10 | ||
|
|
45ba785641 | ||
|
|
ea3d5e68db | ||
|
|
eb75203926 | ||
|
|
1303ea3969 | ||
|
|
bb69e9e70b | ||
|
|
16d1b20ed6 | ||
|
|
b0c3cd75e1 | ||
|
|
f4eef1dc0b | ||
|
|
76c6655520 | ||
|
|
d5c8734555 | ||
|
|
62ee138173 | ||
|
|
ff6cd3ca55 | ||
|
|
852e7ed5aa | ||
|
|
b7e8042a02 | ||
|
|
6bfd7cff72 | ||
|
|
9d77cbea8c | ||
|
|
8bbe147c14 | ||
|
|
b67179e951 | ||
|
|
47237aa7a2 | ||
|
|
5e6c06fb61 | ||
|
|
901455eb0b | ||
|
|
f8c80b7335 | ||
|
|
8db82d27e9 | ||
|
|
1eab47b63f | ||
|
|
c4656b71e5 | ||
|
|
711d3d3515 | ||
|
|
0488f570cb | ||
|
|
0e459ebac8 | ||
|
|
1d65f24b04 | ||
|
|
70719a8f65 | ||
|
|
773c1f2199 | ||
|
|
bf1c801a5e | ||
|
|
e1420b408c | ||
|
|
88a7471039 | ||
|
|
3c58cf0bf0 | ||
|
|
77eebb89fd | ||
|
|
d4d993a53c | ||
|
|
ef3b0672c5 | ||
|
|
0f30c09cbf | ||
|
|
6f670a8f38 | ||
|
|
8c93f7ba74 | ||
|
|
7d3735b19e | ||
|
|
f5f8e49fa3 | ||
|
|
1382d87d7f | ||
|
|
e65b45f969 | ||
|
|
d72b16235a | ||
|
|
3118ccfd05 | ||
|
|
fdba7df3c1 | ||
|
|
02d753027a | ||
|
|
1a43759ac3 | ||
|
|
7574f07be3 | ||
|
|
48717f3f30 | ||
|
|
74d3e92b55 | ||
|
|
f66024b1c1 | ||
|
|
bf4e09a400 | ||
|
|
d968c0b4b1 | ||
|
|
9837b5b429 | ||
|
|
1a03dcabde | ||
|
|
6fb11493ad | ||
|
|
1f063d6712 | ||
|
|
cebcd8a44d | ||
|
|
ce7e5ee2c3 | ||
|
|
242b9209d8 | ||
|
|
92bd809bc8 | ||
|
|
ccc4461827 | ||
|
|
9de524da7d | ||
|
|
7c8db24656 | ||
|
|
d720e9ef49 | ||
|
|
9e69b9dcc4 | ||
|
|
4f7b0c1a21 | ||
|
|
fc5c7264cf | ||
|
|
ede407e6a2 | ||
|
|
e41ca934ac | ||
|
|
0184d5b697 | ||
|
|
e14ebc0adf | ||
|
|
e905704b0c | ||
|
|
f8060f3575 | ||
|
|
120e7b5744 | ||
|
|
b15b20467c | ||
|
|
1d005d47b5 | ||
|
|
d8585334cc | ||
|
|
6444981796 | ||
|
|
713c06354f | ||
|
|
5e2c947cf8 | ||
|
|
92ede0d3c9 | ||
|
|
84057436d6 | ||
|
|
bf18cf2d9e | ||
|
|
7913e5f5bc | ||
|
|
f550540318 | ||
|
|
1d263d24dd | ||
|
|
199763dec8 | ||
|
|
093801479c | ||
|
|
cdcc0052a6 | ||
|
|
b6d4101808 | ||
|
|
83f4fa2190 | ||
|
|
35daae1715 | ||
|
|
1997606372 | ||
|
|
209b115b7c | ||
|
|
93515517b8 | ||
|
|
8c2bff2c91 | ||
|
|
00e9657025 | ||
|
|
31691f0330 | ||
|
|
bb74a42e04 | ||
|
|
731a2a683e | ||
|
|
95fd0c5530 | ||
|
|
79d357b460 | ||
|
|
3ddc20f72c | ||
|
|
4179b8e6c8 | ||
|
|
da23b6fce1 | ||
|
|
feb0502cb4 | ||
|
|
af8a169619 | ||
|
|
68ca28879d | ||
|
|
ad400cd13d | ||
|
|
90b863d124 | ||
|
|
9b7ddd6684 | ||
|
|
bcb939c19d | ||
|
|
e2102dec3c | ||
|
|
2ef16dce90 | ||
|
|
1d9a20b391 | ||
|
|
d953eca630 | ||
|
|
6651e488d6 | ||
|
|
fd3cd47562 | ||
|
|
dcb5682594 | ||
|
|
0bd2a1e232 | ||
|
|
4d77c9f940 | ||
|
|
c461f1f766 | ||
|
|
fbcae2b770 | ||
|
|
c21dfefbdf | ||
|
|
a0c22b8216 | ||
|
|
f7a59178a8 | ||
|
|
028724df08 | ||
|
|
51bbf57e95 | ||
|
|
3e4ec3a12c | ||
|
|
a9dff35a24 | ||
|
|
1cf8156c0c | ||
|
|
f1b8fdec7f | ||
|
|
62d096e57d | ||
|
|
e49bd3ab1d | ||
|
|
1edfdea5e8 | ||
|
|
d141b2421c | ||
|
|
244b3a2c59 | ||
|
|
2e6bdd4041 | ||
|
|
077d554b76 | ||
|
|
4e058f8ece | ||
|
|
d4b90c8f4e | ||
|
|
fed8195eb2 | ||
|
|
b22e43a4a7 | ||
|
|
2c1be17fe7 | ||
|
|
56ce6b8ba4 | ||
|
|
78bac973f7 | ||
|
|
bfd61a7605 | ||
|
|
c40e3d12e8 | ||
|
|
7568531118 | ||
|
|
87411590c5 | ||
|
|
d4170797ae | ||
|
|
6616b25d66 | ||
|
|
6d56c1750f | ||
|
|
4e2c4b94e3 | ||
|
|
0be9815d27 | ||
|
|
f7c34ccb52 | ||
|
|
549290c447 | ||
|
|
2fa4a34589 | ||
|
|
14af70d148 | ||
|
|
6dd599a983 | ||
|
|
176ad74a1c | ||
|
|
38f0ca9f03 | ||
|
|
4d60f9229b | ||
|
|
ea1dd08a8c | ||
|
|
73624da253 | ||
|
|
78a2884b79 | ||
|
|
e24968c679 | ||
|
|
60dc3aa09d | ||
|
|
94be867a54 | ||
|
|
b9ab948ef2 | ||
|
|
a5c6938c65 | ||
|
|
5d0c7aa6a9 | ||
|
|
032fffe111 | ||
|
|
1b726b26cd | ||
|
|
50a8b27854 | ||
|
|
aeb2adbcfb | ||
|
|
746c7b0b5b | ||
|
|
93ad371400 | ||
|
|
d98afdc229 | ||
|
|
80f3afc1ff | ||
|
|
b16a7364fd | ||
|
|
4b543169c8 | ||
|
|
b7e1f8da72 | ||
|
|
97507a92a3 | ||
|
|
a838b85426 | ||
|
|
92fc368ede | ||
|
|
8c994725cb | ||
|
|
20b13a03e0 | ||
|
|
9dcbee1d48 | ||
|
|
77ac84c468 | ||
|
|
8b76799dd9 | ||
|
|
bab0421c6c | ||
|
|
95203c58c4 | ||
|
|
ad7c90b904 | ||
|
|
4583c4a9de | ||
|
|
428cef54c1 | ||
|
|
037f898f81 | ||
|
|
541754df8d | ||
|
|
1cd99ab68e | ||
|
|
6f566d7a38 | ||
|
|
4b25963c93 | ||
|
|
900152f724 | ||
|
|
048c1dde97 | ||
|
|
949fd51463 | ||
|
|
dd17174b35 | ||
|
|
0d14e89549 | ||
|
|
64372a786b | ||
|
|
819b2e99d0 | ||
|
|
d8e703d0f5 | ||
|
|
47a04f2648 | ||
|
|
4af125fa2d | ||
|
|
0665371590 | ||
|
|
ecb83c6ae1 | ||
|
|
c4264daf6a | ||
|
|
243cbae411 | ||
|
|
186dd20ad6 | ||
|
|
d4ba22191a | ||
|
|
29694e5b6a | ||
|
|
a99f415f36 | ||
|
|
54d6cf7087 | ||
|
|
e487e494f9 | ||
|
|
11a19906b9 | ||
|
|
65100e13b3 | ||
|
|
3b52fd5019 | ||
|
|
20e37eaf65 | ||
|
|
b6b2fb9c62 | ||
|
|
b971e6a1da | ||
|
|
087c4976b6 | ||
|
|
d0e4db74b7 | ||
|
|
f7414fec08 | ||
|
|
8cecccbc88 | ||
|
|
441f45e1cc | ||
|
|
48e8a3aec3 | ||
|
|
3fe07888ce | ||
|
|
77b44f570a | ||
|
|
352ec69556 | ||
|
|
bd83773a1e | ||
|
|
bf8b435457 | ||
|
|
e9b19281b2 | ||
|
|
986c85e728 | ||
|
|
8e72931a8b | ||
|
|
aeda4172e4 | ||
|
|
d40f52e953 | ||
|
|
3e47a2c0a7 | ||
|
|
3b9cd6bedd | ||
|
|
0759136d3f | ||
|
|
f0f8aad2bb | ||
|
|
259cecd4b8 | ||
|
|
38873aa0fa | ||
|
|
506c4a330d | ||
|
|
63e4e5ccaa | ||
|
|
cbcc976828 | ||
|
|
1ce1b82f6f | ||
|
|
b92e9ab075 | ||
|
|
cfdae37ef5 | ||
|
|
108b3e497b | ||
|
|
bff228815f | ||
|
|
252cf3723c | ||
|
|
5b88179406 | ||
|
|
6ad5d9f55b | ||
|
|
8a8331bf57 | ||
|
|
931f43f8d7 | ||
|
|
3c1e52bf94 | ||
|
|
a62d15e746 | ||
|
|
8f701a0041 | ||
|
|
593f9231ae | ||
|
|
59df8a0dda | ||
|
|
6b93973bad | ||
|
|
df3ec571fb | ||
|
|
f03fad7a96 | ||
|
|
f714e9faf3 | ||
|
|
3e1a3d83da | ||
|
|
8ba2ea9ca7 | ||
|
|
7dc8ef1028 | ||
|
|
ef51cceff5 | ||
|
|
a40c2502de | ||
|
|
0201f9cba8 | ||
|
|
7c1277f24c | ||
|
|
29f03dfb55 | ||
|
|
02d2d38c21 | ||
|
|
6757efe290 | ||
|
|
5990af8ced | ||
|
|
6cddc25f0e | ||
|
|
8bd773b536 | ||
|
|
d9ba209543 | ||
|
|
c51646e3db | ||
|
|
4f9d00c021 | ||
|
|
0042c4be54 | ||
|
|
910eb322e0 | ||
|
|
064b768176 | ||
|
|
4daa655516 | ||
|
|
d6910e9788 | ||
|
|
eed16afb00 | ||
|
|
6ec77b27da | ||
|
|
621ed52bab | ||
|
|
b8c2ed20d1 | ||
|
|
19ad9c2d46 | ||
|
|
41cc210fa0 | ||
|
|
3488b542ac | ||
|
|
04a030bcf0 | ||
|
|
25415c5501 | ||
|
|
a094be45d9 | ||
|
|
fdb8a7d74a | ||
|
|
d481dbad62 | ||
|
|
c1e7f1b957 | ||
|
|
93094c78eb | ||
|
|
a14609f730 | ||
|
|
a8984578e4 | ||
|
|
51e9fec65d | ||
|
|
38b7f42f9e | ||
|
|
e574948577 | ||
|
|
ebf1efe07e | ||
|
|
83bc67c8ad | ||
|
|
1648eceb47 | ||
|
|
538aaaf217 | ||
|
|
5b35443533 | ||
|
|
e089a56e05 | ||
|
|
5c4a778e6a | ||
|
|
e0ec8028eb | ||
|
|
578ff2e45c | ||
|
|
d04877a9e7 | ||
|
|
727b5ebd7f | ||
|
|
af1e1e6942 | ||
|
|
d05bb6b199 | ||
|
|
ba953484bf | ||
|
|
fdf78b1d7d | ||
|
|
95fed47c1c | ||
|
|
4cf916e6f4 | ||
|
|
23bf326d93 | ||
|
|
bcd4d185a7 | ||
|
|
57a5fa593c | ||
|
|
421ca3fb3c | ||
|
|
29945c2c7a | ||
|
|
9d82d54c5b | ||
|
|
4827d9984f | ||
|
|
d718fe3ee1 | ||
|
|
a8fbe35ecf | ||
|
|
5947f696ff | ||
|
|
40e0bbeec2 | ||
|
|
ecbf3c5f51 | ||
|
|
dfa952f0d5 | ||
|
|
793577d044 | ||
|
|
1224dc0c87 | ||
|
|
885c1952a4 | ||
|
|
383b3e798b | ||
|
|
1020167e22 | ||
|
|
3c242f58da | ||
|
|
f8a18ce662 | ||
|
|
6a917c5f36 | ||
|
|
7af1f206aa | ||
|
|
0714abfe79 | ||
|
|
6037734641 | ||
|
|
76ba38cec5 | ||
|
|
5c918dc56a | ||
|
|
292ed0e605 | ||
|
|
e97960c2f0 | ||
|
|
9f73341271 | ||
|
|
163515c5a0 | ||
|
|
41845522f6 |
7
.gitignore
vendored
7
.gitignore
vendored
@@ -4,7 +4,9 @@
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
kustomize
|
||||
|
||||
.idea
|
||||
*.iml
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
@@ -14,3 +16,6 @@ kustomize
|
||||
|
||||
# We use sed -i.bak when doing in-line replace, because it works better cross-platform
|
||||
.bak
|
||||
|
||||
# macOS
|
||||
*.DS_store
|
||||
|
||||
43
.golangci-kustomize.yml
Normal file
43
.golangci-kustomize.yml
Normal file
@@ -0,0 +1,43 @@
|
||||
run:
|
||||
deadline: 5m
|
||||
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- bodyclose
|
||||
- deadcode
|
||||
- depguard
|
||||
- dupl
|
||||
- goconst
|
||||
- gocyclo
|
||||
- gofmt
|
||||
- goimports
|
||||
- golint
|
||||
- gosec
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- interfacer
|
||||
- lll
|
||||
- misspell
|
||||
- nakedret
|
||||
- staticcheck
|
||||
- structcheck
|
||||
# stylecheck demands that acronyms not be treated as words
|
||||
# in camelCase, so JsonOp become JSONOp, etc. Yuck.
|
||||
# - stylecheck
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unused
|
||||
- unparam
|
||||
- varcheck
|
||||
|
||||
linters-settings:
|
||||
dupl:
|
||||
threshold: 400
|
||||
lll:
|
||||
line-length: 170
|
||||
gocyclo:
|
||||
min-complexity: 15
|
||||
golint:
|
||||
min-confidence: 0.85
|
||||
@@ -1,30 +0,0 @@
|
||||
run:
|
||||
deadline: 5m
|
||||
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- dupl
|
||||
- goconst
|
||||
- gocyclo
|
||||
- gofmt
|
||||
- golint
|
||||
- govet
|
||||
- ineffassign
|
||||
- interfacer
|
||||
- lll
|
||||
- misspell
|
||||
- nakedret
|
||||
- structcheck
|
||||
- unparam
|
||||
- varcheck
|
||||
|
||||
linters-settings:
|
||||
dupl:
|
||||
threshold: 400
|
||||
lll:
|
||||
line-length: 170
|
||||
gocyclo:
|
||||
min-complexity: 15
|
||||
golint:
|
||||
min-confidence: 0.85
|
||||
47
.travis.yml
47
.travis.yml
@@ -1,34 +1,41 @@
|
||||
language: go
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
# TODO: Speed up the slowness of the osx travis runs
|
||||
# Maybe cache brew installs?
|
||||
#
|
||||
# TODO: Uncomment when some gets the tests running on Windows.
|
||||
# - windows
|
||||
|
||||
go:
|
||||
- 1.11.x
|
||||
|
||||
go_import_path: sigs.k8s.io/kustomize
|
||||
|
||||
# Maybe, maybe not.
|
||||
# sudo: false
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- tree
|
||||
homebrew:
|
||||
packages:
|
||||
- tree
|
||||
update: true
|
||||
|
||||
# Only clone the most recent commit.
|
||||
git:
|
||||
depth: 1
|
||||
|
||||
env:
|
||||
- GOLANGCI_RELEASE="v1.10.2"
|
||||
language: go
|
||||
|
||||
go:
|
||||
- "1.13"
|
||||
|
||||
go_import_path: sigs.k8s.io/kustomize
|
||||
|
||||
before_install:
|
||||
- source ./bin/consider-early-travis-exit.sh
|
||||
- sudo apt-get install tree
|
||||
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $GOPATH/bin ${GOLANGCI_RELEASE}
|
||||
- go get -u github.com/monopole/mdrip
|
||||
- source ./travis/consider-early-travis-exit.sh
|
||||
|
||||
# Install must be set to prevent default `go get` to run.
|
||||
# The dependencies have already been vendored by `dep` so
|
||||
# we don't need to fetch them.
|
||||
install:
|
||||
-
|
||||
# Skip the install process; let pre-commit.sh do it.
|
||||
install: true
|
||||
|
||||
script:
|
||||
- ./bin/pre-commit.sh
|
||||
- make verify-kustomize
|
||||
- ./travis/kyaml-pre-commit.sh
|
||||
|
||||
# TBD. Suppressing for now.
|
||||
notifications:
|
||||
|
||||
@@ -6,11 +6,15 @@ _As contributors and maintainers of this project, and in the interest of fosteri
|
||||
|
||||
## Getting Started
|
||||
|
||||
Dev guides:
|
||||
|
||||
- [Mac](docs/macDevGuide.md)
|
||||
|
||||
We have full documentation on how to get started contributing here:
|
||||
|
||||
- [Contributor License Agreement](https://git.k8s.io/community/CLA.md) Kubernetes projects require that you sign a Contributor License Agreement (CLA) before we can accept your pull requests
|
||||
- [Kubernetes Contributor Guide](http://git.k8s.io/community/contributors/guide) - Main contributor documentation, or you can just jump directly to the [contributing section](http://git.k8s.io/community/contributors/guide#contributing)
|
||||
- [Contributor Cheat Sheet](https://git.k8s.io/community/contributors/guide/contributor-cheatsheet.md) - Common resources for existing developers
|
||||
- [Contributor Cheat Sheet](https://git.k8s.io/community/contributors/guide/contributor-cheatsheet/README.md) - Common resources for existing developers
|
||||
|
||||
## Mentorship
|
||||
|
||||
|
||||
517
Gopkg.lock
generated
517
Gopkg.lock
generated
@@ -1,517 +0,0 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
digest = "1:8e47871087b94913898333f37af26732faaab30cdb41571136cf7aec9921dae7"
|
||||
name = "github.com/PuerkitoBio/purell"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "0bcb03f4b4d0a9428594752bd2a3b9aa0a9d4bd4"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:331a419049c2be691e5ba1d24342fc77c7e767a80c666a18fd8a9f7b82419c1c"
|
||||
name = "github.com/PuerkitoBio/urlesc"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "de5bf2ad457846296e2031421a34e2568e304e35"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:4905d4dce09d1a9e93fe2b9733f92bee5f149dd229727bbc074465b24fb489cd"
|
||||
name = "github.com/aws/aws-sdk-go"
|
||||
packages = [
|
||||
"aws",
|
||||
"aws/awserr",
|
||||
"aws/awsutil",
|
||||
"aws/client",
|
||||
"aws/client/metadata",
|
||||
"aws/corehandlers",
|
||||
"aws/credentials",
|
||||
"aws/credentials/ec2rolecreds",
|
||||
"aws/credentials/endpointcreds",
|
||||
"aws/credentials/stscreds",
|
||||
"aws/csm",
|
||||
"aws/defaults",
|
||||
"aws/ec2metadata",
|
||||
"aws/endpoints",
|
||||
"aws/request",
|
||||
"aws/session",
|
||||
"aws/signer/v4",
|
||||
"internal/sdkio",
|
||||
"internal/sdkrand",
|
||||
"internal/sdkuri",
|
||||
"internal/shareddefaults",
|
||||
"private/protocol",
|
||||
"private/protocol/eventstream",
|
||||
"private/protocol/eventstream/eventstreamapi",
|
||||
"private/protocol/query",
|
||||
"private/protocol/query/queryutil",
|
||||
"private/protocol/rest",
|
||||
"private/protocol/restxml",
|
||||
"private/protocol/xml/xmlutil",
|
||||
"service/s3",
|
||||
"service/sts",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "fde4ded7becdeae4d26bf1212916aabba79349b4"
|
||||
version = "v1.14.12"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:98e84060475ed245c3b355042afd43a74aa7d32efe50658f4f995977916f9fc3"
|
||||
name = "github.com/bgentry/go-netrc"
|
||||
packages = ["netrc"]
|
||||
pruneopts = ""
|
||||
revision = "9fd32a8b3d3d3f9d43c341bfe098430e07609480"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:56c130d885a4aacae1dd9c7b71cfe39912c7ebc1ff7d2b46083c8812996dc43b"
|
||||
name = "github.com/davecgh/go-spew"
|
||||
packages = ["spew"]
|
||||
pruneopts = ""
|
||||
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:971e9ba63a417c5f1f83ab358677bc59e96ff04285f26c6646ff089fb60b15e8"
|
||||
name = "github.com/emicklei/go-restful"
|
||||
packages = [
|
||||
".",
|
||||
"log",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "3658237ded108b4134956c1b3050349d93e7b895"
|
||||
version = "v2.7.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:dcefbadf4534c5ecac8573698fba6e6e601157bfa8f96aafe29df31ae582ef2a"
|
||||
name = "github.com/evanphx/json-patch"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "afac545df32f2287a079e2dfb7ba2745a643747e"
|
||||
version = "v3.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:b13707423743d41665fd23f0c36b2f37bb49c30e94adb813319c44188a51ba22"
|
||||
name = "github.com/ghodss/yaml"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "0ca9ea5df5451ffdf184b4428c902747c2c11cd7"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:858b7fe7b0f4bc7ef9953926828f2816ea52d01a88d72d1c45bc8c108f23c356"
|
||||
name = "github.com/go-ini/ini"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "358ee7663966325963d4e8b2e1fbd570c5195153"
|
||||
version = "v1.38.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:e116a4866bffeec941056a1fcfd37e520fad1ee60e4e3579719f19a43c392e10"
|
||||
name = "github.com/go-openapi/jsonpointer"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "3a0015ad55fa9873f41605d3e8f28cd279c32ab2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:3830527ef0f4f9b268d9286661c0f52f9115f8aefd9f45ee7352516f93489ac9"
|
||||
name = "github.com/go-openapi/jsonreference"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "3fb327e6747da3043567ee86abd02bb6376b6be2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:238a056875c4b053b4b29984765ee335bf8c539fdf17e527fd9b7aa72521c8dd"
|
||||
name = "github.com/go-openapi/spec"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "bcff419492eeeb01f76e77d2ebc714dc97b607f5"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:7b067ca8b94982960860d18c42e29f15bbd0e8d9ae8145a83a218296e75393cf"
|
||||
name = "github.com/go-openapi/swag"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "811b1089cde9dad18d4d0c2d09fbdbf28dbd27a5"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:0a3f6a0c68ab8f3d455f8892295503b179e571b7fefe47cc6c556405d1f83411"
|
||||
name = "github.com/gogo/protobuf"
|
||||
packages = [
|
||||
"proto",
|
||||
"sortkeys",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "1adfc126b41513cc696b209667c8656ea7aac67c"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:107b233e45174dbab5b1324201d092ea9448e58243ab9f039e4c0f332e121e3a"
|
||||
name = "github.com/golang/glog"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "23def4e6c14b4da8ac2ed8007337bc5eb5007998"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:f958a1c137db276e52f0b50efee41a1a389dcdded59a69711f3e872757dab34b"
|
||||
name = "github.com/golang/protobuf"
|
||||
packages = [
|
||||
"proto",
|
||||
"ptypes",
|
||||
"ptypes/any",
|
||||
"ptypes/duration",
|
||||
"ptypes/timestamp",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "b4deda0973fb4c70b50d226b1af49f3da59f5265"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:754f77e9c839b24778a4b64422236d38515301d2baeb63113aa3edc42e6af692"
|
||||
name = "github.com/google/gofuzz"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "24818f796faf91cd76ec7bddd72458fbced7a6c1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:2a131706ff80636629ab6373f2944569b8252ecc018cda8040931b05d32e3c16"
|
||||
name = "github.com/googleapis/gnostic"
|
||||
packages = [
|
||||
"OpenAPIv2",
|
||||
"compiler",
|
||||
"extensions",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "ee43cbb60db7bd22502942cccbc39059117352ab"
|
||||
version = "v0.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:f5d25fd7bdda08e39e01193ef94a1ebf7547b1b931bcdec785d08050598f306c"
|
||||
name = "github.com/hashicorp/go-cleanhttp"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "d5fe4b57a186c716b0e00b8c301cbd9b4182694d"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:fd15b3f6aac9d0fe68c6e38922282e0d2e88cd77b927ac3dd842e363645522c0"
|
||||
name = "github.com/hashicorp/go-getter"
|
||||
packages = [
|
||||
".",
|
||||
"helper/url",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "4bda8fa99001c61db3cad96b421d4c12a81f256d"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:2cf6c60c74eacadd31652674364af55c8d54a86b8ea193548f1c37f8c9af8f9c"
|
||||
name = "github.com/hashicorp/go-safetemp"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "b1a1dbde6fdc11e3ae79efd9039009e22d4ae240"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:139bdc2c89779b8ff8b1150be28f889b0ed964e6da96f32cbc9035bd4642881c"
|
||||
name = "github.com/hashicorp/go-version"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "270f2f71b1ee587f3b609f00f422b76a6b28f348"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be"
|
||||
name = "github.com/inconshreveable/mousetrap"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
|
||||
version = "v1.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:6f49eae0c1e5dab1dafafee34b207aeb7a42303105960944828c2079b92fc88e"
|
||||
name = "github.com/jmespath/go-jmespath"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "0b12d6b5"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:9eab2325abbed0ebcee9d44bb3660a69d5d10e42d5ac4a0e77f7a6ea22bfce88"
|
||||
name = "github.com/json-iterator/go"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "ca39e5af3ece67bbcda3d0f4f56a8e24d9f2dad4"
|
||||
version = "1.1.3"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:d9e483f4b9e306facf126bd90b02d512bd22ea4471e1568867e32221a8abbb16"
|
||||
name = "github.com/mailru/easyjson"
|
||||
packages = [
|
||||
"buffer",
|
||||
"jlexer",
|
||||
"jwriter",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "3fdea8d05856a0c8df22ed4bc71b3219245e4485"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:83854f6b1d2ce047b69657e3a87ba7602f4c5505e8bdfd02ab857db8e983bde1"
|
||||
name = "github.com/mitchellh/go-homedir"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "58046073cbffe2f25d425fe1331102f55cf719de"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:51c98e2c9a8d0a724a69f46421876af14e12132cb02f1d0e144785d752247162"
|
||||
name = "github.com/mitchellh/go-testing-interface"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "a61a99592b77c9ba629d254a693acffaeb4b7e28"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:0c0ff2a89c1bb0d01887e1dac043ad7efbf3ec77482ef058ac423d13497e16fd"
|
||||
name = "github.com/modern-go/concurrent"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94"
|
||||
version = "1.0.3"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:420f9231f816eeca3ff5aab070caac3ed7f27e4d37ded96ce9de3d7a7a2e31ad"
|
||||
name = "github.com/modern-go/reflect2"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "1df9eeb2bb81f327b96228865c5687bc2194af3f"
|
||||
version = "1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:7365acd48986e205ccb8652cc746f09c8b7876030d53710ea6ef7d0bd0dcd7ca"
|
||||
name = "github.com/pkg/errors"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
|
||||
version = "v0.8.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:74c32990510c9f188556aa17600313e867d1d06f5a9db244056a95d144ec34ce"
|
||||
name = "github.com/spf13/cobra"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "a1f051bc3eba734da4772d60e2d677f47cf93ef4"
|
||||
version = "v0.0.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:8e243c568f36b09031ec18dff5f7d2769dcf5ca4d624ea511c8e3197dc3d352d"
|
||||
name = "github.com/spf13/pflag"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "583c0c0531f06d5278b7d917446061adc344b5cd"
|
||||
version = "v1.0.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:ee723e6a1962a196eeba1b24f82af61a4f60f8821d7aa96d48e787f8337bcffc"
|
||||
name = "github.com/ulikunitz/xz"
|
||||
packages = [
|
||||
".",
|
||||
"internal/hash",
|
||||
"internal/xlog",
|
||||
"lzma",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "0c6b41e72360850ca4f98dc341fd999726ea007f"
|
||||
version = "v0.5.4"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:35171304d8332a0cfac5f3bd9222467f036732ddde75c65278b16f65216e03ed"
|
||||
name = "golang.org/x/net"
|
||||
packages = [
|
||||
"http2",
|
||||
"http2/hpack",
|
||||
"idna",
|
||||
"lex/httplex",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "1c05540f6879653db88113bc4a2b70aec4bd491f"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:5acd3512b047305d49e8763eef7ba423901e85d5dd2fd1e71778a0ea8de10bd4"
|
||||
name = "golang.org/x/text"
|
||||
packages = [
|
||||
"collate",
|
||||
"collate/build",
|
||||
"internal/colltab",
|
||||
"internal/gen",
|
||||
"internal/tag",
|
||||
"internal/triegen",
|
||||
"internal/ucd",
|
||||
"language",
|
||||
"secure/bidirule",
|
||||
"transform",
|
||||
"unicode/bidi",
|
||||
"unicode/cldr",
|
||||
"unicode/norm",
|
||||
"unicode/rangetable",
|
||||
"width",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
|
||||
version = "v0.3.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:75fb3fcfc73a8c723efde7777b40e8e8ff9babf30d8c56160d01beffea8a95a6"
|
||||
name = "gopkg.in/inf.v0"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "d2d2541c53f18d2a059457998ce2876cc8e67cbf"
|
||||
version = "v0.9.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:f0620375dd1f6251d9973b5f2596228cc8042e887cd7f827e4220bc1ce8c30e2"
|
||||
name = "gopkg.in/yaml.v2"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183"
|
||||
version = "v2.2.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:663df6da5560210fc39194a0a2c4fceba09ead717c330f1174bb15597cf18ce8"
|
||||
name = "k8s.io/api"
|
||||
packages = [
|
||||
"admissionregistration/v1alpha1",
|
||||
"admissionregistration/v1beta1",
|
||||
"apps/v1",
|
||||
"apps/v1beta1",
|
||||
"apps/v1beta2",
|
||||
"authentication/v1",
|
||||
"authentication/v1beta1",
|
||||
"authorization/v1",
|
||||
"authorization/v1beta1",
|
||||
"autoscaling/v1",
|
||||
"autoscaling/v2beta1",
|
||||
"batch/v1",
|
||||
"batch/v1beta1",
|
||||
"batch/v2alpha1",
|
||||
"certificates/v1beta1",
|
||||
"core/v1",
|
||||
"events/v1beta1",
|
||||
"extensions/v1beta1",
|
||||
"networking/v1",
|
||||
"policy/v1beta1",
|
||||
"rbac/v1",
|
||||
"rbac/v1alpha1",
|
||||
"rbac/v1beta1",
|
||||
"scheduling/v1alpha1",
|
||||
"settings/v1alpha1",
|
||||
"storage/v1",
|
||||
"storage/v1alpha1",
|
||||
"storage/v1beta1",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "53d615ae3f440f957cb9989d989d597f047262d9"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:bcb2285bb525712de7903a5d254c2789df65c8b58d2cfac5a26d950ad94c2079"
|
||||
name = "k8s.io/apimachinery"
|
||||
packages = [
|
||||
"pkg/api/equality",
|
||||
"pkg/api/meta",
|
||||
"pkg/api/resource",
|
||||
"pkg/api/validation",
|
||||
"pkg/apis/meta/v1",
|
||||
"pkg/apis/meta/v1/unstructured",
|
||||
"pkg/apis/meta/v1/validation",
|
||||
"pkg/apis/meta/v1beta1",
|
||||
"pkg/conversion",
|
||||
"pkg/conversion/queryparams",
|
||||
"pkg/fields",
|
||||
"pkg/labels",
|
||||
"pkg/runtime",
|
||||
"pkg/runtime/schema",
|
||||
"pkg/runtime/serializer",
|
||||
"pkg/runtime/serializer/json",
|
||||
"pkg/runtime/serializer/protobuf",
|
||||
"pkg/runtime/serializer/recognizer",
|
||||
"pkg/runtime/serializer/versioning",
|
||||
"pkg/selection",
|
||||
"pkg/types",
|
||||
"pkg/util/errors",
|
||||
"pkg/util/framer",
|
||||
"pkg/util/intstr",
|
||||
"pkg/util/json",
|
||||
"pkg/util/mergepatch",
|
||||
"pkg/util/net",
|
||||
"pkg/util/runtime",
|
||||
"pkg/util/sets",
|
||||
"pkg/util/strategicpatch",
|
||||
"pkg/util/validation",
|
||||
"pkg/util/validation/field",
|
||||
"pkg/util/wait",
|
||||
"pkg/util/yaml",
|
||||
"pkg/watch",
|
||||
"third_party/forked/golang/json",
|
||||
"third_party/forked/golang/reflect",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "13b73596e4b63e03203e86f6d9c7bcc1b937c62f"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:071cc2f032b701b9dba26568e040940f26931a49e3a3985f3375f17f7f6d9c5f"
|
||||
name = "k8s.io/client-go"
|
||||
packages = ["kubernetes/scheme"]
|
||||
pruneopts = ""
|
||||
revision = "23781f4d6632d88e869066eaebb743857aa1ef9b"
|
||||
version = "v7.0.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:386c5d69077ce740614e8309ddf107dde91a5db25d3d779143f452fb4fbdfd1e"
|
||||
name = "k8s.io/kube-openapi"
|
||||
packages = [
|
||||
"pkg/common",
|
||||
"pkg/util/proto",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "b3f03f55328800731ce03a164b80973014ecd455"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
input-imports = [
|
||||
"github.com/evanphx/json-patch",
|
||||
"github.com/ghodss/yaml",
|
||||
"github.com/golang/glog",
|
||||
"github.com/hashicorp/go-getter",
|
||||
"github.com/pkg/errors",
|
||||
"github.com/spf13/cobra",
|
||||
"gopkg.in/yaml.v2",
|
||||
"k8s.io/api/core/v1",
|
||||
"k8s.io/apimachinery/pkg/api/validation",
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured",
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/validation",
|
||||
"k8s.io/apimachinery/pkg/runtime",
|
||||
"k8s.io/apimachinery/pkg/runtime/schema",
|
||||
"k8s.io/apimachinery/pkg/util/mergepatch",
|
||||
"k8s.io/apimachinery/pkg/util/strategicpatch",
|
||||
"k8s.io/apimachinery/pkg/util/validation",
|
||||
"k8s.io/apimachinery/pkg/util/validation/field",
|
||||
"k8s.io/apimachinery/pkg/util/yaml",
|
||||
"k8s.io/client-go/kubernetes/scheme",
|
||||
"k8s.io/kube-openapi/pkg/common",
|
||||
]
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
66
Gopkg.toml
66
Gopkg.toml
@@ -1,66 +0,0 @@
|
||||
|
||||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project"
|
||||
# version = "1.0.0"
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project2"
|
||||
# branch = "dev"
|
||||
# source = "github.com/myfork/project2"
|
||||
#
|
||||
# [[override]]
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/evanphx/json-patch"
|
||||
version = "3.0.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/ghodss/yaml"
|
||||
version = "1.0.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/golang/glog"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/spf13/cobra"
|
||||
version = "0.0.2"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "k8s.io/api"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "k8s.io/apimachinery"
|
||||
|
||||
[[constraint]]
|
||||
name = "k8s.io/client-go"
|
||||
version = "7.0.0"
|
||||
|
||||
[[override]]
|
||||
branch = "master"
|
||||
name = "k8s.io/utils"
|
||||
|
||||
[[override]]
|
||||
branch = "master"
|
||||
name = "github.com/go-openapi/spec"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/hashicorp/go-getter"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/krishicks/yaml-patch"
|
||||
version = "0.0.10"
|
||||
250
Makefile
Normal file
250
Makefile
Normal file
@@ -0,0 +1,250 @@
|
||||
# Copyright 2019 The Kubernetes Authors.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Makefile for kustomize CLI and API.
|
||||
|
||||
MYGOBIN := $(shell go env GOPATH)/bin
|
||||
SHELL := /bin/bash
|
||||
export PATH := $(MYGOBIN):$(PATH)
|
||||
|
||||
.PHONY: all
|
||||
all: verify-kustomize
|
||||
|
||||
.PHONY: verify-kustomize
|
||||
verify-kustomize: \
|
||||
lint-kustomize \
|
||||
test-unit-kustomize-all \
|
||||
test-examples-kustomize-against-HEAD \
|
||||
test-examples-kustomize-against-latest
|
||||
|
||||
# Other builds in this repo might want a different linter version.
|
||||
# Without one Makefile to rule them all, the different makes
|
||||
# cannot assume that golanci-lint is at the version they want
|
||||
# since everything uses the same implicit GOPATH.
|
||||
# This installs in a temp dir to avoid overwriting someone else's
|
||||
# linter, then installs in MYGOBIN with a new name.
|
||||
# Version pinned by api/go.mod
|
||||
$(MYGOBIN)/golangci-lint-kustomize:
|
||||
( \
|
||||
set -e; \
|
||||
export GOBIN=$$(mktemp -d) \
|
||||
cd api; \
|
||||
GO111MODULE=on go install github.com/golangci/golangci-lint/cmd/golangci-lint; \
|
||||
mv $$GOBIN/golangci-lint $(MYGOBIN)/golangci-lint-kustomize \
|
||||
)
|
||||
|
||||
# Version pinned by api/go.mod
|
||||
$(MYGOBIN)/mdrip:
|
||||
cd api; \
|
||||
go install github.com/monopole/mdrip
|
||||
|
||||
# Version pinned by api/go.mod
|
||||
$(MYGOBIN)/stringer:
|
||||
cd api; \
|
||||
go install golang.org/x/tools/cmd/stringer
|
||||
|
||||
# Version pinned by api/go.mod
|
||||
$(MYGOBIN)/goimports:
|
||||
cd api; \
|
||||
go install golang.org/x/tools/cmd/goimports
|
||||
|
||||
# To pin pluginator, use this recipe instead:
|
||||
# cd api;
|
||||
# go install sigs.k8s.io/kustomize/pluginator/v2
|
||||
$(MYGOBIN)/pluginator:
|
||||
cd pluginator; \
|
||||
go install .
|
||||
|
||||
# Install kustomize from whatever is checked out.
|
||||
$(MYGOBIN)/kustomize:
|
||||
cd kustomize; \
|
||||
go install .
|
||||
|
||||
.PHONY: install-tools
|
||||
install-tools: \
|
||||
$(MYGOBIN)/goimports \
|
||||
$(MYGOBIN)/golangci-lint-kustomize \
|
||||
$(MYGOBIN)/mdrip \
|
||||
$(MYGOBIN)/pluginator \
|
||||
$(MYGOBIN)/stringer
|
||||
|
||||
### Begin kustomize plugin rules.
|
||||
#
|
||||
# The rules to deal with builtin plugins are a bit
|
||||
# complicated because
|
||||
#
|
||||
# - Every builtin plugin is a Go plugin -
|
||||
# meaning it gets its own module directory
|
||||
# (outside of the api module) with Go
|
||||
# code in a 'main' package per Go plugin rules.
|
||||
# - kustomize locates plugins using the
|
||||
# 'apiVersion' and 'kind' fields from the
|
||||
# plugin config file.
|
||||
# - k8s wants CamelCase in 'kind' fields.
|
||||
# - The module name (the last name in the path)
|
||||
# must be the lowercased 'kind' of the
|
||||
# plugin because Go and related tools
|
||||
# demand lowercase in import paths, but
|
||||
# allow CamelCase in file names.
|
||||
# - the generated code must live in the api
|
||||
# module (it's linked into the api).
|
||||
|
||||
# Where all generated builtin plugin code should go.
|
||||
pGen=api/builtins
|
||||
# Where the builtin Go plugin modules live.
|
||||
pSrc=plugin/builtin
|
||||
|
||||
_builtinplugins = \
|
||||
AnnotationsTransformer.go \
|
||||
ConfigMapGenerator.go \
|
||||
HashTransformer.go \
|
||||
ImageTagTransformer.go \
|
||||
InventoryTransformer.go \
|
||||
LabelTransformer.go \
|
||||
LegacyOrderTransformer.go \
|
||||
NamespaceTransformer.go \
|
||||
PatchJson6902Transformer.go \
|
||||
PatchStrategicMergeTransformer.go \
|
||||
PatchTransformer.go \
|
||||
PrefixSuffixTransformer.go \
|
||||
ReplicaCountTransformer.go \
|
||||
SecretGenerator.go
|
||||
|
||||
# Maintaining this explicit list of generated files, and
|
||||
# adding it as a dependency to a few targets, to assure
|
||||
# they get recreated if deleted. The rules below on how
|
||||
# to make them don't, by themselves, assure they will be
|
||||
# recreated if deleted.
|
||||
builtinplugins = $(patsubst %,$(pGen)/%,$(_builtinplugins))
|
||||
|
||||
# These rules are verbose, but assure that if a source file
|
||||
# is modified, the corresponding generated file, and only
|
||||
# that file, will be recreated.
|
||||
$(pGen)/AnnotationsTransformer.go: $(pSrc)/annotationstransformer/AnnotationsTransformer.go
|
||||
$(pGen)/ConfigMapGenerator.go: $(pSrc)/configmapgenerator/ConfigMapGenerator.go
|
||||
$(pGen)/HashTransformer.go: $(pSrc)/hashtransformer/HashTransformer.go
|
||||
$(pGen)/ImageTagTransformer.go: $(pSrc)/imagetagtransformer/ImageTagTransformer.go
|
||||
$(pGen)/InventoryTransformer.go: $(pSrc)/inventorytransformer/InventoryTransformer.go
|
||||
$(pGen)/LabelTransformer.go: $(pSrc)/labeltransformer/LabelTransformer.go
|
||||
$(pGen)/LegacyOrderTransformer.go: $(pSrc)/legacyordertransformer/LegacyOrderTransformer.go
|
||||
$(pGen)/NamespaceTransformer.go: $(pSrc)/namespacetransformer/NamespaceTransformer.go
|
||||
$(pGen)/PatchJson6902Transformer.go: $(pSrc)/patchjson6902transformer/PatchJson6902Transformer.go
|
||||
$(pGen)/PatchStrategicMergeTransformer.go: $(pSrc)/patchstrategicmergetransformer/PatchStrategicMergeTransformer.go
|
||||
$(pGen)/PatchTransformer.go: $(pSrc)/patchtransformer/PatchTransformer.go
|
||||
$(pGen)/PrefixSuffixTransformer.go: $(pSrc)/prefixsuffixtransformer/PrefixSuffixTransformer.go
|
||||
$(pGen)/ReplicaCountTransformer.go: $(pSrc)/replicacounttransformer/ReplicaCountTransformer.go
|
||||
$(pGen)/SecretGenerator.go: $(pSrc)/secretgenerator/SecretGenerator.go
|
||||
|
||||
# The (verbose but portable) Makefile way to convert to lowercase.
|
||||
toLowerCase = $(subst A,a,$(subst B,b,$(subst C,c,$(subst D,d,$(subst E,e,$(subst F,f,$(subst G,g,$(subst H,h,$(subst I,i,$(subst J,j,$(subst K,k,$(subst L,l,$(subst M,m,$(subst N,n,$(subst O,o,$(subst P,p,$(subst Q,q,$(subst R,r,$(subst S,s,$(subst T,t,$(subst U,u,$(subst V,v,$(subst W,w,$(subst X,x,$(subst Y,y,$(subst Z,z,$1))))))))))))))))))))))))))
|
||||
|
||||
$(pGen)/%.go: $(MYGOBIN)/pluginator
|
||||
@echo "generating $*"
|
||||
( \
|
||||
set -e; \
|
||||
cd $(pSrc)/$(call toLowerCase,$*); \
|
||||
go generate .; \
|
||||
cd ../../../$(pGen); \
|
||||
$(MYGOBIN)/goimports -w $*.go \
|
||||
)
|
||||
|
||||
# Target is for debugging.
|
||||
.PHONY: generate-kustomize-builtin-plugins
|
||||
generate-kustomize-builtin-plugins: $(builtinplugins)
|
||||
|
||||
.PHONY: kustomize-external-go-plugin-build
|
||||
kustomize-external-go-plugin-build:
|
||||
./hack/buildExternalGoPlugins.sh ./plugin
|
||||
|
||||
.PHONY: kustomize-external-go-plugin-clean
|
||||
kustomize-external-go-plugin-clean:
|
||||
./hack/buildExternalGoPlugins.sh ./plugin clean
|
||||
|
||||
### End kustomize plugin rules.
|
||||
|
||||
.PHONY: lint-kustomize
|
||||
lint-kustomize: install-tools $(builtinplugins)
|
||||
cd api; \
|
||||
$(MYGOBIN)/golangci-lint-kustomize -c ../.golangci-kustomize.yml run ./...
|
||||
cd kustomize; \
|
||||
$(MYGOBIN)/golangci-lint-kustomize -c ../.golangci-kustomize.yml run ./...
|
||||
cd pluginator; \
|
||||
$(MYGOBIN)/golangci-lint-kustomize -c ../.golangci-kustomize.yml run ./...
|
||||
|
||||
.PHONY: test-unit-kustomize-api
|
||||
test-unit-kustomize-api: $(builtinplugins)
|
||||
cd api; go test ./...
|
||||
|
||||
.PHONY: test-unit-kustomize-plugins
|
||||
test-unit-kustomize-plugins:
|
||||
./hack/testUnitKustomizePlugins.sh
|
||||
|
||||
.PHONY: test-unit-kustomize-cli
|
||||
test-unit-kustomize-cli:
|
||||
cd kustomize; go test ./...
|
||||
|
||||
.PHONY: test-unit-kustomize-all
|
||||
test-unit-kustomize-all: \
|
||||
test-unit-kustomize-api \
|
||||
test-unit-kustomize-cli \
|
||||
test-unit-kustomize-plugins
|
||||
|
||||
.PHONY:
|
||||
test-examples-kustomize-against-HEAD: $(MYGOBIN)/kustomize $(MYGOBIN)/mdrip
|
||||
./hack/testExamplesAgainstKustomize.sh HEAD
|
||||
|
||||
.PHONY:
|
||||
test-examples-kustomize-against-latest: $(MYGOBIN)/mdrip
|
||||
( \
|
||||
set -e; \
|
||||
/bin/rm -f $(MYGOBIN)/kustomize; \
|
||||
echo "Installing kustomize from latest."; \
|
||||
GO111MODULE=on go install sigs.k8s.io/kustomize/kustomize/v3; \
|
||||
./hack/testExamplesAgainstKustomize.sh latest; \
|
||||
echo "Reinstalling kustomize from HEAD."; \
|
||||
cd kustomize; go install .; \
|
||||
)
|
||||
|
||||
# linux only.
|
||||
# This is for testing an example plugin that
|
||||
# uses kubeval for validation.
|
||||
# Don't want to add a hard dependence in go.mod file
|
||||
# to github.com/instrumenta/kubeval.
|
||||
# Instead, download the binary.
|
||||
$(MYGOBIN)/kubeval:
|
||||
( \
|
||||
set -e; \
|
||||
d=$(shell mktemp -d); cd $$d; \
|
||||
wget https://github.com/instrumenta/kubeval/releases/latest/download/kubeval-linux-amd64.tar.gz; \
|
||||
tar xf kubeval-linux-amd64.tar.gz; \
|
||||
mv kubeval $(MYGOBIN); \
|
||||
rm -rf $$d; \
|
||||
)
|
||||
|
||||
# linux only.
|
||||
# This is for testing an example plugin that
|
||||
# uses helm to inflate a chart for subsequent kustomization.
|
||||
# Don't want to add a hard dependence in go.mod file
|
||||
# to helm.
|
||||
# Instead, download the binary.
|
||||
$(MYGOBIN)/helm:
|
||||
( \
|
||||
set -e; \
|
||||
d=$(shell mktemp -d); cd $$d; \
|
||||
wget https://storage.googleapis.com/kubernetes-helm/helm-v2.13.1-linux-amd64.tar.gz; \
|
||||
tar -xvzf helm-v2.13.1-linux-amd64.tar.gz; \
|
||||
mv linux-amd64/helm $(MYGOBIN); \
|
||||
rm -rf $$d \
|
||||
)
|
||||
|
||||
.PHONY: clean
|
||||
clean: kustomize-external-go-plugin-clean
|
||||
go clean --cache
|
||||
rm -f $(builtinplugins)
|
||||
rm -f $(MYGOBIN)/pluginator
|
||||
rm -f $(MYGOBIN)/kustomize
|
||||
rm -f $(MYGOBIN)/golangci-lint-kustomize
|
||||
|
||||
.PHONY: nuke
|
||||
nuke: clean
|
||||
sudo rm -rf $(shell go env GOPATH)/pkg/mod/sigs.k8s.io
|
||||
@@ -1,6 +1,5 @@
|
||||
aliases:
|
||||
kustomize-admins:
|
||||
- grodrigues3
|
||||
- monopole
|
||||
- pwittrock
|
||||
kustomize-maintainers:
|
||||
|
||||
69
README.md
69
README.md
@@ -7,16 +7,32 @@ untouched and usable as is.
|
||||
`kustomize` targets kubernetes; it understands and can
|
||||
patch [kubernetes style] API objects. It's like
|
||||
[`make`], in that what it does is declared in a file,
|
||||
and it's like [`sed`], in that it emits editted text.
|
||||
and it's like [`sed`], in that it emits edited text.
|
||||
|
||||
This tool is sponsored by [sig-cli] ([KEP]), and
|
||||
inspired by [DAM].
|
||||
|
||||
This tool is sponsored by [sig-cli] ([KEP]).
|
||||
|
||||
[](https://travis-ci.org/kubernetes-sigs/kustomize)
|
||||
[](https://goreportcard.com/report/github.com/kubernetes-sigs/kustomize)
|
||||
|
||||
**Installation**: Download a binary from the [release
|
||||
page], or see these [install] notes. Then try one of
|
||||
the tested [examples].
|
||||
Download a binary from the [release page], or see
|
||||
these [instructions](docs/INSTALL.md).
|
||||
|
||||
Browse the [docs](docs) or jump right into the
|
||||
tested [examples](examples).
|
||||
|
||||
## kubectl integration
|
||||
|
||||
Since [v1.14][kubectl announcement] the kustomize build system has been included in kubectl.
|
||||
|
||||
| kubectl version | kustomize version |
|
||||
|---------|--------|
|
||||
| v1.16.0 | [v2.0.3](/../../tree/v2.0.3) |
|
||||
| v1.15.x | [v2.0.3](/../../tree/v2.0.3) |
|
||||
| v1.14.x | [v2.0.3](/../../tree/v2.0.3) |
|
||||
|
||||
For examples and guides for using the kubectl integration please see the [kubectl book] or the [kubernetes documentation].
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -115,41 +131,56 @@ The YAML can be directly [applied] to a cluster:
|
||||
> kustomize build ~/someApp/overlays/production | kubectl apply -f -
|
||||
> ```
|
||||
|
||||
## Community, discussion, contribution, and support
|
||||
## Community
|
||||
|
||||
Learn how to engage with the Kubernetes community on the [community page].
|
||||
To file bugs please read [this](docs/bugs.md).
|
||||
|
||||
You can reach the maintainers of this project at:
|
||||
Before working on an implementation, please
|
||||
|
||||
* Read the [eschewed feature list].
|
||||
* File an issue describing
|
||||
how the new feature would behave
|
||||
and label it [kind/feature].
|
||||
|
||||
### Other communication channels
|
||||
|
||||
- [Slack]
|
||||
- [Mailing List]
|
||||
- General kubernetes [community page]
|
||||
|
||||
### Code of conduct
|
||||
|
||||
Participation in the Kubernetes community is governed by the [Kubernetes Code of Conduct].
|
||||
Participation in the Kubernetes community
|
||||
is governed by the [Kubernetes Code of Conduct].
|
||||
|
||||
[KEP]: https://github.com/kubernetes/community/blob/master/keps/sig-cli/0008-kustomize.md
|
||||
[`make`]: https://www.gnu.org/software/make
|
||||
[`sed`]: https://www.gnu.org/software/sed
|
||||
[DAM]: docs/glossary.md#declarative-application-management
|
||||
[KEP]: https://github.com/kubernetes/enhancements/blob/master/keps/sig-cli/0008-kustomize.md
|
||||
[Kubernetes Code of Conduct]: code-of-conduct.md
|
||||
[Mailing List]: https://groups.google.com/forum/#!forum/kubernetes-sig-cli
|
||||
[Slack]: https://kubernetes.slack.com/messages/sig-cli
|
||||
[applied]: docs/glossary.md#apply
|
||||
[base]: docs/glossary.md#base
|
||||
[community page]: http://kubernetes.io/community/
|
||||
[declarative configuration]: docs/glossary.md#declarative-application-management
|
||||
[examples]: examples/README.md
|
||||
[imageBase]: docs/base.jpg
|
||||
[imageOverlay]: docs/overlay.jpg
|
||||
[install]: docs/INSTALL.md
|
||||
[eschewed feature list]: docs/eschewedFeatures.md
|
||||
[imageBase]: docs/images/base.jpg
|
||||
[imageOverlay]: docs/images/overlay.jpg
|
||||
[kind/feature]: /../../labels/kind%2Ffeature
|
||||
[kubectl announcement]: https://kubernetes.io/blog/2019/03/25/kubernetes-1-14-release-announcement
|
||||
[kubectl book]: https://kubectl.docs.kubernetes.io/pages/app_customization/introduction.html
|
||||
[kubernetes documentation]: https://kubernetes.io/docs/tasks/manage-kubernetes-objects/kustomization/
|
||||
[kubernetes style]: docs/glossary.md#kubernetes-style-object
|
||||
[kustomization]: docs/glossary.md#kustomization
|
||||
[overlay]: docs/glossary.md#overlay
|
||||
[overlays]: docs/glossary.md#overlay
|
||||
[release page]: https://github.com/kubernetes-sigs/kustomize/releases
|
||||
[release page]: /../../releases
|
||||
[resource]: docs/glossary.md#resource
|
||||
[resources]: docs/glossary.md#resource
|
||||
[sig-cli]: https://github.com/kubernetes/community/blob/master/sig-cli/README.md
|
||||
[variant]: docs/glossary.md#variant
|
||||
[variants]: docs/glossary.md#variant
|
||||
[v2.0.3]: /../../releases/tag/v2.0.3
|
||||
[v2.1.0]: /../../releases/tag/v2.1.0
|
||||
[workflows]: docs/workflows.md
|
||||
[community page]: http://kubernetes.io/community/
|
||||
[Kubernetes Code of Conduct]: code-of-conduct.md
|
||||
[Slack]: https://kubernetes.slack.com/messages/sig-cli
|
||||
[Mailing List]: https://groups.google.com/forum/#!forum/kubernetes-sig-cli
|
||||
|
||||
39
api/builtins/AnnotationsTransformer.go
Normal file
39
api/builtins/AnnotationsTransformer.go
Normal file
@@ -0,0 +1,39 @@
|
||||
// Code generated by pluginator on AnnotationsTransformer; DO NOT EDIT.
|
||||
// pluginator {unknown 1970-01-01T00:00:00Z }
|
||||
|
||||
package builtins
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/transform"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
// Add the given annotations to the given field specifications.
|
||||
type AnnotationsTransformerPlugin struct {
|
||||
Annotations map[string]string `json:"annotations,omitempty" yaml:"annotations,omitempty"`
|
||||
FieldSpecs []types.FieldSpec `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
|
||||
}
|
||||
|
||||
func (p *AnnotationsTransformerPlugin) Config(
|
||||
_ *resmap.PluginHelpers, c []byte) (err error) {
|
||||
p.Annotations = nil
|
||||
p.FieldSpecs = nil
|
||||
return yaml.Unmarshal(c, p)
|
||||
}
|
||||
|
||||
func (p *AnnotationsTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
t, err := transform.NewMapTransformer(
|
||||
p.FieldSpecs,
|
||||
p.Annotations,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return t.Transform(m)
|
||||
}
|
||||
|
||||
func NewAnnotationsTransformerPlugin() resmap.TransformerPlugin {
|
||||
return &AnnotationsTransformerPlugin{}
|
||||
}
|
||||
43
api/builtins/ConfigMapGenerator.go
Normal file
43
api/builtins/ConfigMapGenerator.go
Normal file
@@ -0,0 +1,43 @@
|
||||
// Code generated by pluginator on ConfigMapGenerator; DO NOT EDIT.
|
||||
// pluginator {unknown 1970-01-01T00:00:00Z }
|
||||
|
||||
package builtins
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/api/kv"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
type ConfigMapGeneratorPlugin struct {
|
||||
h *resmap.PluginHelpers
|
||||
types.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
||||
types.GeneratorOptions
|
||||
types.ConfigMapArgs
|
||||
}
|
||||
|
||||
func (p *ConfigMapGeneratorPlugin) Config(
|
||||
h *resmap.PluginHelpers, config []byte) (err error) {
|
||||
p.GeneratorOptions = types.GeneratorOptions{}
|
||||
p.ConfigMapArgs = types.ConfigMapArgs{}
|
||||
err = yaml.Unmarshal(config, p)
|
||||
if p.ConfigMapArgs.Name == "" {
|
||||
p.ConfigMapArgs.Name = p.Name
|
||||
}
|
||||
if p.ConfigMapArgs.Namespace == "" {
|
||||
p.ConfigMapArgs.Namespace = p.Namespace
|
||||
}
|
||||
p.h = h
|
||||
return
|
||||
}
|
||||
|
||||
func (p *ConfigMapGeneratorPlugin) Generate() (resmap.ResMap, error) {
|
||||
return p.h.ResmapFactory().FromConfigMapArgs(
|
||||
kv.NewLoader(p.h.Loader(), p.h.Validator()),
|
||||
&p.GeneratorOptions, p.ConfigMapArgs)
|
||||
}
|
||||
|
||||
func NewConfigMapGeneratorPlugin() resmap.GeneratorPlugin {
|
||||
return &ConfigMapGeneratorPlugin{}
|
||||
}
|
||||
39
api/builtins/HashTransformer.go
Normal file
39
api/builtins/HashTransformer.go
Normal file
@@ -0,0 +1,39 @@
|
||||
// Code generated by pluginator on HashTransformer; DO NOT EDIT.
|
||||
// pluginator {unknown 1970-01-01T00:00:00Z }
|
||||
|
||||
package builtins
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/ifc"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
)
|
||||
|
||||
type HashTransformerPlugin struct {
|
||||
hasher ifc.KunstructuredHasher
|
||||
}
|
||||
|
||||
func (p *HashTransformerPlugin) Config(
|
||||
h *resmap.PluginHelpers, _ []byte) (err error) {
|
||||
p.hasher = h.ResmapFactory().RF().Hasher()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Transform appends hash to generated resources.
|
||||
func (p *HashTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
for _, res := range m.Resources() {
|
||||
if res.NeedHashSuffix() {
|
||||
h, err := p.hasher.Hash(res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res.SetName(fmt.Sprintf("%s-%s", res.GetName(), h))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewHashTransformerPlugin() resmap.TransformerPlugin {
|
||||
return &HashTransformerPlugin{}
|
||||
}
|
||||
185
api/builtins/ImageTagTransformer.go
Normal file
185
api/builtins/ImageTagTransformer.go
Normal file
@@ -0,0 +1,185 @@
|
||||
// Code generated by pluginator on ImageTagTransformer; DO NOT EDIT.
|
||||
// pluginator {unknown 1970-01-01T00:00:00Z }
|
||||
|
||||
package builtins
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/transform"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
// Find matching image declarations and replace
|
||||
// the name, tag and/or digest.
|
||||
type ImageTagTransformerPlugin struct {
|
||||
ImageTag types.Image `json:"imageTag,omitempty" yaml:"imageTag,omitempty"`
|
||||
FieldSpecs []types.FieldSpec `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
|
||||
}
|
||||
|
||||
func (p *ImageTagTransformerPlugin) Config(
|
||||
_ *resmap.PluginHelpers, c []byte) (err error) {
|
||||
p.ImageTag = types.Image{}
|
||||
p.FieldSpecs = nil
|
||||
return yaml.Unmarshal(c, p)
|
||||
}
|
||||
|
||||
func (p *ImageTagTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
for _, r := range m.Resources() {
|
||||
for _, path := range p.FieldSpecs {
|
||||
if !r.OrgId().IsSelected(&path.Gvk) {
|
||||
continue
|
||||
}
|
||||
err := transform.MutateField(
|
||||
r.Map(), path.PathSlice(), false, p.mutateImage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Kept for backward compatibility
|
||||
if err := p.findAndReplaceImage(r.Map()); err != nil && r.OrgId().Kind != `CustomResourceDefinition` {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *ImageTagTransformerPlugin) mutateImage(in interface{}) (interface{}, error) {
|
||||
original, ok := in.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("image path is not of type string but %T", in)
|
||||
}
|
||||
|
||||
if !isImageMatched(original, p.ImageTag.Name) {
|
||||
return original, nil
|
||||
}
|
||||
name, tag := split(original)
|
||||
if p.ImageTag.NewName != "" {
|
||||
name = p.ImageTag.NewName
|
||||
}
|
||||
if p.ImageTag.NewTag != "" {
|
||||
tag = ":" + p.ImageTag.NewTag
|
||||
}
|
||||
if p.ImageTag.Digest != "" {
|
||||
tag = "@" + p.ImageTag.Digest
|
||||
}
|
||||
return name + tag, nil
|
||||
}
|
||||
|
||||
// findAndReplaceImage replaces the image name and
|
||||
// tags inside one object.
|
||||
// It searches the object for container session
|
||||
// then loops though all images inside containers
|
||||
// session, finds matched ones and update the
|
||||
// image name and tag name
|
||||
func (p *ImageTagTransformerPlugin) findAndReplaceImage(obj map[string]interface{}) error {
|
||||
paths := []string{"containers", "initContainers"}
|
||||
updated := false
|
||||
for _, path := range paths {
|
||||
containers, found := obj[path]
|
||||
if found && containers != nil {
|
||||
if _, err := p.updateContainers(containers); err != nil {
|
||||
return err
|
||||
}
|
||||
updated = true
|
||||
}
|
||||
}
|
||||
if !updated {
|
||||
return p.findContainers(obj)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *ImageTagTransformerPlugin) updateContainers(in interface{}) (interface{}, error) {
|
||||
containers, ok := in.([]interface{})
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(
|
||||
"containers path is not of type []interface{} but %T", in)
|
||||
}
|
||||
for i := range containers {
|
||||
container := containers[i].(map[string]interface{})
|
||||
containerImage, found := container["image"]
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
imageName := containerImage.(string)
|
||||
if isImageMatched(imageName, p.ImageTag.Name) {
|
||||
newImage, err := p.mutateImage(imageName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
container["image"] = newImage
|
||||
}
|
||||
}
|
||||
return containers, nil
|
||||
}
|
||||
|
||||
func (p *ImageTagTransformerPlugin) findContainers(obj map[string]interface{}) error {
|
||||
for key := range obj {
|
||||
switch typedV := obj[key].(type) {
|
||||
case map[string]interface{}:
|
||||
err := p.findAndReplaceImage(typedV)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case []interface{}:
|
||||
for i := range typedV {
|
||||
item := typedV[i]
|
||||
typedItem, ok := item.(map[string]interface{})
|
||||
if ok {
|
||||
err := p.findAndReplaceImage(typedItem)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isImageMatched(s, t string) bool {
|
||||
// Tag values are limited to [a-zA-Z0-9_.-].
|
||||
pattern, _ := regexp.Compile("^" + t + "(@sha256)?(:[a-zA-Z0-9_.-]*)?$")
|
||||
return pattern.MatchString(s)
|
||||
}
|
||||
|
||||
// split separates and returns the name and tag parts
|
||||
// from the image string using either colon `:` or at `@` separators.
|
||||
// Note that the returned tag keeps its separator.
|
||||
func split(imageName string) (name string, tag string) {
|
||||
// check if image name contains a domain
|
||||
// if domain is present, ignore domain and check for `:`
|
||||
ic := -1
|
||||
if slashIndex := strings.Index(imageName, "/"); slashIndex < 0 {
|
||||
ic = strings.LastIndex(imageName, ":")
|
||||
} else {
|
||||
lastIc := strings.LastIndex(imageName[slashIndex:], ":")
|
||||
// set ic only if `:` is present
|
||||
if lastIc > 0 {
|
||||
ic = slashIndex + lastIc
|
||||
}
|
||||
}
|
||||
ia := strings.LastIndex(imageName, "@")
|
||||
if ic < 0 && ia < 0 {
|
||||
return imageName, ""
|
||||
}
|
||||
|
||||
i := ic
|
||||
if ia > 0 {
|
||||
i = ia
|
||||
}
|
||||
|
||||
name = imageName[:i]
|
||||
tag = imageName[i:]
|
||||
return
|
||||
}
|
||||
|
||||
func NewImageTagTransformerPlugin() resmap.TransformerPlugin {
|
||||
return &ImageTagTransformerPlugin{}
|
||||
}
|
||||
128
api/builtins/InventoryTransformer.go
Normal file
128
api/builtins/InventoryTransformer.go
Normal file
@@ -0,0 +1,128 @@
|
||||
// Code generated by pluginator on InventoryTransformer; DO NOT EDIT.
|
||||
// pluginator {unknown 1970-01-01T00:00:00Z }
|
||||
|
||||
package builtins
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/hasher"
|
||||
"sigs.k8s.io/kustomize/api/inventory"
|
||||
"sigs.k8s.io/kustomize/api/kv"
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/resource"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
type InventoryTransformerPlugin struct {
|
||||
h *resmap.PluginHelpers
|
||||
types.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
||||
Policy string `json:"policy,omitempty" yaml:"policy,omitempty"`
|
||||
}
|
||||
|
||||
func (p *InventoryTransformerPlugin) Config(
|
||||
h *resmap.PluginHelpers, c []byte) (err error) {
|
||||
p.h = h
|
||||
err = yaml.Unmarshal(c, p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if p.Policy == "" {
|
||||
p.Policy = types.GarbageIgnore.String()
|
||||
}
|
||||
if p.Policy != types.GarbageCollect.String() &&
|
||||
p.Policy != types.GarbageIgnore.String() {
|
||||
return fmt.Errorf(
|
||||
"unrecognized garbagePolicy '%s'", p.Policy)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Transform generates an inventory object from the input ResMap.
|
||||
// This ConfigMap supports the pruning command in
|
||||
// the client side tool proposed here:
|
||||
// https://github.com/kubernetes/enhancements/pull/810
|
||||
//
|
||||
// The inventory data is written to the ConfigMap's
|
||||
// annotations, rather than to the key-value pairs in
|
||||
// the ConfigMap's data field, since
|
||||
// 1. Keys in a ConfigMap's data field are too
|
||||
// constrained for this purpose.
|
||||
// 2. Using annotations allow any object to be used,
|
||||
// not just a ConfigMap, should some other object
|
||||
// (e.g. some App object) become more desirable
|
||||
// for this purpose.
|
||||
func (p *InventoryTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
|
||||
inv, h, err := makeInventory(m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := types.ConfigMapArgs{}
|
||||
args.Name = p.Name
|
||||
args.Namespace = p.Namespace
|
||||
opts := &types.GeneratorOptions{
|
||||
Annotations: make(map[string]string),
|
||||
}
|
||||
opts.Annotations[inventory.HashAnnotation] = h
|
||||
err = inv.UpdateAnnotations(opts.Annotations)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cm, err := p.h.ResmapFactory().RF().MakeConfigMap(
|
||||
kv.NewLoader(p.h.Loader(), p.h.Validator()), opts, &args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if p.Policy == types.GarbageCollect.String() {
|
||||
for _, byeBye := range m.AllIds() {
|
||||
m.Remove(byeBye)
|
||||
}
|
||||
}
|
||||
return m.Append(cm)
|
||||
}
|
||||
|
||||
func makeInventory(m resmap.ResMap) (
|
||||
inv *inventory.Inventory, hash string, err error) {
|
||||
inv = inventory.NewInventory()
|
||||
var keys []string
|
||||
for _, r := range m.Resources() {
|
||||
ns := r.GetNamespace()
|
||||
item := resid.NewResIdWithNamespace(r.GetGvk(), r.GetName(), ns)
|
||||
if _, ok := inv.Current[item]; ok {
|
||||
return nil, "", fmt.Errorf(
|
||||
"item '%v' already in inventory", item)
|
||||
}
|
||||
inv.Current[item], err = computeRefs(r, m)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
keys = append(keys, item.String())
|
||||
}
|
||||
h, err := hasher.SortArrayAndComputeHash(keys)
|
||||
return inv, h, err
|
||||
}
|
||||
|
||||
func computeRefs(
|
||||
r *resource.Resource, m resmap.ResMap) (refs []resid.ResId, err error) {
|
||||
for _, refid := range r.GetRefBy() {
|
||||
ref, err := m.GetByCurrentId(refid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
refs = append(
|
||||
refs,
|
||||
resid.NewResIdWithNamespace(
|
||||
ref.GetGvk(), ref.GetName(), ref.GetNamespace()))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func NewInventoryTransformerPlugin() resmap.TransformerPlugin {
|
||||
return &InventoryTransformerPlugin{}
|
||||
}
|
||||
39
api/builtins/LabelTransformer.go
Normal file
39
api/builtins/LabelTransformer.go
Normal file
@@ -0,0 +1,39 @@
|
||||
// Code generated by pluginator on LabelTransformer; DO NOT EDIT.
|
||||
// pluginator {unknown 1970-01-01T00:00:00Z }
|
||||
|
||||
package builtins
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/transform"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
// Add the given labels to the given field specifications.
|
||||
type LabelTransformerPlugin struct {
|
||||
Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`
|
||||
FieldSpecs []types.FieldSpec `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
|
||||
}
|
||||
|
||||
func (p *LabelTransformerPlugin) Config(
|
||||
_ *resmap.PluginHelpers, c []byte) (err error) {
|
||||
p.Labels = nil
|
||||
p.FieldSpecs = nil
|
||||
return yaml.Unmarshal(c, p)
|
||||
}
|
||||
|
||||
func (p *LabelTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
t, err := transform.NewMapTransformer(
|
||||
p.FieldSpecs,
|
||||
p.Labels,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return t.Transform(m)
|
||||
}
|
||||
|
||||
func NewLabelTransformerPlugin() resmap.TransformerPlugin {
|
||||
return &LabelTransformerPlugin{}
|
||||
}
|
||||
46
api/builtins/LegacyOrderTransformer.go
Normal file
46
api/builtins/LegacyOrderTransformer.go
Normal file
@@ -0,0 +1,46 @@
|
||||
// Code generated by pluginator on LegacyOrderTransformer; DO NOT EDIT.
|
||||
// pluginator {unknown 1970-01-01T00:00:00Z }
|
||||
|
||||
package builtins
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/resource"
|
||||
)
|
||||
|
||||
// Sort the resources using an ordering defined in the Gvk class.
|
||||
// This puts cluster-wide basic resources with no
|
||||
// dependencies (like Namespace, StorageClass, etc.)
|
||||
// first, and resources with a high number of dependencies
|
||||
// (like ValidatingWebhookConfiguration) last.
|
||||
type LegacyOrderTransformerPlugin struct{}
|
||||
|
||||
// Nothing needed for configuration.
|
||||
func (p *LegacyOrderTransformerPlugin) Config(
|
||||
_ *resmap.PluginHelpers, _ []byte) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *LegacyOrderTransformerPlugin) Transform(m resmap.ResMap) (err error) {
|
||||
resources := make([]*resource.Resource, m.Size())
|
||||
ids := m.AllIds()
|
||||
sort.Sort(resmap.IdSlice(ids))
|
||||
for i, id := range ids {
|
||||
resources[i], err = m.GetByCurrentId(id)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "expected match for sorting")
|
||||
}
|
||||
}
|
||||
m.Clear()
|
||||
for _, r := range resources {
|
||||
m.Append(r)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewLegacyOrderTransformerPlugin() resmap.TransformerPlugin {
|
||||
return &LegacyOrderTransformerPlugin{}
|
||||
}
|
||||
131
api/builtins/NamespaceTransformer.go
Normal file
131
api/builtins/NamespaceTransformer.go
Normal file
@@ -0,0 +1,131 @@
|
||||
// Code generated by pluginator on NamespaceTransformer; DO NOT EDIT.
|
||||
// pluginator {unknown 1970-01-01T00:00:00Z }
|
||||
|
||||
package builtins
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/transform"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/resource"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
// Change or set the namespace of non-cluster level resources.
|
||||
type NamespaceTransformerPlugin struct {
|
||||
types.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
||||
FieldSpecs []types.FieldSpec `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
|
||||
}
|
||||
|
||||
func (p *NamespaceTransformerPlugin) Config(
|
||||
_ *resmap.PluginHelpers, c []byte) (err error) {
|
||||
p.Namespace = ""
|
||||
p.FieldSpecs = nil
|
||||
return yaml.Unmarshal(c, p)
|
||||
}
|
||||
|
||||
func (p *NamespaceTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
if len(p.Namespace) == 0 {
|
||||
return nil
|
||||
}
|
||||
for _, r := range m.Resources() {
|
||||
if len(r.Map()) == 0 {
|
||||
// Don't mutate empty objects?
|
||||
continue
|
||||
}
|
||||
|
||||
id := r.OrgId()
|
||||
applicableFs := p.applicableFieldSpecs(id)
|
||||
|
||||
for _, fs := range applicableFs {
|
||||
err := transform.MutateField(
|
||||
r.Map(), fs.PathSlice(), fs.CreateIfNotPresent,
|
||||
p.changeNamespace(r))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
matches := m.GetMatchingResourcesByCurrentId(r.CurId().Equals)
|
||||
if len(matches) != 1 {
|
||||
return fmt.Errorf("namespace tranformation produces ID conflict: %#v", matches)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const metaNamespace = "metadata/namespace"
|
||||
|
||||
// Special casing metadata.namespace since
|
||||
// all objects have it, even "ClusterKind" objects
|
||||
// that don't exist in a namespace (the Namespace
|
||||
// object itself doesn't live in a namespace).
|
||||
func (p *NamespaceTransformerPlugin) applicableFieldSpecs(id resid.ResId) []types.FieldSpec {
|
||||
var res []types.FieldSpec
|
||||
for _, fs := range p.FieldSpecs {
|
||||
if id.IsSelected(&fs.Gvk) && (fs.Path != metaNamespace || (fs.Path == metaNamespace && id.IsNamespaceableKind())) {
|
||||
res = append(res, fs)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (p *NamespaceTransformerPlugin) changeNamespace(
|
||||
_ *resource.Resource) func(in interface{}) (interface{}, error) {
|
||||
return func(in interface{}) (interface{}, error) {
|
||||
switch in.(type) {
|
||||
case string:
|
||||
// will happen when the metadata/namespace
|
||||
// value is replaced
|
||||
return p.Namespace, nil
|
||||
case []interface{}:
|
||||
l, _ := in.([]interface{})
|
||||
for idx, item := range l {
|
||||
switch item.(type) {
|
||||
case map[string]interface{}:
|
||||
// Will happen when mutating the subjects
|
||||
// field of ClusterRoleBinding and RoleBinding
|
||||
inMap, _ := item.(map[string]interface{})
|
||||
if _, ok := inMap["name"]; !ok {
|
||||
continue
|
||||
}
|
||||
name, ok := inMap["name"].(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
// The only case we need to force the namespace
|
||||
// if for the "service account". "default" is
|
||||
// kind of hardcoded here for right now.
|
||||
if name != "default" {
|
||||
continue
|
||||
}
|
||||
inMap["namespace"] = p.Namespace
|
||||
l[idx] = inMap
|
||||
default:
|
||||
// nothing to do for right now
|
||||
}
|
||||
}
|
||||
return in, nil
|
||||
case map[string]interface{}:
|
||||
// Will happen if the createField=true
|
||||
// when the namespace is added to the
|
||||
// object
|
||||
inMap := in.(map[string]interface{})
|
||||
if len(inMap) == 0 {
|
||||
return p.Namespace, nil
|
||||
} else {
|
||||
return in, nil
|
||||
}
|
||||
default:
|
||||
return in, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func NewNamespaceTransformerPlugin() resmap.TransformerPlugin {
|
||||
return &NamespaceTransformerPlugin{}
|
||||
}
|
||||
100
api/builtins/PatchJson6902Transformer.go
Normal file
100
api/builtins/PatchJson6902Transformer.go
Normal file
@@ -0,0 +1,100 @@
|
||||
// Code generated by pluginator on PatchJson6902Transformer; DO NOT EDIT.
|
||||
// pluginator {unknown 1970-01-01T00:00:00Z }
|
||||
|
||||
package builtins
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
jsonpatch "github.com/evanphx/json-patch"
|
||||
"github.com/pkg/errors"
|
||||
"sigs.k8s.io/kustomize/api/ifc"
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
type PatchJson6902TransformerPlugin struct {
|
||||
ldr ifc.Loader
|
||||
decodedPatch jsonpatch.Patch
|
||||
Target types.PatchTarget `json:"target,omitempty" yaml:"target,omitempty"`
|
||||
Path string `json:"path,omitempty" yaml:"path,omitempty"`
|
||||
JsonOp string `json:"jsonOp,omitempty" yaml:"jsonOp,omitempty"`
|
||||
}
|
||||
|
||||
func (p *PatchJson6902TransformerPlugin) Config(
|
||||
h *resmap.PluginHelpers, c []byte) (err error) {
|
||||
p.ldr = h.Loader()
|
||||
err = yaml.Unmarshal(c, p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if p.Target.Name == "" {
|
||||
return fmt.Errorf("must specify the target name")
|
||||
}
|
||||
if p.Path == "" && p.JsonOp == "" {
|
||||
return fmt.Errorf("empty file path and empty jsonOp")
|
||||
}
|
||||
if p.Path != "" {
|
||||
if p.JsonOp != "" {
|
||||
return fmt.Errorf("must specify a file path or jsonOp, not both")
|
||||
}
|
||||
rawOp, err := p.ldr.Load(p.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.JsonOp = string(rawOp)
|
||||
if p.JsonOp == "" {
|
||||
return fmt.Errorf("patch file '%s' empty seems to be empty", p.Path)
|
||||
}
|
||||
}
|
||||
if p.JsonOp[0] != '[' {
|
||||
// if it doesn't seem to be JSON, imagine
|
||||
// it is YAML, and convert to JSON.
|
||||
op, err := yaml.YAMLToJSON([]byte(p.JsonOp))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.JsonOp = string(op)
|
||||
}
|
||||
p.decodedPatch, err = jsonpatch.DecodePatch([]byte(p.JsonOp))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "decoding %s", p.JsonOp)
|
||||
}
|
||||
if len(p.decodedPatch) == 0 {
|
||||
return fmt.Errorf(
|
||||
"patch appears to be empty; file=%s, JsonOp=%s", p.Path, p.JsonOp)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *PatchJson6902TransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
id := resid.NewResIdWithNamespace(
|
||||
resid.Gvk{
|
||||
Group: p.Target.Group,
|
||||
Version: p.Target.Version,
|
||||
Kind: p.Target.Kind,
|
||||
},
|
||||
p.Target.Name,
|
||||
p.Target.Namespace,
|
||||
)
|
||||
obj, err := m.GetById(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rawObj, err := obj.MarshalJSON()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
modifiedObj, err := p.decodedPatch.Apply(rawObj)
|
||||
if err != nil {
|
||||
return errors.Wrapf(
|
||||
err, "failed to apply json patch '%s'", p.JsonOp)
|
||||
}
|
||||
return obj.UnmarshalJSON(modifiedObj)
|
||||
}
|
||||
|
||||
func NewPatchJson6902TransformerPlugin() resmap.TransformerPlugin {
|
||||
return &PatchJson6902TransformerPlugin{}
|
||||
}
|
||||
90
api/builtins/PatchStrategicMergeTransformer.go
Normal file
90
api/builtins/PatchStrategicMergeTransformer.go
Normal file
@@ -0,0 +1,90 @@
|
||||
// Code generated by pluginator on PatchStrategicMergeTransformer; DO NOT EDIT.
|
||||
// pluginator {unknown 1970-01-01T00:00:00Z }
|
||||
|
||||
package builtins
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/resource"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
type PatchStrategicMergeTransformerPlugin struct {
|
||||
h *resmap.PluginHelpers
|
||||
loadedPatches []*resource.Resource
|
||||
Paths []types.PatchStrategicMerge `json:"paths,omitempty" yaml:"paths,omitempty"`
|
||||
Patches string `json:"patches,omitempty" yaml:"patches,omitempty"`
|
||||
}
|
||||
|
||||
func (p *PatchStrategicMergeTransformerPlugin) Config(
|
||||
h *resmap.PluginHelpers, c []byte) (err error) {
|
||||
p.h = h
|
||||
err = yaml.Unmarshal(c, p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(p.Paths) == 0 && p.Patches == "" {
|
||||
return fmt.Errorf("empty file path and empty patch content")
|
||||
}
|
||||
if len(p.Paths) != 0 {
|
||||
for _, onePath := range p.Paths {
|
||||
res, err := p.h.ResmapFactory().RF().SliceFromBytes([]byte(onePath))
|
||||
if err == nil {
|
||||
p.loadedPatches = append(p.loadedPatches, res...)
|
||||
continue
|
||||
}
|
||||
res, err = p.h.ResmapFactory().RF().SliceFromPatches(
|
||||
p.h.Loader(), []types.PatchStrategicMerge{onePath})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.loadedPatches = append(p.loadedPatches, res...)
|
||||
}
|
||||
}
|
||||
if p.Patches != "" {
|
||||
res, err := p.h.ResmapFactory().RF().SliceFromBytes([]byte(p.Patches))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.loadedPatches = append(p.loadedPatches, res...)
|
||||
}
|
||||
|
||||
if len(p.loadedPatches) == 0 {
|
||||
return fmt.Errorf(
|
||||
"patch appears to be empty; files=%v, Patch=%s", p.Paths, p.Patches)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *PatchStrategicMergeTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
patches, err := p.h.ResmapFactory().MergePatches(p.loadedPatches)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, patch := range patches.Resources() {
|
||||
target, err := m.GetById(patch.OrgId())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = target.Patch(patch.Kunstructured)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// remove the resource from resmap
|
||||
// when the patch is to $patch: delete that target
|
||||
if len(target.Map()) == 0 {
|
||||
err = m.Remove(target.CurId())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewPatchStrategicMergeTransformerPlugin() resmap.TransformerPlugin {
|
||||
return &PatchStrategicMergeTransformerPlugin{}
|
||||
}
|
||||
145
api/builtins/PatchTransformer.go
Normal file
145
api/builtins/PatchTransformer.go
Normal file
@@ -0,0 +1,145 @@
|
||||
// Code generated by pluginator on PatchTransformer; DO NOT EDIT.
|
||||
// pluginator {unknown 1970-01-01T00:00:00Z }
|
||||
|
||||
package builtins
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
jsonpatch "github.com/evanphx/json-patch"
|
||||
"github.com/pkg/errors"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/resource"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
type PatchTransformerPlugin struct {
|
||||
loadedPatch *resource.Resource
|
||||
decodedPatch jsonpatch.Patch
|
||||
Path string `json:"path,omitempty" yaml:"path,omitempty"`
|
||||
Patch string `json:"patch,omitempty" yaml:"patch,omitempty"`
|
||||
Target *types.Selector `json:"target,omitempty" yaml:"target,omitempty"`
|
||||
}
|
||||
|
||||
func (p *PatchTransformerPlugin) Config(
|
||||
h *resmap.PluginHelpers, c []byte) (err error) {
|
||||
err = yaml.Unmarshal(c, p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if p.Patch == "" && p.Path == "" {
|
||||
err = fmt.Errorf(
|
||||
"must specify one of patch and path in\n%s", string(c))
|
||||
return
|
||||
}
|
||||
if p.Patch != "" && p.Path != "" {
|
||||
err = fmt.Errorf(
|
||||
"patch and path can't be set at the same time\n%s", string(c))
|
||||
return
|
||||
}
|
||||
var in []byte
|
||||
if p.Path != "" {
|
||||
in, err = h.Loader().Load(p.Path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if p.Patch != "" {
|
||||
in = []byte(p.Patch)
|
||||
}
|
||||
|
||||
patchSM, errSM := h.ResmapFactory().RF().FromBytes(in)
|
||||
patchJson, errJson := jsonPatchFromBytes(in)
|
||||
if errSM != nil && errJson != nil {
|
||||
err = fmt.Errorf(
|
||||
"unable to get either a Strategic Merge Patch or JSON patch 6902 from %s", p.Patch)
|
||||
return
|
||||
}
|
||||
if errSM == nil && errJson != nil {
|
||||
p.loadedPatch = patchSM
|
||||
}
|
||||
if errJson == nil && errSM != nil {
|
||||
p.decodedPatch = patchJson
|
||||
}
|
||||
if patchSM != nil && patchJson != nil {
|
||||
err = fmt.Errorf(
|
||||
"a patch can't be both a Strategic Merge Patch and JSON patch 6902 %s", p.Patch)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PatchTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
if p.loadedPatch != nil && p.Target == nil {
|
||||
target, err := m.GetById(p.loadedPatch.OrgId())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = target.Patch(p.loadedPatch.Kunstructured)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if p.Target == nil {
|
||||
return fmt.Errorf("must specify a target for patch %s", p.Patch)
|
||||
}
|
||||
|
||||
resources, err := m.Select(*p.Target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, res := range resources {
|
||||
if p.decodedPatch != nil {
|
||||
rawObj, err := res.MarshalJSON()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
modifiedObj, err := p.decodedPatch.Apply(rawObj)
|
||||
if err != nil {
|
||||
return errors.Wrapf(
|
||||
err, "failed to apply json patch '%s'", p.Patch)
|
||||
}
|
||||
err = res.UnmarshalJSON(modifiedObj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if p.loadedPatch != nil {
|
||||
patchCopy := p.loadedPatch.DeepCopy()
|
||||
patchCopy.SetName(res.GetName())
|
||||
patchCopy.SetNamespace(res.GetNamespace())
|
||||
patchCopy.SetGvk(res.GetGvk())
|
||||
err = res.Patch(patchCopy.Kunstructured)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// jsonPatchFromBytes loads a Json 6902 patch from
|
||||
// a bytes input
|
||||
func jsonPatchFromBytes(
|
||||
in []byte) (jsonpatch.Patch, error) {
|
||||
ops := string(in)
|
||||
if ops == "" {
|
||||
return nil, fmt.Errorf("empty json patch operations")
|
||||
}
|
||||
|
||||
if ops[0] != '[' {
|
||||
jsonOps, err := yaml.YAMLToJSON(in)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ops = string(jsonOps)
|
||||
}
|
||||
return jsonpatch.DecodePatch([]byte(ops))
|
||||
}
|
||||
|
||||
func NewPatchTransformerPlugin() resmap.TransformerPlugin {
|
||||
return &PatchTransformerPlugin{}
|
||||
}
|
||||
123
api/builtins/PrefixSuffixTransformer.go
Normal file
123
api/builtins/PrefixSuffixTransformer.go
Normal file
@@ -0,0 +1,123 @@
|
||||
// Code generated by pluginator on PrefixSuffixTransformer; DO NOT EDIT.
|
||||
// pluginator {unknown 1970-01-01T00:00:00Z }
|
||||
|
||||
package builtins
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/transform"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
// Add the given prefix and suffix to the field.
|
||||
type PrefixSuffixTransformerPlugin struct {
|
||||
Prefix string `json:"prefix,omitempty" yaml:"prefix,omitempty"`
|
||||
Suffix string `json:"suffix,omitempty" yaml:"suffix,omitempty"`
|
||||
FieldSpecs []types.FieldSpec `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
|
||||
}
|
||||
|
||||
// Not placed in a file yet due to lack of demand.
|
||||
var prefixSuffixFieldSpecsToSkip = []types.FieldSpec{
|
||||
{
|
||||
Gvk: resid.Gvk{Kind: "CustomResourceDefinition"},
|
||||
},
|
||||
{
|
||||
Gvk: resid.Gvk{Group: "apiregistration.k8s.io", Kind: "APIService"},
|
||||
},
|
||||
}
|
||||
|
||||
func (p *PrefixSuffixTransformerPlugin) Config(
|
||||
_ *resmap.PluginHelpers, c []byte) (err error) {
|
||||
p.Prefix = ""
|
||||
p.Suffix = ""
|
||||
p.FieldSpecs = nil
|
||||
err = yaml.Unmarshal(c, p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if p.FieldSpecs == nil {
|
||||
return errors.New("fieldSpecs is not expected to be nil")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p *PrefixSuffixTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
|
||||
// Even if both the Prefix and Suffix are empty we want
|
||||
// to proceed with the transformation. This allows to add contextual
|
||||
// information to the resources (AddNamePrefix and AddNameSuffix).
|
||||
|
||||
for _, r := range m.Resources() {
|
||||
if p.shouldSkip(r.OrgId()) {
|
||||
// Don't change the actual definition
|
||||
// of a CRD.
|
||||
continue
|
||||
}
|
||||
id := r.OrgId()
|
||||
// current default configuration contains
|
||||
// only one entry: "metadata/name" with no GVK
|
||||
for _, path := range p.FieldSpecs {
|
||||
if !id.IsSelected(&path.Gvk) {
|
||||
// With the currrent default configuration,
|
||||
// because no Gvk is specified, so a wild
|
||||
// card
|
||||
continue
|
||||
}
|
||||
|
||||
if smellsLikeANameChange(&path) {
|
||||
// "metadata/name" is the only field.
|
||||
// this will add a prefix and a suffix
|
||||
// to the resource even if those are
|
||||
// empty
|
||||
r.AddNamePrefix(p.Prefix)
|
||||
r.AddNameSuffix(p.Suffix)
|
||||
}
|
||||
|
||||
// the addPrefixSuffix method will not
|
||||
// change the name if both the prefix and suffix
|
||||
// are empty.
|
||||
err := transform.MutateField(
|
||||
r.Map(),
|
||||
path.PathSlice(),
|
||||
path.CreateIfNotPresent,
|
||||
p.addPrefixSuffix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func smellsLikeANameChange(fs *types.FieldSpec) bool {
|
||||
return fs.Path == "metadata/name"
|
||||
}
|
||||
|
||||
func (p *PrefixSuffixTransformerPlugin) shouldSkip(
|
||||
id resid.ResId) bool {
|
||||
for _, path := range prefixSuffixFieldSpecsToSkip {
|
||||
if id.IsSelected(&path.Gvk) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *PrefixSuffixTransformerPlugin) addPrefixSuffix(
|
||||
in interface{}) (interface{}, error) {
|
||||
s, ok := in.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%#v is expected to be %T", in, s)
|
||||
}
|
||||
return fmt.Sprintf("%s%s%s", p.Prefix, s, p.Suffix), nil
|
||||
}
|
||||
|
||||
func NewPrefixSuffixTransformerPlugin() resmap.TransformerPlugin {
|
||||
return &PrefixSuffixTransformerPlugin{}
|
||||
}
|
||||
88
api/builtins/ReplicaCountTransformer.go
Normal file
88
api/builtins/ReplicaCountTransformer.go
Normal file
@@ -0,0 +1,88 @@
|
||||
// Code generated by pluginator on ReplicaCountTransformer; DO NOT EDIT.
|
||||
// pluginator {unknown 1970-01-01T00:00:00Z }
|
||||
|
||||
package builtins
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/transform"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
// Find matching replicas declarations and replace the count.
|
||||
// Eases the kustomization configuration of replica changes.
|
||||
type ReplicaCountTransformerPlugin struct {
|
||||
Replica types.Replica `json:"replica,omitempty" yaml:"replica,omitempty"`
|
||||
FieldSpecs []types.FieldSpec `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
|
||||
}
|
||||
|
||||
func (p *ReplicaCountTransformerPlugin) Config(
|
||||
_ *resmap.PluginHelpers, c []byte) (err error) {
|
||||
p.Replica = types.Replica{}
|
||||
p.FieldSpecs = nil
|
||||
return yaml.Unmarshal(c, p)
|
||||
}
|
||||
|
||||
func (p *ReplicaCountTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
found := false
|
||||
for i, replicaSpec := range p.FieldSpecs {
|
||||
matcher := p.createMatcher(i)
|
||||
matchOriginal := m.GetMatchingResourcesByOriginalId(matcher)
|
||||
matchCurrent := m.GetMatchingResourcesByCurrentId(matcher)
|
||||
|
||||
for _, res := range append(matchOriginal, matchCurrent...) {
|
||||
found = true
|
||||
err := transform.MutateField(
|
||||
res.Map(), replicaSpec.PathSlice(),
|
||||
replicaSpec.CreateIfNotPresent, p.addReplicas)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
gvks := make([]string, len(p.FieldSpecs))
|
||||
for i, replicaSpec := range p.FieldSpecs {
|
||||
gvks[i] = replicaSpec.Gvk.String()
|
||||
}
|
||||
return fmt.Errorf("resource with name %s does not match a config with the following GVK %v",
|
||||
p.Replica.Name, gvks)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Match Replica.Name and FieldSpec
|
||||
func (p *ReplicaCountTransformerPlugin) createMatcher(i int) resmap.IdMatcher {
|
||||
return func(r resid.ResId) bool {
|
||||
return r.Name == p.Replica.Name &&
|
||||
r.Gvk.IsSelected(&p.FieldSpecs[i].Gvk)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ReplicaCountTransformerPlugin) addReplicas(in interface{}) (interface{}, error) {
|
||||
switch m := in.(type) {
|
||||
case int64:
|
||||
// Was already in the field.
|
||||
case map[string]interface{}:
|
||||
if len(m) != 0 {
|
||||
// A map was already in the replicas field, don't want to
|
||||
// discard this data silently.
|
||||
return nil, fmt.Errorf("%#v is expected to be %T", in, m)
|
||||
}
|
||||
// Just got added, default type is map, but we can return anything.
|
||||
default:
|
||||
return nil, fmt.Errorf("%#v is expected to be %T", in, m)
|
||||
}
|
||||
return p.Replica.Count, nil
|
||||
}
|
||||
|
||||
func NewReplicaCountTransformerPlugin() resmap.TransformerPlugin {
|
||||
return &ReplicaCountTransformerPlugin{}
|
||||
}
|
||||
42
api/builtins/SecretGenerator.go
Normal file
42
api/builtins/SecretGenerator.go
Normal file
@@ -0,0 +1,42 @@
|
||||
// Code generated by pluginator on SecretGenerator; DO NOT EDIT.
|
||||
// pluginator {unknown 1970-01-01T00:00:00Z }
|
||||
|
||||
package builtins
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/api/kv"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
type SecretGeneratorPlugin struct {
|
||||
h *resmap.PluginHelpers
|
||||
types.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
||||
types.GeneratorOptions
|
||||
types.SecretArgs
|
||||
}
|
||||
|
||||
func (p *SecretGeneratorPlugin) Config(h *resmap.PluginHelpers, config []byte) (err error) {
|
||||
p.GeneratorOptions = types.GeneratorOptions{}
|
||||
p.SecretArgs = types.SecretArgs{}
|
||||
err = yaml.Unmarshal(config, p)
|
||||
if p.SecretArgs.Name == "" {
|
||||
p.SecretArgs.Name = p.Name
|
||||
}
|
||||
if p.SecretArgs.Namespace == "" {
|
||||
p.SecretArgs.Namespace = p.Namespace
|
||||
}
|
||||
p.h = h
|
||||
return
|
||||
}
|
||||
|
||||
func (p *SecretGeneratorPlugin) Generate() (resmap.ResMap, error) {
|
||||
return p.h.ResmapFactory().FromSecretArgs(
|
||||
kv.NewLoader(p.h.Loader(), p.h.Validator()),
|
||||
&p.GeneratorOptions, p.SecretArgs)
|
||||
}
|
||||
|
||||
func NewSecretGeneratorPlugin() resmap.GeneratorPlugin {
|
||||
return &SecretGeneratorPlugin{}
|
||||
}
|
||||
8
api/builtins/doc.go
Normal file
8
api/builtins/doc.go
Normal file
@@ -0,0 +1,8 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package builtins holds code generated from the builtin plugins.
|
||||
// The "builtin" plugins are written as normal plugins and can
|
||||
// be used as such, but they are also used to generate the code
|
||||
// in this package so they can be statically linked to client code.
|
||||
package builtins
|
||||
79
api/filesys/confirmeddir.go
Normal file
79
api/filesys/confirmeddir.go
Normal file
@@ -0,0 +1,79 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package filesys
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ConfirmedDir is a clean, absolute, delinkified path
|
||||
// that was confirmed to point to an existing directory.
|
||||
type ConfirmedDir string
|
||||
|
||||
// NewTmpConfirmedDir returns a temporary dir, else error.
|
||||
// The directory is cleaned, no symlinks, etc. so it's
|
||||
// returned as a ConfirmedDir.
|
||||
func NewTmpConfirmedDir() (ConfirmedDir, error) {
|
||||
n, err := ioutil.TempDir("", "kustomize-")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// In MacOs `ioutil.TempDir` creates a directory
|
||||
// with root in the `/var` folder, which is in turn
|
||||
// a symlinked path to `/private/var`.
|
||||
// Function `filepath.EvalSymlinks`is used to
|
||||
// resolve the real absolute path.
|
||||
deLinked, err := filepath.EvalSymlinks(n)
|
||||
return ConfirmedDir(deLinked), err
|
||||
}
|
||||
|
||||
// HasPrefix returns true if the directory argument
|
||||
// is a prefix of self (d) from the point of view of
|
||||
// a file system.
|
||||
//
|
||||
// I.e., it's true if the argument equals or contains
|
||||
// self (d) in a file path sense.
|
||||
//
|
||||
// HasPrefix emulates the semantics of strings.HasPrefix
|
||||
// such that the following are true:
|
||||
//
|
||||
// strings.HasPrefix("foobar", "foobar")
|
||||
// strings.HasPrefix("foobar", "foo")
|
||||
// strings.HasPrefix("foobar", "")
|
||||
//
|
||||
// d := fSys.ConfirmDir("/foo/bar")
|
||||
// d.HasPrefix("/foo/bar")
|
||||
// d.HasPrefix("/foo")
|
||||
// d.HasPrefix("/")
|
||||
//
|
||||
// Not contacting a file system here to check for
|
||||
// actual path existence.
|
||||
//
|
||||
// This is tested on linux, but will have trouble
|
||||
// on other operating systems.
|
||||
// TODO(monopole) Refactor when #golang/go/18358 closes.
|
||||
// See also:
|
||||
// https://github.com/golang/go/issues/18358
|
||||
// https://github.com/golang/dep/issues/296
|
||||
// https://github.com/golang/dep/blob/master/internal/fs/fs.go#L33
|
||||
// https://codereview.appspot.com/5712045
|
||||
func (d ConfirmedDir) HasPrefix(path ConfirmedDir) bool {
|
||||
if path.String() == string(filepath.Separator) || path == d {
|
||||
return true
|
||||
}
|
||||
return strings.HasPrefix(
|
||||
string(d),
|
||||
string(path)+string(filepath.Separator))
|
||||
}
|
||||
|
||||
func (d ConfirmedDir) Join(path string) string {
|
||||
return filepath.Join(string(d), path)
|
||||
}
|
||||
|
||||
func (d ConfirmedDir) String() string {
|
||||
return string(d)
|
||||
}
|
||||
109
api/filesys/confirmeddir_test.go
Normal file
109
api/filesys/confirmeddir_test.go
Normal file
@@ -0,0 +1,109 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package filesys_test
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
. "sigs.k8s.io/kustomize/api/filesys"
|
||||
)
|
||||
|
||||
func TestJoin(t *testing.T) {
|
||||
fSys := MakeFsInMemory()
|
||||
err := fSys.Mkdir("/foo")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
d, f, err := fSys.CleanedAbs("/foo")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
if f != "" {
|
||||
t.Fatalf("unexpected file: %v", f)
|
||||
}
|
||||
if d.Join("bar") != "/foo/bar" {
|
||||
t.Fatalf("expected join %s", d.Join("bar"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasPrefix_Slash(t *testing.T) {
|
||||
fSys := MakeFsInMemory()
|
||||
d, f, err := fSys.CleanedAbs("/")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
if f != "" {
|
||||
t.Fatalf("unexpected file: %v", f)
|
||||
}
|
||||
if d.HasPrefix("/hey") {
|
||||
t.Fatalf("should be false")
|
||||
}
|
||||
if !d.HasPrefix("/") {
|
||||
t.Fatalf("/ should have the prefix /")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasPrefix_SlashFoo(t *testing.T) {
|
||||
fSys := MakeFsInMemory()
|
||||
err := fSys.Mkdir("/foo")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
d, _, err := fSys.CleanedAbs("/foo")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
if d.HasPrefix("/fo") {
|
||||
t.Fatalf("/foo does not have path prefix /fo")
|
||||
}
|
||||
if d.HasPrefix("/fod") {
|
||||
t.Fatalf("/foo does not have path prefix /fod")
|
||||
}
|
||||
if !d.HasPrefix("/foo") {
|
||||
t.Fatalf("/foo should have prefix /foo")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasPrefix_SlashFooBar(t *testing.T) {
|
||||
fSys := MakeFsInMemory()
|
||||
err := fSys.MkdirAll("/foo/bar")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
d, _, err := fSys.CleanedAbs("/foo/bar")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
if d.HasPrefix("/fo") {
|
||||
t.Fatalf("/foo/bar does not have path prefix /fo")
|
||||
}
|
||||
if d.HasPrefix("/foobar") {
|
||||
t.Fatalf("/foo/bar does not have path prefix /foobar")
|
||||
}
|
||||
if !d.HasPrefix("/foo/bar") {
|
||||
t.Fatalf("/foo/bar should have prefix /foo/bar")
|
||||
}
|
||||
if !d.HasPrefix("/foo") {
|
||||
t.Fatalf("/foo/bar should have prefix /foo")
|
||||
}
|
||||
if !d.HasPrefix("/") {
|
||||
t.Fatalf("/foo/bar should have prefix /")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewTempConfirmDir(t *testing.T) {
|
||||
tmp, err := NewTmpConfirmedDir()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
delinked, err := filepath.EvalSymlinks(string(tmp))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if string(tmp) != delinked {
|
||||
t.Fatalf("unexpected path containing symlinks")
|
||||
}
|
||||
}
|
||||
15
api/filesys/file.go
Normal file
15
api/filesys/file.go
Normal file
@@ -0,0 +1,15 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package filesys
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// File groups the basic os.File methods.
|
||||
type File interface {
|
||||
io.ReadWriteCloser
|
||||
Stat() (os.FileInfo, error)
|
||||
}
|
||||
34
api/filesys/fileinfo.go
Normal file
34
api/filesys/fileinfo.go
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package filesys
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
var _ os.FileInfo = fileInfo{}
|
||||
|
||||
// fileInfo implements os.FileInfo for a fileInMemory instance.
|
||||
type fileInfo struct {
|
||||
node *fsNode
|
||||
}
|
||||
|
||||
// Name returns the name of the file
|
||||
func (fi fileInfo) Name() string { return fi.node.Name() }
|
||||
|
||||
// Size returns the size of the file
|
||||
func (fi fileInfo) Size() int64 { return fi.node.Size() }
|
||||
|
||||
// Mode returns the file mode
|
||||
func (fi fileInfo) Mode() os.FileMode { return 0777 }
|
||||
|
||||
// ModTime returns a bogus time
|
||||
func (fi fileInfo) ModTime() time.Time { return time.Time{} }
|
||||
|
||||
// IsDir returns true if it is a directory
|
||||
func (fi fileInfo) IsDir() bool { return fi.node.isNodeADir() }
|
||||
|
||||
// Sys should return underlying data source, but it now returns nil
|
||||
func (fi fileInfo) Sys() interface{} { return nil }
|
||||
27
api/filesys/fileondisk.go
Normal file
27
api/filesys/fileondisk.go
Normal file
@@ -0,0 +1,27 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package filesys
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
var _ File = &fileOnDisk{}
|
||||
|
||||
// fileOnDisk implements File using the local filesystem.
|
||||
type fileOnDisk struct {
|
||||
file *os.File
|
||||
}
|
||||
|
||||
// Close closes a file.
|
||||
func (f *fileOnDisk) Close() error { return f.file.Close() }
|
||||
|
||||
// Read reads a file's content.
|
||||
func (f *fileOnDisk) Read(p []byte) (n int, err error) { return f.file.Read(p) }
|
||||
|
||||
// Write writes bytes to a file
|
||||
func (f *fileOnDisk) Write(p []byte) (n int, err error) { return f.file.Write(p) }
|
||||
|
||||
// Stat returns an interface which has all the information regarding the file.
|
||||
func (f *fileOnDisk) Stat() (os.FileInfo, error) { return f.file.Stat() }
|
||||
50
api/filesys/filesystem.go
Normal file
50
api/filesys/filesystem.go
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package filesys provides a file system abstraction layer.
|
||||
package filesys
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const (
|
||||
Separator = string(filepath.Separator)
|
||||
SelfDir = "."
|
||||
ParentDir = ".."
|
||||
)
|
||||
|
||||
// FileSystem groups basic os filesystem methods.
|
||||
// It's supposed be functional subset of https://golang.org/pkg/os
|
||||
type FileSystem interface {
|
||||
// Create a file.
|
||||
Create(path string) (File, error)
|
||||
// MkDir makes a directory.
|
||||
Mkdir(path string) error
|
||||
// MkDirAll makes a directory path, creating intervening directories.
|
||||
MkdirAll(path string) error
|
||||
// RemoveAll removes path and any children it contains.
|
||||
RemoveAll(path string) error
|
||||
// Open opens the named file for reading.
|
||||
Open(path string) (File, error)
|
||||
// IsDir returns true if the path is a directory.
|
||||
IsDir(path string) bool
|
||||
// CleanedAbs converts the given path into a
|
||||
// directory and a file name, where the directory
|
||||
// is represented as a ConfirmedDir and all that implies.
|
||||
// If the entire path is a directory, the file component
|
||||
// is an empty string.
|
||||
CleanedAbs(path string) (ConfirmedDir, string, error)
|
||||
// Exists is true if the path exists in the file system.
|
||||
Exists(path string) bool
|
||||
// Glob returns the list of matching files,
|
||||
// emulating https://golang.org/pkg/path/filepath/#Glob
|
||||
Glob(pattern string) ([]string, error)
|
||||
// ReadFile returns the contents of the file at the given path.
|
||||
ReadFile(path string) ([]byte, error)
|
||||
// WriteFile writes the data to a file at the given path,
|
||||
// overwriting anything that's already there.
|
||||
WriteFile(path string, data []byte) error
|
||||
// Walk walks the file system with the given WalkFunc.
|
||||
Walk(path string, walkFn filepath.WalkFunc) error
|
||||
}
|
||||
557
api/filesys/fsnode.go
Normal file
557
api/filesys/fsnode.go
Normal file
@@ -0,0 +1,557 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package filesys
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var _ File = &fsNode{}
|
||||
var _ FileSystem = &fsNode{}
|
||||
|
||||
// fsNode is either a file or a directory.
|
||||
type fsNode struct {
|
||||
// What node owns me?
|
||||
parent *fsNode
|
||||
|
||||
// Value to return as the Name() when the
|
||||
// parent is nil.
|
||||
nilParentName string
|
||||
|
||||
// A directory mapping names to nodes.
|
||||
// If dir is nil, then self node is a file.
|
||||
// If dir is non-nil, then self node is a directory,
|
||||
// albeit possibly an empty directory.
|
||||
dir map[string]*fsNode
|
||||
|
||||
// if this node is a file, this is the content.
|
||||
content []byte
|
||||
|
||||
// if this node is a file, this tracks whether or
|
||||
// not it is "open".
|
||||
open bool
|
||||
}
|
||||
|
||||
// MakeEmptyDirInMemory returns an empty directory.
|
||||
// The paths of nodes in this object will never
|
||||
// report a leading Separator, meaning they
|
||||
// aren't "absolute" in the sense defined by
|
||||
// https://golang.org/pkg/path/filepath/#IsAbs.
|
||||
func MakeEmptyDirInMemory() *fsNode {
|
||||
return &fsNode{
|
||||
dir: make(map[string]*fsNode),
|
||||
}
|
||||
}
|
||||
|
||||
// MakeFsInMemory returns an empty 'file system'.
|
||||
// The paths of nodes in this object will always
|
||||
// report a leading Separator, meaning they
|
||||
// are "absolute" in the sense defined by
|
||||
// https://golang.org/pkg/path/filepath/#IsAbs.
|
||||
// This is a relevant difference when using Walk,
|
||||
// Glob, Match, etc.
|
||||
func MakeFsInMemory() FileSystem {
|
||||
return &fsNode{
|
||||
nilParentName: Separator,
|
||||
dir: make(map[string]*fsNode),
|
||||
}
|
||||
}
|
||||
|
||||
// Name returns the name of the node.
|
||||
func (n *fsNode) Name() string {
|
||||
if n.parent == nil {
|
||||
// Unable to lookup name in parent.
|
||||
return n.nilParentName
|
||||
}
|
||||
if !n.parent.isNodeADir() {
|
||||
log.Fatal("parent not a dir")
|
||||
}
|
||||
for key, value := range n.parent.dir {
|
||||
if value == n {
|
||||
return key
|
||||
}
|
||||
}
|
||||
log.Fatal("unable to find fsNode name")
|
||||
return ""
|
||||
}
|
||||
|
||||
// Path returns the full path to the node.
|
||||
func (n *fsNode) Path() string {
|
||||
if n.parent == nil {
|
||||
return n.nilParentName
|
||||
}
|
||||
if !n.parent.isNodeADir() {
|
||||
log.Fatal("parent not a dir, structural error")
|
||||
}
|
||||
return filepath.Join(n.parent.Path(), n.Name())
|
||||
}
|
||||
|
||||
// mySplit trims trailing separators from the directory
|
||||
// result of filepath.Split.
|
||||
func mySplit(s string) (string, string) {
|
||||
dName, fName := filepath.Split(s)
|
||||
return StripTrailingSeps(dName), fName
|
||||
}
|
||||
|
||||
func (n *fsNode) addFile(name string, c []byte) (result *fsNode, err error) {
|
||||
parent := n
|
||||
dName, fileName := mySplit(name)
|
||||
if dName != "" {
|
||||
parent, err = parent.addDir(dName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if !isLegalFileNameForCreation(fileName) {
|
||||
return nil, fmt.Errorf(
|
||||
"illegal name '%s' in file creation", fileName)
|
||||
}
|
||||
result, ok := parent.dir[fileName]
|
||||
if ok {
|
||||
// File already exists; overwrite it.
|
||||
result.content = c
|
||||
return result, nil
|
||||
}
|
||||
result = &fsNode{
|
||||
content: c,
|
||||
parent: parent,
|
||||
}
|
||||
parent.dir[fileName] = result
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Create implements FileSystem.
|
||||
// Create makes an empty file.
|
||||
func (n *fsNode) Create(path string) (result File, err error) {
|
||||
return n.AddFile(path, []byte{})
|
||||
}
|
||||
|
||||
// WriteFile implements FileSystem.
|
||||
func (n *fsNode) WriteFile(path string, d []byte) error {
|
||||
_, err := n.AddFile(path, d)
|
||||
return err
|
||||
}
|
||||
|
||||
// AddFile adds a file and any necessary containing
|
||||
// directories to the node.
|
||||
func (n *fsNode) AddFile(
|
||||
name string, c []byte) (result *fsNode, err error) {
|
||||
if n.dir == nil {
|
||||
return nil, fmt.Errorf(
|
||||
"cannot add a file to a non-directory '%s'", n.Name())
|
||||
}
|
||||
return n.addFile(cleanQueryPath(name), c)
|
||||
}
|
||||
|
||||
func (n *fsNode) addDir(path string) (result *fsNode, err error) {
|
||||
parent := n
|
||||
dName, subDirName := mySplit(path)
|
||||
if dName != "" {
|
||||
parent, err = n.addDir(dName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
switch subDirName {
|
||||
case "", SelfDir:
|
||||
return n, nil
|
||||
case ParentDir:
|
||||
if n.parent == nil {
|
||||
return nil, fmt.Errorf(
|
||||
"cannot add a directory above '%s'", n.Path())
|
||||
}
|
||||
return n.parent, nil
|
||||
default:
|
||||
if !isLegalFileNameForCreation(subDirName) {
|
||||
return nil, fmt.Errorf(
|
||||
"illegal name '%s' in directory creation", subDirName)
|
||||
}
|
||||
result, ok := parent.dir[subDirName]
|
||||
if ok {
|
||||
if result.isNodeADir() {
|
||||
// it's already there.
|
||||
return result, nil
|
||||
}
|
||||
return nil, fmt.Errorf(
|
||||
"cannot make dir '%s'; a file of that name already exists in '%s'",
|
||||
subDirName, parent.Name())
|
||||
}
|
||||
result = &fsNode{
|
||||
dir: make(map[string]*fsNode),
|
||||
parent: parent,
|
||||
}
|
||||
parent.dir[subDirName] = result
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Mkdir implements FileSystem.
|
||||
// Mkdir creates a directory.
|
||||
func (n *fsNode) Mkdir(path string) error {
|
||||
_, err := n.AddDir(path)
|
||||
return err
|
||||
}
|
||||
|
||||
// MkdirAll implements FileSystem.
|
||||
// MkdirAll creates a directory.
|
||||
func (n *fsNode) MkdirAll(path string) error {
|
||||
_, err := n.AddDir(path)
|
||||
return err
|
||||
}
|
||||
|
||||
// AddDir adds a directory to the node, not complaining
|
||||
// if it is already there.
|
||||
func (n *fsNode) AddDir(path string) (result *fsNode, err error) {
|
||||
if n.dir == nil {
|
||||
return nil, fmt.Errorf(
|
||||
"cannot add a directory to file node '%s'", n.Name())
|
||||
}
|
||||
return n.addDir(cleanQueryPath(path))
|
||||
}
|
||||
|
||||
// CleanedAbs implements FileSystem.
|
||||
func (n *fsNode) CleanedAbs(path string) (ConfirmedDir, string, error) {
|
||||
node, err := n.Find(path)
|
||||
if err != nil {
|
||||
return "", "", errors.Wrap(err, "unable to clean")
|
||||
}
|
||||
if node == nil {
|
||||
return "", "", fmt.Errorf("'%s' doesn't exist", path)
|
||||
}
|
||||
if node.isNodeADir() {
|
||||
return ConfirmedDir(node.Path()), "", nil
|
||||
}
|
||||
return ConfirmedDir(node.parent.Path()), node.Name(), nil
|
||||
}
|
||||
|
||||
// Exists implements FileSystem.
|
||||
// Exists returns true if the path exists.
|
||||
func (n *fsNode) Exists(path string) bool {
|
||||
if !n.isNodeADir() {
|
||||
return n.Name() == path
|
||||
}
|
||||
result, err := n.Find(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return result != nil
|
||||
}
|
||||
|
||||
func cleanQueryPath(path string) string {
|
||||
// Always ignore leading separator?
|
||||
// Remember that filepath.Clean returns "." if
|
||||
// given an empty string argument.
|
||||
return filepath.Clean(StripLeadingSeps(path))
|
||||
}
|
||||
|
||||
// Find finds the given node, else nil if not found.
|
||||
// Return error on structural/argument errors.
|
||||
func (n *fsNode) Find(path string) (*fsNode, error) {
|
||||
if !n.isNodeADir() {
|
||||
return nil, fmt.Errorf("can only find inside a dir")
|
||||
}
|
||||
if path == "" {
|
||||
// Special case; check *before* cleaning and *before*
|
||||
// comparison to nilParentName.
|
||||
return nil, nil
|
||||
}
|
||||
if (n.parent == nil && path == n.nilParentName) || path == SelfDir {
|
||||
// Special case
|
||||
return n, nil
|
||||
}
|
||||
return n.findIt(cleanQueryPath(path))
|
||||
}
|
||||
|
||||
func (n *fsNode) findIt(path string) (result *fsNode, err error) {
|
||||
parent := n
|
||||
dName, item := mySplit(path)
|
||||
if dName != "" {
|
||||
parent, err = n.findIt(dName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if parent == nil {
|
||||
// all done, target doesn't exist.
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
if !parent.isNodeADir() {
|
||||
return nil, fmt.Errorf("'%s' is not a directory", parent.Path())
|
||||
}
|
||||
return parent.dir[item], nil
|
||||
}
|
||||
|
||||
// RemoveAll implements FileSystem.
|
||||
// RemoveAll removes an item and everything it contains.
|
||||
func (n *fsNode) RemoveAll(path string) error {
|
||||
result, err := n.Find(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if result == nil {
|
||||
return fmt.Errorf("cannot find '%s' to remove it", path)
|
||||
}
|
||||
return result.Remove()
|
||||
}
|
||||
|
||||
// Remove drop the node, and everything it contains, from its parent.
|
||||
func (n *fsNode) Remove() error {
|
||||
if n.parent == nil {
|
||||
return fmt.Errorf("cannot remove a root node")
|
||||
}
|
||||
if !n.parent.isNodeADir() {
|
||||
log.Fatal("parent not a dir")
|
||||
}
|
||||
for key, value := range n.parent.dir {
|
||||
if value == n {
|
||||
delete(n.parent.dir, key)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
log.Fatal("unable to find self in parent")
|
||||
return nil
|
||||
}
|
||||
|
||||
// isNodeADir returns true if the node is a directory.
|
||||
// Cannot collide with the poorly named "IsDir".
|
||||
func (n *fsNode) isNodeADir() bool {
|
||||
return n.dir != nil
|
||||
}
|
||||
|
||||
// IsDir implements FileSystem.
|
||||
// IsDir returns true if the argument resolves
|
||||
// to a directory rooted at the node.
|
||||
func (n *fsNode) IsDir(path string) bool {
|
||||
result, err := n.Find(path)
|
||||
if err != nil || result == nil {
|
||||
return false
|
||||
}
|
||||
return result.isNodeADir()
|
||||
}
|
||||
|
||||
// Size returns the size of the node.
|
||||
func (n *fsNode) Size() int64 {
|
||||
if n.isNodeADir() {
|
||||
return int64(len(n.dir))
|
||||
}
|
||||
return int64(len(n.content))
|
||||
}
|
||||
|
||||
// Open implements FileSystem.
|
||||
// Open opens the node for reading (just marks it).
|
||||
func (n *fsNode) Open(path string) (File, error) {
|
||||
result, err := n.Find(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if result == nil {
|
||||
return nil, fmt.Errorf("cannot find '%s' to open it", path)
|
||||
}
|
||||
result.open = true
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Close marks the node closed.
|
||||
func (n *fsNode) Close() error {
|
||||
n.open = false
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadFile implements FileSystem.
|
||||
func (n *fsNode) ReadFile(path string) (c []byte, err error) {
|
||||
result, err := n.Find(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if result == nil {
|
||||
return nil, fmt.Errorf("cannot find '%s' to read it", path)
|
||||
}
|
||||
c = make([]byte, len(result.content))
|
||||
_, err = result.Read(c)
|
||||
return c, err
|
||||
}
|
||||
|
||||
// Read returns the content of the file node.
|
||||
func (n *fsNode) Read(d []byte) (c int, err error) {
|
||||
if n.isNodeADir() {
|
||||
return 0, fmt.Errorf(
|
||||
"cannot read content from non-file '%s'", n.Path())
|
||||
}
|
||||
return copy(d, n.content), nil
|
||||
}
|
||||
|
||||
// Write saves the contents of the argument to the file node.
|
||||
func (n *fsNode) Write(p []byte) (c int, err error) {
|
||||
if n.isNodeADir() {
|
||||
return 0, fmt.Errorf(
|
||||
"cannot write content to non-file '%s'", n.Path())
|
||||
}
|
||||
n.content = make([]byte, len(p))
|
||||
return copy(n.content, p), nil
|
||||
}
|
||||
|
||||
// ContentMatches returns true if v matches fake file's content.
|
||||
func (n *fsNode) ContentMatches(v []byte) bool {
|
||||
return bytes.Equal(v, n.content)
|
||||
}
|
||||
|
||||
// GetContent the content of a fake file.
|
||||
func (n *fsNode) GetContent() []byte {
|
||||
return n.content
|
||||
}
|
||||
|
||||
// Stat returns an instance of FileInfo.
|
||||
func (n *fsNode) Stat() (os.FileInfo, error) {
|
||||
return fileInfo{node: n}, nil
|
||||
}
|
||||
|
||||
// Walk implements FileSystem.
|
||||
func (n *fsNode) Walk(path string, walkFn filepath.WalkFunc) error {
|
||||
result, err := n.Find(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if result == nil {
|
||||
return fmt.Errorf("cannot find '%s' to walk it", path)
|
||||
}
|
||||
return result.WalkMe(walkFn)
|
||||
}
|
||||
|
||||
// Walk runs the given walkFn on each node.
|
||||
func (n *fsNode) WalkMe(walkFn filepath.WalkFunc) error {
|
||||
fi, err := n.Stat()
|
||||
// always visit self first
|
||||
err = walkFn(n.Path(), fi, err)
|
||||
if !n.isNodeADir() {
|
||||
// it's a file, so nothing more to do
|
||||
return err
|
||||
}
|
||||
// process self as a directory
|
||||
if err == filepath.SkipDir {
|
||||
return nil
|
||||
}
|
||||
// Walk is supposed to visit in lexical order.
|
||||
for _, k := range n.sortedDirEntries() {
|
||||
if err := n.dir[k].WalkMe(walkFn); err != nil {
|
||||
if err == filepath.SkipDir {
|
||||
// stop processing this directory
|
||||
break
|
||||
}
|
||||
// bail out completely
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *fsNode) sortedDirEntries() []string {
|
||||
keys := make([]string, len(n.dir))
|
||||
i := 0
|
||||
for k := range n.dir {
|
||||
keys[i] = k
|
||||
i++
|
||||
}
|
||||
sort.Strings(keys)
|
||||
return keys
|
||||
}
|
||||
|
||||
// FileCount returns a count of files.
|
||||
// Directories, empty or otherwise, not counted.
|
||||
func (n *fsNode) FileCount() int {
|
||||
count := 0
|
||||
n.WalkMe(func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.IsDir() {
|
||||
count++
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return count
|
||||
}
|
||||
|
||||
func (n *fsNode) DebugPrint() {
|
||||
n.WalkMe(func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
fmt.Printf("err '%v' at path %q\n", err, path)
|
||||
return nil
|
||||
}
|
||||
if info.IsDir() {
|
||||
if info.Size() == 0 {
|
||||
fmt.Println("empty dir: " + path)
|
||||
}
|
||||
} else {
|
||||
fmt.Println(" file: " + path)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
var legalFileNamePattern = regexp.MustCompile("^[a-zA-Z0-9-_.]+$")
|
||||
|
||||
// This rules enforced here should be simpler and tighter
|
||||
// than what's allowed on a real OS.
|
||||
// Should be fine for testing or in-memory purposes.
|
||||
func isLegalFileNameForCreation(n string) bool {
|
||||
if n == "" || n == SelfDir || !legalFileNamePattern.MatchString(n) {
|
||||
return false
|
||||
}
|
||||
return !strings.Contains(n, ParentDir)
|
||||
}
|
||||
|
||||
// RegExpGlob returns a list of file paths matching the regexp.
|
||||
// Excludes directories.
|
||||
func (n *fsNode) RegExpGlob(pattern string) ([]string, error) {
|
||||
var result []string
|
||||
var expression = regexp.MustCompile(pattern)
|
||||
n.WalkMe(func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.IsDir() {
|
||||
if expression.MatchString(path) {
|
||||
result = append(result, path)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
sort.Strings(result)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Glob implements FileSystem.
|
||||
// Glob returns the list of file paths matching
|
||||
// per filepath.Match semantics, i.e. unlike RegExpGlob,
|
||||
// Match("foo/a*") will not match sub-sub directories of foo.
|
||||
// This is how /bin/ls behaves.
|
||||
func (n *fsNode) Glob(pattern string) ([]string, error) {
|
||||
var result []string
|
||||
n.WalkMe(func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.IsDir() {
|
||||
match, err := filepath.Match(pattern, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if match {
|
||||
result = append(result, path)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
sort.Strings(result)
|
||||
return result, nil
|
||||
}
|
||||
788
api/filesys/fsnode_test.go
Normal file
788
api/filesys/fsnode_test.go
Normal file
@@ -0,0 +1,788 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package filesys
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const content = `
|
||||
Lorem ipsum dolor sit amet,
|
||||
consectetur adipiscing elit,
|
||||
sed do eiusmod tempor incididunt
|
||||
ut labore et dolore magna aliqua.
|
||||
`
|
||||
const shortContent = "hi"
|
||||
|
||||
var topCases = []pathCase{
|
||||
{
|
||||
what: "dotdot",
|
||||
arg: ParentDir,
|
||||
errStr: "illegal name '..' in file creation",
|
||||
},
|
||||
{
|
||||
what: "empty",
|
||||
arg: "",
|
||||
name: "",
|
||||
errStr: "illegal name '.' in file creation",
|
||||
},
|
||||
{
|
||||
what: "simple",
|
||||
arg: "bob",
|
||||
name: "bob",
|
||||
path: "bob",
|
||||
},
|
||||
{
|
||||
what: "longer",
|
||||
arg: filepath.Join("longer", "bob"),
|
||||
name: "bob",
|
||||
path: filepath.Join("longer", "bob"),
|
||||
},
|
||||
{
|
||||
what: "longer yet",
|
||||
arg: filepath.Join("longer", "foo", "bar", "beans", "bob"),
|
||||
name: "bob",
|
||||
path: filepath.Join("longer", "foo", "bar", "beans", "bob"),
|
||||
},
|
||||
{
|
||||
what: "tricky",
|
||||
arg: filepath.Join("bob", ParentDir, "sally"),
|
||||
name: "sally",
|
||||
path: "sally",
|
||||
},
|
||||
{
|
||||
what: "trickier",
|
||||
arg: filepath.Join("bob", "sally", ParentDir, ParentDir, "jean"),
|
||||
name: "jean",
|
||||
path: "jean",
|
||||
},
|
||||
}
|
||||
|
||||
func TestMakeEmptyDirInMemory(t *testing.T) {
|
||||
n := MakeEmptyDirInMemory()
|
||||
if !n.isNodeADir() {
|
||||
t.Fatalf("not a directory")
|
||||
}
|
||||
if n.Size() != 0 {
|
||||
t.Fatalf("unexpected size %d", n.Size())
|
||||
}
|
||||
if n.Name() != "" {
|
||||
t.Fatalf("unexpected name '%s'", n.Name())
|
||||
}
|
||||
if n.Path() != "" {
|
||||
t.Fatalf("unexpected path '%s'", n.Path())
|
||||
}
|
||||
runBasicOperations(
|
||||
t, "MakeEmptyDirInMemory", false, topCases, n)
|
||||
}
|
||||
|
||||
func TestMakeFsInMemory(t *testing.T) {
|
||||
runBasicOperations(
|
||||
t, "MakeFsInMemory", true, topCases, MakeFsInMemory())
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func runBasicOperations(
|
||||
t *testing.T, tName string, isFSysRooted bool,
|
||||
cases []pathCase, fSys FileSystem) {
|
||||
buff := make([]byte, 500)
|
||||
for _, c := range cases {
|
||||
err := fSys.WriteFile(c.arg, []byte(content))
|
||||
if c.errStr != "" {
|
||||
if err == nil {
|
||||
t.Fatalf("%s; expected error writing to '%s'!", c.what, c.arg)
|
||||
}
|
||||
if !strings.Contains(err.Error(), c.errStr) {
|
||||
t.Fatalf("%s; expected err containing '%s', got '%v'",
|
||||
c.what, c.errStr, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("%s; unexpected error: %v", c.what, err)
|
||||
}
|
||||
if !fSys.Exists(c.path) {
|
||||
t.Fatalf("%s; expect existence of '%s'", c.what, c.path)
|
||||
}
|
||||
stuff, err := fSys.ReadFile(c.path)
|
||||
if err != nil {
|
||||
t.Fatalf("%s; unexpected error: %v", c.what, err)
|
||||
}
|
||||
if string(stuff) != content {
|
||||
t.Fatalf("%s; unexpected content '%s'", c.what, stuff)
|
||||
}
|
||||
f, err := fSys.Open(c.arg)
|
||||
if err != nil {
|
||||
t.Fatalf("%s; unexpected error: %v", c.what, err)
|
||||
}
|
||||
fi, err := f.Stat()
|
||||
if err != nil {
|
||||
t.Fatalf("%s; unexpected error: %v", c.what, err)
|
||||
}
|
||||
if fi.Name() != c.name {
|
||||
t.Fatalf("%s; expected name '%s', got '%s'", c.what, c.name, fi.Name())
|
||||
}
|
||||
count, err := f.Read(buff)
|
||||
if err != nil {
|
||||
t.Fatalf("%s; unexpected error: %v", c.what, err)
|
||||
}
|
||||
if string(buff[:count]) != content {
|
||||
t.Fatalf("%s; unexpected buff '%s'", c.what, buff)
|
||||
}
|
||||
count, err = f.Write([]byte(shortContent))
|
||||
if err != nil {
|
||||
t.Fatalf("%s; unexpected error: %v", c.what, err)
|
||||
}
|
||||
if count != len(shortContent) {
|
||||
t.Fatalf("%s; unexpected count: %d", c.what, len(shortContent))
|
||||
}
|
||||
stuff, err = fSys.ReadFile(c.path)
|
||||
if err != nil {
|
||||
t.Fatalf("%s; unexpected error: %v", c.what, err)
|
||||
}
|
||||
if string(stuff) != shortContent {
|
||||
t.Fatalf("%s; unexpected content '%s'", c.what, stuff)
|
||||
}
|
||||
}
|
||||
|
||||
var actualPaths []string
|
||||
var err error
|
||||
prefix := ""
|
||||
{
|
||||
root := SelfDir
|
||||
if isFSysRooted {
|
||||
root = Separator
|
||||
prefix = Separator
|
||||
}
|
||||
err = fSys.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
fmt.Printf("err '%v' at path %q\n", err, path)
|
||||
return nil
|
||||
}
|
||||
if !info.IsDir() {
|
||||
actualPaths = append(actualPaths, path)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
}
|
||||
var expectedPaths []string
|
||||
for _, c := range cases {
|
||||
if c.errStr == "" {
|
||||
expectedPaths = append(expectedPaths, prefix+c.path)
|
||||
}
|
||||
}
|
||||
sort.Strings(expectedPaths)
|
||||
assertEqualStringSlices(t, expectedPaths, actualPaths, tName)
|
||||
}
|
||||
|
||||
type pathCase struct {
|
||||
what string
|
||||
arg string
|
||||
name string
|
||||
path string
|
||||
errStr string
|
||||
}
|
||||
|
||||
func TestAddDir(t *testing.T) {
|
||||
cases := []pathCase{
|
||||
{
|
||||
what: "dotdot",
|
||||
arg: ParentDir,
|
||||
errStr: "cannot add a directory above ''",
|
||||
},
|
||||
{
|
||||
what: "empty",
|
||||
arg: "",
|
||||
name: "",
|
||||
path: "",
|
||||
},
|
||||
{
|
||||
what: "simple",
|
||||
arg: "bob",
|
||||
name: "bob",
|
||||
path: "bob",
|
||||
},
|
||||
{
|
||||
what: "longer",
|
||||
arg: filepath.Join("longer", "bob"),
|
||||
name: "bob",
|
||||
path: filepath.Join("longer", "bob"),
|
||||
},
|
||||
{
|
||||
what: "longer yet",
|
||||
arg: filepath.Join("longer", "foo", "bar", "beans", "bob"),
|
||||
name: "bob",
|
||||
path: filepath.Join("longer", "foo", "bar", "beans", "bob"),
|
||||
},
|
||||
{
|
||||
what: "tricky",
|
||||
arg: filepath.Join("bob", ParentDir, "sally"),
|
||||
name: "sally",
|
||||
path: "sally",
|
||||
},
|
||||
{
|
||||
what: "trickier",
|
||||
arg: filepath.Join("bob", "sally", ParentDir, ParentDir, "jean"),
|
||||
name: "jean",
|
||||
path: "jean",
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
n := MakeEmptyDirInMemory()
|
||||
f, err := n.AddDir(c.arg)
|
||||
if c.errStr != "" {
|
||||
if err == nil {
|
||||
t.Fatalf("%s; expected error!", c.what)
|
||||
}
|
||||
if !strings.Contains(err.Error(), c.errStr) {
|
||||
t.Fatalf(
|
||||
"%s; expected error with '%s', got '%v'",
|
||||
c.what, c.errStr, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("%s; unexpected error: %v", c.what, err)
|
||||
}
|
||||
checkNode(t, c.what, f, c.name, 0, true, c.path)
|
||||
checkOsStat(t, c.what, f, f.Name(), 0, true)
|
||||
}
|
||||
}
|
||||
|
||||
var bagOfCases = []pathCase{
|
||||
{
|
||||
what: "empty",
|
||||
arg: "",
|
||||
errStr: "illegal name '.' in file creation",
|
||||
},
|
||||
{
|
||||
what: "simple",
|
||||
arg: "bob",
|
||||
name: "bob",
|
||||
path: "bob",
|
||||
},
|
||||
{
|
||||
what: "longer",
|
||||
arg: filepath.Join("longer", "bob"),
|
||||
name: "bob",
|
||||
path: filepath.Join("longer", "bob"),
|
||||
},
|
||||
{
|
||||
what: "longer",
|
||||
arg: filepath.Join("longer", "sally"),
|
||||
name: "sally",
|
||||
path: filepath.Join("longer", "sally"),
|
||||
},
|
||||
{
|
||||
what: "even longer",
|
||||
arg: filepath.Join("longer", "than", "the", "other", "bob"),
|
||||
name: "bob",
|
||||
path: filepath.Join("longer", "than", "the", "other", "bob"),
|
||||
},
|
||||
{
|
||||
what: "even longer",
|
||||
arg: filepath.Join("even", "much", "longer", "than", "the", "other", "bob"),
|
||||
name: "bob",
|
||||
path: filepath.Join("even", "much", "longer", "than", "the", "other", "bob"),
|
||||
},
|
||||
}
|
||||
|
||||
func TestAddFile(t *testing.T) {
|
||||
n := MakeEmptyDirInMemory()
|
||||
if n.FileCount() != 0 {
|
||||
t.Fatalf("expected no files, got %d", n.FileCount())
|
||||
}
|
||||
expectedFileCount := 0
|
||||
for _, c := range bagOfCases {
|
||||
f, err := n.AddFile(c.arg, []byte(content))
|
||||
if c.errStr != "" {
|
||||
if err == nil {
|
||||
t.Fatalf("%s; expected error!", c.what)
|
||||
}
|
||||
if !strings.Contains(err.Error(), c.errStr) {
|
||||
t.Fatalf("%s; expected err containing '%s', got '%v'",
|
||||
c.what, c.errStr, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("%s; unexpected error %v", c.what, err)
|
||||
}
|
||||
checkNode(t, c.what, f, c.name, len(content), false, c.path)
|
||||
checkOsStat(t, c.what, f, f.Name(), len(content), false)
|
||||
|
||||
result, err := n.Find(c.arg)
|
||||
if err != nil {
|
||||
t.Fatalf("%s; unexpected find error %v", c.what, err)
|
||||
}
|
||||
if result != f {
|
||||
t.Fatalf("%s; unexpected find result %v", c.what, result)
|
||||
}
|
||||
|
||||
result, err = n.Find(filepath.Join("longer", "bogus"))
|
||||
if err != nil {
|
||||
t.Fatalf("%s; unexpected find error %v", c.what, err)
|
||||
}
|
||||
if result != nil {
|
||||
t.Fatalf("%s; unexpected find result %v", c.what, result)
|
||||
}
|
||||
|
||||
expectedFileCount++
|
||||
fc := n.FileCount()
|
||||
if fc != expectedFileCount {
|
||||
t.Fatalf("expected file count %d, got %d",
|
||||
expectedFileCount, fc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func checkNode(
|
||||
t *testing.T, what string, f *fsNode, name string,
|
||||
size int, isDir bool, path string) {
|
||||
if f.isNodeADir() != isDir {
|
||||
t.Fatalf("%s; unexpected isNodeADir = %v", what, f.isNodeADir())
|
||||
}
|
||||
if f.Size() != int64(size) {
|
||||
t.Fatalf("%s; unexpected size %d", what, f.Size())
|
||||
}
|
||||
if name != f.Name() {
|
||||
t.Fatalf("%s; expected name '%s', got '%s'", what, name, f.Name())
|
||||
}
|
||||
if path != f.Path() {
|
||||
t.Fatalf("%s; expected path '%s', got '%s'", what, path, f.Path())
|
||||
}
|
||||
}
|
||||
|
||||
func checkOsStat(
|
||||
t *testing.T, what string, f File, name string,
|
||||
size int, isDir bool) {
|
||||
info, err := f.Stat()
|
||||
if err != nil {
|
||||
t.Fatalf("%s; unexpected stat error %v", what, err)
|
||||
}
|
||||
if info.IsDir() != isDir {
|
||||
t.Fatalf("%s; unexpected info.isNodeADir = %v", what, info.IsDir())
|
||||
}
|
||||
if info.Size() != int64(size) {
|
||||
t.Fatalf("%s; unexpected info.size %d", what, info.Size())
|
||||
}
|
||||
if info.Name() != name {
|
||||
t.Fatalf("%s; expected name '%s', got info.Name '%s'", what, name, info.Name())
|
||||
}
|
||||
}
|
||||
|
||||
var bunchOfFiles = []struct {
|
||||
path string
|
||||
addAsDir bool
|
||||
}{
|
||||
{
|
||||
path: filepath.Join("b", "e", "a", "c", "g"),
|
||||
},
|
||||
{
|
||||
path: filepath.Join("z", "r", "a", "b", "g"),
|
||||
},
|
||||
{
|
||||
path: filepath.Join("b", "q", "a", "c", "g"),
|
||||
},
|
||||
{
|
||||
path: filepath.Join("b", "a", "a", "m", "g"),
|
||||
addAsDir: true,
|
||||
},
|
||||
{
|
||||
path: filepath.Join("b", "w"),
|
||||
},
|
||||
{
|
||||
path: filepath.Join("b", "d", "a", "c", "m"),
|
||||
},
|
||||
{
|
||||
path: filepath.Join("b", "d", "z"),
|
||||
},
|
||||
{
|
||||
path: filepath.Join("b", "d", "y"),
|
||||
},
|
||||
{
|
||||
path: filepath.Join("b", "d", "ignore", "c", "n"),
|
||||
},
|
||||
{
|
||||
path: filepath.Join("b", "d", "x"),
|
||||
},
|
||||
{
|
||||
path: filepath.Join("b", "d", "ignore", "c", "o"),
|
||||
},
|
||||
{
|
||||
path: filepath.Join("b", "d", "ignore", "c", "m"),
|
||||
},
|
||||
{
|
||||
path: filepath.Join("b", "d", "a", "c", "i"),
|
||||
addAsDir: true,
|
||||
},
|
||||
{
|
||||
path: filepath.Join("x"),
|
||||
},
|
||||
{
|
||||
path: filepath.Join("y"),
|
||||
},
|
||||
{
|
||||
path: filepath.Join("b", "d", "a", "c", "i", "beans"),
|
||||
},
|
||||
{
|
||||
path: filepath.Join("b", "d", "a", "c", "r", "w"),
|
||||
addAsDir: true,
|
||||
},
|
||||
{
|
||||
path: filepath.Join("b", "d", "a", "c", "u"),
|
||||
},
|
||||
}
|
||||
|
||||
func makeLoadedFileTree(t *testing.T) *fsNode {
|
||||
n := MakeEmptyDirInMemory()
|
||||
var err error
|
||||
expectedFileCount := 0
|
||||
for _, item := range bunchOfFiles {
|
||||
if item.addAsDir {
|
||||
_, err = n.AddDir(item.path)
|
||||
} else {
|
||||
_, err = n.AddFile(item.path, []byte(content))
|
||||
expectedFileCount++
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
}
|
||||
}
|
||||
fc := n.FileCount()
|
||||
if fc != expectedFileCount {
|
||||
t.Fatalf("expected file count %d, got %d",
|
||||
expectedFileCount, fc)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func TestWalkMe(t *testing.T) {
|
||||
n := makeLoadedFileTree(t)
|
||||
var actualPaths []string
|
||||
err := n.WalkMe(func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
fmt.Printf("err '%v' at path %q\n", err, path)
|
||||
return nil
|
||||
}
|
||||
if info.IsDir() {
|
||||
if info.Name() == "ignore" {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
} else {
|
||||
actualPaths = append(actualPaths, path)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
}
|
||||
var expectedPaths []string
|
||||
for _, c := range bunchOfFiles {
|
||||
if !c.addAsDir && !strings.Contains(c.path, "ignore") {
|
||||
expectedPaths = append(expectedPaths, c.path)
|
||||
}
|
||||
}
|
||||
sort.Strings(expectedPaths)
|
||||
assertEqualStringSlices(t, expectedPaths, actualPaths, "testWalkMe")
|
||||
}
|
||||
|
||||
func TestRemove(t *testing.T) {
|
||||
n := makeLoadedFileTree(t)
|
||||
orgCount := n.FileCount()
|
||||
|
||||
// Remove the "ignore" directory and everything below it.
|
||||
path := filepath.Join("b", "d", "ignore")
|
||||
result, err := n.Find(path)
|
||||
if err != nil {
|
||||
t.Fatalf("%s; unexpected error %v", path, err)
|
||||
}
|
||||
if result == nil {
|
||||
t.Fatalf("%s; expected to find '%s'", path, path)
|
||||
}
|
||||
if !result.isNodeADir() {
|
||||
t.Fatalf("%s; expected to find a directory", path)
|
||||
}
|
||||
err = result.Remove()
|
||||
if err != nil {
|
||||
t.Fatalf("%s; unable to remove: %v", path, err)
|
||||
}
|
||||
result, err = n.Find(path)
|
||||
if err != nil {
|
||||
// Just because it's gone doesn't mean error.
|
||||
t.Fatalf("%s; unexpected error %v", path, err)
|
||||
}
|
||||
if result != nil {
|
||||
t.Fatalf("%s; should not have been able to find '%s'", path, path)
|
||||
}
|
||||
|
||||
// There were three files below "ignore".
|
||||
orgCount -= 3
|
||||
|
||||
// Now drop one more for a total of four dropped.
|
||||
result, _ = n.Find(filepath.Join("y"))
|
||||
err = result.Remove()
|
||||
if err != nil {
|
||||
t.Fatalf("%s; unable to remove: %v", path, err)
|
||||
}
|
||||
orgCount -= 1
|
||||
|
||||
fc := n.FileCount()
|
||||
if fc != orgCount {
|
||||
t.Fatalf("expected file count %d, got %d",
|
||||
orgCount, fc)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExists(t *testing.T) {
|
||||
n := makeLoadedFileTree(t)
|
||||
path := filepath.Join("b", "d", "a")
|
||||
if !n.Exists(path) {
|
||||
t.Fatalf("expected existence at %s", path)
|
||||
}
|
||||
if !n.IsDir(path) {
|
||||
t.Fatalf("expected directory at %s", path)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegExpGlob(t *testing.T) {
|
||||
n := makeLoadedFileTree(t)
|
||||
expected := []string{
|
||||
filepath.Join("b", "d", "a", "c", "i", "beans"),
|
||||
filepath.Join("b", "d", "a", "c", "m"),
|
||||
filepath.Join("b", "d", "a", "c", "u"),
|
||||
filepath.Join("b", "d", "ignore", "c", "m"),
|
||||
filepath.Join("b", "d", "ignore", "c", "n"),
|
||||
filepath.Join("b", "d", "ignore", "c", "o"),
|
||||
filepath.Join("b", "d", "x"),
|
||||
filepath.Join("b", "d", "y"),
|
||||
filepath.Join("b", "d", "z"),
|
||||
}
|
||||
paths, err := n.RegExpGlob("b/d/*")
|
||||
if err != nil {
|
||||
t.Fatalf("glob error: %v", err)
|
||||
}
|
||||
assertEqualStringSlices(t, expected, paths, "glob test")
|
||||
}
|
||||
|
||||
func TestGlob(t *testing.T) {
|
||||
n := makeLoadedFileTree(t)
|
||||
expected := []string{
|
||||
filepath.Join("b", "d", "x"),
|
||||
filepath.Join("b", "d", "y"),
|
||||
filepath.Join("b", "d", "z"),
|
||||
}
|
||||
paths, err := n.Glob("b/d/*")
|
||||
if err != nil {
|
||||
t.Fatalf("glob error: %v", err)
|
||||
}
|
||||
assertEqualStringSlices(t, expected, paths, "glob test")
|
||||
}
|
||||
|
||||
func assertEqualStringSlices(t *testing.T, expected, actual []string, message string) {
|
||||
if len(expected) != len(actual) {
|
||||
t.Fatalf(
|
||||
"%s; unequal sizes; len(expected)=%d, len(actual)=%d\n%+v\n%+v\n",
|
||||
message, len(expected), len(actual), expected, actual)
|
||||
}
|
||||
for i := range expected {
|
||||
if expected[i] != actual[i] {
|
||||
t.Fatalf(
|
||||
"%s; unequal entries; expected=%s, actual=%s",
|
||||
message, expected[i], actual[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFind(t *testing.T) {
|
||||
cases := []struct {
|
||||
what string
|
||||
arg string
|
||||
expectDir bool
|
||||
expectFile bool
|
||||
errStr string
|
||||
}{
|
||||
{
|
||||
what: "garbage",
|
||||
arg: "///1(*&SA",
|
||||
},
|
||||
{
|
||||
what: "simple",
|
||||
arg: "bob",
|
||||
},
|
||||
{
|
||||
what: "no directory",
|
||||
arg: filepath.Join("b", "rrrrrr"),
|
||||
},
|
||||
{
|
||||
what: "is a directory",
|
||||
arg: filepath.Join("b", "d", "ignore"),
|
||||
expectDir: true,
|
||||
},
|
||||
{
|
||||
what: "longer, ending in file",
|
||||
arg: filepath.Join("b", "d", "x"),
|
||||
expectFile: true,
|
||||
},
|
||||
{
|
||||
what: "moar longer, ending in file",
|
||||
arg: filepath.Join("b", "d", "a", "c", "u"),
|
||||
expectFile: true,
|
||||
},
|
||||
{
|
||||
what: "directory",
|
||||
arg: filepath.Join("b"),
|
||||
expectDir: true,
|
||||
},
|
||||
{
|
||||
// Querying for the empty string could
|
||||
// 1) be an error,
|
||||
// 2) return no result (and no error) as with
|
||||
// any illegal and therefore non-existent
|
||||
// file name,
|
||||
// 3) return the node itself, like running
|
||||
// 'ls' with no argument.
|
||||
// Going with option 2 (no result, no error),
|
||||
// since at this low level it makes more sense
|
||||
// if the results for the empty string query
|
||||
// differ from the results for the "." query.
|
||||
what: "empty name",
|
||||
arg: "",
|
||||
},
|
||||
{
|
||||
what: "self dir",
|
||||
arg: SelfDir,
|
||||
expectDir: true,
|
||||
},
|
||||
{
|
||||
what: "parent dir - doesn't exist",
|
||||
arg: ParentDir,
|
||||
},
|
||||
{
|
||||
what: "many parents - doesn't exist",
|
||||
arg: filepath.Join(ParentDir, ParentDir, ParentDir),
|
||||
},
|
||||
}
|
||||
|
||||
n := makeLoadedFileTree(t)
|
||||
for _, item := range cases {
|
||||
result, err := n.Find(item.arg)
|
||||
if item.errStr != "" {
|
||||
if err == nil {
|
||||
t.Fatalf("%s; expected error", item.what)
|
||||
}
|
||||
if !strings.Contains(err.Error(), item.errStr) {
|
||||
t.Fatalf("%s; expected err containing '%s', got '%v'",
|
||||
item.what, item.errStr, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("%s; unexpected error: %v", item.what, err)
|
||||
}
|
||||
if result == nil {
|
||||
if item.expectDir {
|
||||
t.Fatalf(
|
||||
"%s; expected to find directory '%s'", item.what, item.arg)
|
||||
}
|
||||
if item.expectFile {
|
||||
t.Fatalf(
|
||||
"%s; expected to find file '%s'", item.what, item.arg)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if item.expectDir {
|
||||
if !result.isNodeADir() {
|
||||
t.Fatalf(
|
||||
"%s; expected '%s' to be a directory", item.what, item.arg)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if item.expectFile {
|
||||
if result.isNodeADir() {
|
||||
t.Fatalf("%s; expected '%s' to be a file", item.what, item.arg)
|
||||
}
|
||||
continue
|
||||
}
|
||||
t.Fatalf(
|
||||
"%s; expected nothing for '%s', but got '%s'",
|
||||
item.what, item.arg, result.Path())
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanedAbs(t *testing.T) {
|
||||
cases := []struct {
|
||||
what string
|
||||
full string
|
||||
cDir string
|
||||
name string
|
||||
errStr string
|
||||
}{
|
||||
{
|
||||
what: "empty",
|
||||
full: "",
|
||||
errStr: "doesn't exist",
|
||||
},
|
||||
{
|
||||
what: "simple",
|
||||
full: "bob",
|
||||
errStr: "'bob' doesn't exist",
|
||||
},
|
||||
{
|
||||
what: "no directory",
|
||||
full: filepath.Join("b", "rrrrrr"),
|
||||
errStr: "'b/rrrrrr' doesn't exist",
|
||||
},
|
||||
{
|
||||
what: "longer, ending in file",
|
||||
full: filepath.Join("b", "d", "x"),
|
||||
cDir: filepath.Join("b", "d"),
|
||||
name: "x",
|
||||
},
|
||||
{
|
||||
what: "moar longer, ending in file",
|
||||
full: filepath.Join("b", "d", "a", "c", "u"),
|
||||
cDir: filepath.Join("b", "d", "a", "c"),
|
||||
name: "u",
|
||||
},
|
||||
{
|
||||
what: "directory",
|
||||
full: filepath.Join("b", "d"),
|
||||
cDir: filepath.Join("b", "d"),
|
||||
name: "",
|
||||
},
|
||||
}
|
||||
|
||||
n := makeLoadedFileTree(t)
|
||||
for _, item := range cases {
|
||||
cDir, name, err := n.CleanedAbs(item.full)
|
||||
if item.errStr != "" {
|
||||
if err == nil {
|
||||
t.Fatalf("%s; expected error", item.what)
|
||||
}
|
||||
if !strings.Contains(err.Error(), item.errStr) {
|
||||
t.Fatalf("%s; expected err containing '%s', got '%v'",
|
||||
item.what, item.errStr, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("%s; unexpected error: %v", item.what, err)
|
||||
}
|
||||
if cDir != ConfirmedDir(item.cDir) {
|
||||
t.Fatalf("%s; expected cDir=%s, got '%s'", item.what, item.cDir, cDir)
|
||||
}
|
||||
if name != item.name {
|
||||
t.Fatalf("%s; expected name=%s, got '%s'", item.what, item.name, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
114
api/filesys/fsondisk.go
Normal file
114
api/filesys/fsondisk.go
Normal file
@@ -0,0 +1,114 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package filesys
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
var _ FileSystem = fsOnDisk{}
|
||||
|
||||
// fsOnDisk implements FileSystem using the local filesystem.
|
||||
type fsOnDisk struct{}
|
||||
|
||||
// MakeFsOnDisk makes an instance of fsOnDisk.
|
||||
func MakeFsOnDisk() FileSystem {
|
||||
return fsOnDisk{}
|
||||
}
|
||||
|
||||
// Create delegates to os.Create.
|
||||
func (fsOnDisk) Create(name string) (File, error) { return os.Create(name) }
|
||||
|
||||
// Mkdir delegates to os.Mkdir.
|
||||
func (fsOnDisk) Mkdir(name string) error {
|
||||
return os.Mkdir(name, 0777|os.ModeDir)
|
||||
}
|
||||
|
||||
// MkdirAll delegates to os.MkdirAll.
|
||||
func (fsOnDisk) MkdirAll(name string) error {
|
||||
return os.MkdirAll(name, 0777|os.ModeDir)
|
||||
}
|
||||
|
||||
// RemoveAll delegates to os.RemoveAll.
|
||||
func (fsOnDisk) RemoveAll(name string) error {
|
||||
return os.RemoveAll(name)
|
||||
}
|
||||
|
||||
// Open delegates to os.Open.
|
||||
func (fsOnDisk) Open(name string) (File, error) { return os.Open(name) }
|
||||
|
||||
// CleanedAbs converts the given path into a
|
||||
// directory and a file name, where the directory
|
||||
// is represented as a ConfirmedDir and all that implies.
|
||||
// If the entire path is a directory, the file component
|
||||
// is an empty string.
|
||||
func (x fsOnDisk) CleanedAbs(
|
||||
path string) (ConfirmedDir, string, error) {
|
||||
absRoot, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf(
|
||||
"abs path error on '%s' : %v", path, err)
|
||||
}
|
||||
deLinked, err := filepath.EvalSymlinks(absRoot)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf(
|
||||
"evalsymlink failure on '%s' : %v", path, err)
|
||||
}
|
||||
if x.IsDir(deLinked) {
|
||||
return ConfirmedDir(deLinked), "", nil
|
||||
}
|
||||
d := filepath.Dir(deLinked)
|
||||
if !x.IsDir(d) {
|
||||
// Programmer/assumption error.
|
||||
log.Fatalf("first part of '%s' not a directory", deLinked)
|
||||
}
|
||||
if d == deLinked {
|
||||
// Programmer/assumption error.
|
||||
log.Fatalf("d '%s' should be a subset of deLinked", d)
|
||||
}
|
||||
f := filepath.Base(deLinked)
|
||||
if filepath.Join(d, f) != deLinked {
|
||||
// Programmer/assumption error.
|
||||
log.Fatalf("these should be equal: '%s', '%s'",
|
||||
filepath.Join(d, f), deLinked)
|
||||
}
|
||||
return ConfirmedDir(d), f, nil
|
||||
}
|
||||
|
||||
// Exists returns true if os.Stat succeeds.
|
||||
func (fsOnDisk) Exists(name string) bool {
|
||||
_, err := os.Stat(name)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// Glob returns the list of matching files
|
||||
func (fsOnDisk) Glob(pattern string) ([]string, error) {
|
||||
return filepath.Glob(pattern)
|
||||
}
|
||||
|
||||
// IsDir delegates to os.Stat and FileInfo.IsDir
|
||||
func (fsOnDisk) IsDir(name string) bool {
|
||||
info, err := os.Stat(name)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return info.IsDir()
|
||||
}
|
||||
|
||||
// ReadFile delegates to ioutil.ReadFile.
|
||||
func (fsOnDisk) ReadFile(name string) ([]byte, error) { return ioutil.ReadFile(name) }
|
||||
|
||||
// WriteFile delegates to ioutil.WriteFile with read/write permissions.
|
||||
func (fsOnDisk) WriteFile(name string, c []byte) error {
|
||||
return ioutil.WriteFile(name, c, 0666)
|
||||
}
|
||||
|
||||
// Walk delegates to filepath.Walk.
|
||||
func (fsOnDisk) Walk(path string, walkFn filepath.WalkFunc) error {
|
||||
return filepath.Walk(path, walkFn)
|
||||
}
|
||||
165
api/filesys/fsondisk_test.go
Normal file
165
api/filesys/fsondisk_test.go
Normal file
@@ -0,0 +1,165 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package filesys_test
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
. "sigs.k8s.io/kustomize/api/filesys"
|
||||
)
|
||||
|
||||
func makeTestDir(t *testing.T) (FileSystem, string) {
|
||||
fSys := MakeFsOnDisk()
|
||||
td, err := ioutil.TempDir("", "kustomize_testing_dir")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %s", err)
|
||||
}
|
||||
testDir, err := filepath.EvalSymlinks(td)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %s", err)
|
||||
}
|
||||
if !fSys.Exists(testDir) {
|
||||
t.Fatalf("expected existence")
|
||||
}
|
||||
if !fSys.IsDir(testDir) {
|
||||
t.Fatalf("expected directory")
|
||||
}
|
||||
return fSys, testDir
|
||||
}
|
||||
|
||||
func TestCleanedAbs_1(t *testing.T) {
|
||||
fSys, testDir := makeTestDir(t)
|
||||
defer os.RemoveAll(testDir)
|
||||
|
||||
d, f, err := fSys.CleanedAbs("")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err=%v", err)
|
||||
}
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err=%v", err)
|
||||
}
|
||||
if d.String() != wd {
|
||||
t.Fatalf("unexpected d=%s", d)
|
||||
}
|
||||
if f != "" {
|
||||
t.Fatalf("unexpected f=%s", f)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanedAbs_2(t *testing.T) {
|
||||
fSys, testDir := makeTestDir(t)
|
||||
defer os.RemoveAll(testDir)
|
||||
|
||||
d, f, err := fSys.CleanedAbs("/")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err=%v", err)
|
||||
}
|
||||
if d != "/" {
|
||||
t.Fatalf("unexpected d=%s", d)
|
||||
}
|
||||
if f != "" {
|
||||
t.Fatalf("unexpected f=%s", f)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanedAbs_3(t *testing.T) {
|
||||
fSys, testDir := makeTestDir(t)
|
||||
defer os.RemoveAll(testDir)
|
||||
|
||||
err := fSys.WriteFile(
|
||||
filepath.Join(testDir, "foo"), []byte(`foo`))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err=%v", err)
|
||||
}
|
||||
|
||||
d, f, err := fSys.CleanedAbs(filepath.Join(testDir, "foo"))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err=%v", err)
|
||||
}
|
||||
if d.String() != testDir {
|
||||
t.Fatalf("unexpected d=%s", d)
|
||||
}
|
||||
if f != "foo" {
|
||||
t.Fatalf("unexpected f=%s", f)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanedAbs_4(t *testing.T) {
|
||||
fSys, testDir := makeTestDir(t)
|
||||
defer os.RemoveAll(testDir)
|
||||
|
||||
err := fSys.MkdirAll(filepath.Join(testDir, "d1", "d2"))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err=%v", err)
|
||||
}
|
||||
err = fSys.WriteFile(
|
||||
filepath.Join(testDir, "d1", "d2", "bar"),
|
||||
[]byte(`bar`))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err=%v", err)
|
||||
}
|
||||
|
||||
d, f, err := fSys.CleanedAbs(
|
||||
filepath.Join(testDir, "d1", "d2"))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err=%v", err)
|
||||
}
|
||||
if d.String() != filepath.Join(testDir, "d1", "d2") {
|
||||
t.Fatalf("unexpected d=%s", d)
|
||||
}
|
||||
if f != "" {
|
||||
t.Fatalf("unexpected f=%s", f)
|
||||
}
|
||||
|
||||
d, f, err = fSys.CleanedAbs(
|
||||
filepath.Join(testDir, "d1", "d2", "bar"))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err=%v", err)
|
||||
}
|
||||
if d.String() != filepath.Join(testDir, "d1", "d2") {
|
||||
t.Fatalf("unexpected d=%s", d)
|
||||
}
|
||||
if f != "bar" {
|
||||
t.Fatalf("unexpected f=%s", f)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadFilesRealFS(t *testing.T) {
|
||||
fSys, testDir := makeTestDir(t)
|
||||
defer os.RemoveAll(testDir)
|
||||
|
||||
err := fSys.WriteFile(path.Join(testDir, "foo"), []byte(`foo`))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %s", err)
|
||||
}
|
||||
if !fSys.Exists(path.Join(testDir, "foo")) {
|
||||
t.Fatalf("expected foo")
|
||||
}
|
||||
if fSys.IsDir(path.Join(testDir, "foo")) {
|
||||
t.Fatalf("expected foo not to be a directory")
|
||||
}
|
||||
|
||||
err = fSys.WriteFile(path.Join(testDir, "bar"), []byte(`bar`))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %s", err)
|
||||
}
|
||||
|
||||
files, err := fSys.Glob(path.Join("testDir", "*"))
|
||||
expected := []string{
|
||||
path.Join(testDir, "bar"),
|
||||
path.Join(testDir, "foo"),
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error")
|
||||
}
|
||||
if reflect.DeepEqual(files, expected) {
|
||||
t.Fatalf("incorrect files found by glob: %v", files)
|
||||
}
|
||||
}
|
||||
30
api/filesys/util.go
Normal file
30
api/filesys/util.go
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package filesys
|
||||
|
||||
import "path/filepath"
|
||||
|
||||
// RootedPath returns a rooted path, e.g. "/foo/bar" as
|
||||
// opposed to "foo/bar".
|
||||
func RootedPath(elem ...string) string {
|
||||
return Separator + filepath.Join(elem...)
|
||||
}
|
||||
|
||||
// StripTrailingSeps trims trailing filepath separators from input.
|
||||
func StripTrailingSeps(s string) string {
|
||||
k := len(s)
|
||||
for k > 0 && s[k-1] == filepath.Separator {
|
||||
k--
|
||||
}
|
||||
return s[:k]
|
||||
}
|
||||
|
||||
// StripLeadingSeps trims leading filepath separators from input.
|
||||
func StripLeadingSeps(s string) string {
|
||||
k := 0
|
||||
for k < len(s) && s[k] == filepath.Separator {
|
||||
k++
|
||||
}
|
||||
return s[k:]
|
||||
}
|
||||
205
api/filesys/util_test.go
Normal file
205
api/filesys/util_test.go
Normal file
@@ -0,0 +1,205 @@
|
||||
package filesys_test
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
. "sigs.k8s.io/kustomize/api/filesys"
|
||||
)
|
||||
|
||||
// Confirm behavior of filepath.Match
|
||||
func TestFilePathMatch(t *testing.T) {
|
||||
cases := []struct {
|
||||
pattern string
|
||||
path string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
pattern: "*e*",
|
||||
path: "hey",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
pattern: "*e*",
|
||||
path: "hay",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
pattern: "*e*",
|
||||
path: filepath.Join("h", "e", "y"),
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
pattern: "*/e/*",
|
||||
path: filepath.Join("h", "e", "y"),
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
pattern: "h/e/*",
|
||||
path: filepath.Join("h", "e", "y"),
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
pattern: "*/e/y",
|
||||
path: filepath.Join("h", "e", "y"),
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
pattern: "*/*/*",
|
||||
path: filepath.Join("h", "e", "y"),
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
pattern: "*/*/*",
|
||||
path: filepath.Join("h", "e", "y", "there"),
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
pattern: "*/*/*/t*e",
|
||||
path: filepath.Join("h", "e", "y", "there"),
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
for _, item := range cases {
|
||||
match, err := filepath.Match(item.pattern, item.path)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
if match != item.expected {
|
||||
t.Fatalf("'%s' '%s' %v\n", item.pattern, item.path, match)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Confirm behavior of filepath.Split
|
||||
func TestFilePathSplit(t *testing.T) {
|
||||
cases := []struct {
|
||||
full string
|
||||
dir string
|
||||
file string
|
||||
}{
|
||||
{
|
||||
full: "",
|
||||
dir: "",
|
||||
file: "",
|
||||
},
|
||||
{
|
||||
full: SelfDir,
|
||||
dir: "",
|
||||
file: SelfDir,
|
||||
},
|
||||
{
|
||||
full: "rabbit.jpg",
|
||||
dir: "",
|
||||
file: "rabbit.jpg",
|
||||
},
|
||||
{
|
||||
full: "/beans",
|
||||
dir: "/",
|
||||
file: "beans",
|
||||
},
|
||||
{
|
||||
full: "/home/foo/bar",
|
||||
dir: "/home/foo/",
|
||||
file: "bar",
|
||||
},
|
||||
{
|
||||
full: "/usr/local/",
|
||||
dir: "/usr/local/",
|
||||
file: "",
|
||||
},
|
||||
{
|
||||
full: "/usr//local//go",
|
||||
dir: "/usr//local//",
|
||||
file: "go",
|
||||
},
|
||||
}
|
||||
for _, p := range cases {
|
||||
dir, file := filepath.Split(p.full)
|
||||
if dir != p.dir || file != p.file {
|
||||
t.Fatalf(
|
||||
"in '%s',\ngot dir='%s' (expected '%s'),\n got file='%s' (expected %s).",
|
||||
p.full, dir, p.dir, file, p.file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStripTrailingSeps(t *testing.T) {
|
||||
cases := []struct {
|
||||
full string
|
||||
rem string
|
||||
}{
|
||||
{
|
||||
full: "foo",
|
||||
rem: "foo",
|
||||
},
|
||||
{
|
||||
full: "",
|
||||
rem: "",
|
||||
},
|
||||
{
|
||||
full: "foo/",
|
||||
rem: "foo",
|
||||
},
|
||||
{
|
||||
full: "foo///bar///",
|
||||
rem: "foo///bar",
|
||||
},
|
||||
{
|
||||
full: "/////",
|
||||
rem: "",
|
||||
},
|
||||
{
|
||||
full: "/",
|
||||
rem: "",
|
||||
},
|
||||
}
|
||||
for _, p := range cases {
|
||||
dir := StripTrailingSeps(p.full)
|
||||
if dir != p.rem {
|
||||
t.Fatalf(
|
||||
"in '%s', got dir='%s' (expected '%s')",
|
||||
p.full, dir, p.rem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStripLeadingSeps(t *testing.T) {
|
||||
cases := []struct {
|
||||
full string
|
||||
rem string
|
||||
}{
|
||||
{
|
||||
full: "foo",
|
||||
rem: "foo",
|
||||
},
|
||||
{
|
||||
full: "",
|
||||
rem: "",
|
||||
},
|
||||
{
|
||||
full: "/foo",
|
||||
rem: "foo",
|
||||
},
|
||||
{
|
||||
full: "///foo///bar///",
|
||||
rem: "foo///bar///",
|
||||
},
|
||||
{
|
||||
full: "/////",
|
||||
rem: "",
|
||||
},
|
||||
{
|
||||
full: "/",
|
||||
rem: "",
|
||||
},
|
||||
}
|
||||
for _, p := range cases {
|
||||
dir := StripLeadingSeps(p.full)
|
||||
if dir != p.rem {
|
||||
t.Fatalf(
|
||||
"in '%s', got dir='%s' (expected '%s')",
|
||||
p.full, dir, p.rem)
|
||||
}
|
||||
}
|
||||
}
|
||||
19
api/go.mod
Normal file
19
api/go.mod
Normal file
@@ -0,0 +1,19 @@
|
||||
module sigs.k8s.io/kustomize/api
|
||||
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/evanphx/json-patch v4.5.0+incompatible
|
||||
github.com/go-openapi/spec v0.19.4
|
||||
github.com/golangci/golangci-lint v1.21.0
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||
github.com/monopole/mdrip v1.0.1
|
||||
github.com/pkg/errors v0.8.1
|
||||
golang.org/x/tools v0.0.0-20191010075000-0337d82405ff
|
||||
gopkg.in/yaml.v2 v2.2.4
|
||||
k8s.io/api v0.17.0
|
||||
k8s.io/apimachinery v0.17.0
|
||||
k8s.io/client-go v0.17.0
|
||||
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a
|
||||
sigs.k8s.io/yaml v1.1.0
|
||||
)
|
||||
537
api/go.sum
Normal file
537
api/go.sum
Normal file
@@ -0,0 +1,537 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
|
||||
github.com/Azure/go-autorest/autorest v0.9.2/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc=
|
||||
github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
|
||||
github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM=
|
||||
github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
|
||||
github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/OpenPeeDeeP/depguard v1.0.1 h1:VlW4R6jmBIv3/u1JNlawEvJMM4J+dPORPaZasQee8Us=
|
||||
github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM=
|
||||
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/bombsimon/wsl v1.2.5 h1:9gTOkIwVtoDZywvX802SDHokeX4kW1cKnV8ZTVAPkRs=
|
||||
github.com/bombsimon/wsl v1.2.5/go.mod h1:43lEF/i0kpXbLCeDXL9LMT8c92HyBywXb0AsgMHYngM=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
|
||||
github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
|
||||
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||
github.com/elazarl/goproxy v0.0.0-20191011121108-aa519ddbe484/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
|
||||
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
|
||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633 h1:H2pdYOb3KQ1/YsqVWoWNLQO+fusocsw354rqGTZtAgw=
|
||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
github.com/emicklei/go-restful v2.9.6+incompatible h1:tfrHha8zJ01ywiOEC1miGY8st1/igzWB8OmvPgoYX7w=
|
||||
github.com/emicklei/go-restful v2.9.6+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M=
|
||||
github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-critic/go-critic v0.3.5-0.20190904082202-d79a9f0c64db h1:GYXWx7Vr3+zv833u+8IoXbNnQY0AdXsxAgI0kX7xcwA=
|
||||
github.com/go-critic/go-critic v0.3.5-0.20190904082202-d79a9f0c64db/go.mod h1:+sE8vrLDS2M0pZkBk0wy6+nLdKexVDrl/jBqQOTDThA=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-lintpack/lintpack v0.5.2 h1:DI5mA3+eKdWeJ40nU4d6Wc26qmdG8RCi/btYq0TuRN0=
|
||||
github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
|
||||
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
|
||||
github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
|
||||
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
|
||||
github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
|
||||
github.com/go-openapi/jsonreference v0.19.2 h1:o20suLFB4Ri0tuzpWtyHlh7E7HnkqTNLq6aR6WVNS1w=
|
||||
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
|
||||
github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
|
||||
github.com/go-openapi/spec v0.19.4 h1:ixzUSnHTd6hCemgtAJgluaTSGYpLNpJY4mA2DIkdOAo=
|
||||
github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
|
||||
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
|
||||
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-toolsmith/astcast v1.0.0 h1:JojxlmI6STnFVG9yOImLeGREv8W2ocNUM+iOhR6jE7g=
|
||||
github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4=
|
||||
github.com/go-toolsmith/astcopy v1.0.0 h1:OMgl1b1MEpjFQ1m5ztEO06rz5CUd3oBv9RF7+DyvdG8=
|
||||
github.com/go-toolsmith/astcopy v1.0.0/go.mod h1:vrgyG+5Bxrnz4MZWPF+pI4R8h3qKRjjyvV/DSez4WVQ=
|
||||
github.com/go-toolsmith/astequal v0.0.0-20180903214952-dcb477bfacd6/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY=
|
||||
github.com/go-toolsmith/astequal v1.0.0 h1:4zxD8j3JRFNyLN46lodQuqz3xdKSrur7U/sr0SDS/gQ=
|
||||
github.com/go-toolsmith/astequal v1.0.0/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY=
|
||||
github.com/go-toolsmith/astfmt v0.0.0-20180903215011-8f8ee99c3086/go.mod h1:mP93XdblcopXwlyN4X4uodxXQhldPGZbcEJIimQHrkg=
|
||||
github.com/go-toolsmith/astfmt v1.0.0 h1:A0vDDXt+vsvLEdbMFJAUBI/uTbRw1ffOPnxsILnFL6k=
|
||||
github.com/go-toolsmith/astfmt v1.0.0/go.mod h1:cnWmsOAuq4jJY6Ct5YWlVLmcmLMn1JUPuQIHCY7CJDw=
|
||||
github.com/go-toolsmith/astinfo v0.0.0-20180906194353-9809ff7efb21/go.mod h1:dDStQCHtmZpYOmjRP/8gHHnCCch3Zz3oEgCdZVdtweU=
|
||||
github.com/go-toolsmith/astp v0.0.0-20180903215135-0af7e3c24f30/go.mod h1:SV2ur98SGypH1UjcPpCatrV5hPazG6+IfNHbkDXBRrk=
|
||||
github.com/go-toolsmith/astp v1.0.0 h1:alXE75TXgcmupDsMK1fRAy0YUzLzqPVvBKoyWV+KPXg=
|
||||
github.com/go-toolsmith/astp v1.0.0/go.mod h1:RSyrtpVlfTFGDYRbrjyWP1pYu//tSFcvdYrA8meBmLI=
|
||||
github.com/go-toolsmith/pkgload v0.0.0-20181119091011-e9e65178eee8/go.mod h1:WoMrjiy4zvdS+Bg6z9jZH82QXwkcgCBX6nOfnmdaHks=
|
||||
github.com/go-toolsmith/pkgload v1.0.0 h1:4DFWWMXVfbcN5So1sBNW9+yeiMqLFGl1wFLTL5R0Tgg=
|
||||
github.com/go-toolsmith/pkgload v1.0.0/go.mod h1:5eFArkbO80v7Z0kdngIxsRXRMTaX4Ilcwuh3clNrQJc=
|
||||
github.com/go-toolsmith/strparse v1.0.0 h1:Vcw78DnpCAKlM20kSbAyO4mPfJn/lyYA4BJUDxe2Jb4=
|
||||
github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8=
|
||||
github.com/go-toolsmith/typep v1.0.0 h1:zKymWyA1TRYvqYrYDrfEMZULyrhcnGY3x7LDKU2XQaA=
|
||||
github.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU=
|
||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/gofrs/flock v0.0.0-20190320160742-5135e617513b h1:ekuhfTjngPhisSjOJ0QWKpPQE8/rbknHaes6WVJj5Hw=
|
||||
github.com/gofrs/flock v0.0.0-20190320160742-5135e617513b/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I=
|
||||
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
|
||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 h1:23T5iq8rbUYlhpt5DB4XJkc6BU31uODLD1o1gKvZmD0=
|
||||
github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4=
|
||||
github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9uMCefW1WDie15eSP/4MssdenaM=
|
||||
github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk=
|
||||
github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6 h1:YYWNAGTKWhKpcLLt7aSj/odlKrSrelQwlovBpDuf19w=
|
||||
github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6/go.mod h1:DbHgvLiFKX1Sh2T1w8Q/h4NAI8MHIpzCdnBUDTXU3I0=
|
||||
github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613 h1:9kfjN3AdxcbsZBf8NjltjWihK2QfBBBZuv91cMFfDHw=
|
||||
github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613/go.mod h1:SyvUF2NxV+sN8upjjeVYr5W7tyxaT1JVtvhKhOn2ii8=
|
||||
github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3 h1:pe9JHs3cHHDQgOFXJJdYkK6fLz2PWyYtP4hthoCMvs8=
|
||||
github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3/go.mod h1:JXrF4TWy4tXYn62/9x8Wm/K/dm06p8tCKwFRDPZG/1o=
|
||||
github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee h1:J2XAy40+7yz70uaOiMbNnluTg7gyQhtGqLQncQh+4J8=
|
||||
github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU=
|
||||
github.com/golangci/gofmt v0.0.0-20181222123516-0b8337e80d98/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU=
|
||||
github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a h1:iR3fYXUjHCR97qWS8ch1y9zPNsgXThGwjKPrYfqMPks=
|
||||
github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU=
|
||||
github.com/golangci/golangci-lint v1.19.1/go.mod h1:2CEc4Fxx3vxDv7g8DyXkHCBF73AOzAymcJAprs2vCps=
|
||||
github.com/golangci/golangci-lint v1.21.0 h1:HxAxpR8Z0M8omihvQdsD3PF0qPjlqYqp2vMJzstoKeI=
|
||||
github.com/golangci/golangci-lint v1.21.0/go.mod h1:phxpHK52q7SE+5KpPnti4oZTdFCEsn/tKN+nFvCKXfk=
|
||||
github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc h1:gLLhTLMk2/SutryVJ6D4VZCU3CUqr8YloG7FPIBWFpI=
|
||||
github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc/go.mod h1:e5tpTHCfVze+7EpLEozzMB3eafxo2KT5veNg1k6byQU=
|
||||
github.com/golangci/lint-1 v0.0.0-20190420132249-ee948d087217/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg=
|
||||
github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 h1:MfyDlzVjl1hoaPzPD4Gpb/QgoRfSBR0jdhwGyAWwMSA=
|
||||
github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg=
|
||||
github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca h1:kNY3/svz5T29MYHubXix4aDDuE3RWHkPvopM/EDv/MA=
|
||||
github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o=
|
||||
github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770 h1:EL/O5HGrF7Jaq0yNhBLucz9hTuRzj2LdwGBOaENgxIk=
|
||||
github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA=
|
||||
github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21 h1:leSNB7iYzLYSSx3J/s5sVf4Drkc68W2wm4Ixh/mr0us=
|
||||
github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21/go.mod h1:tf5+bzsHdTM0bsB7+8mt0GUMvjCgwLpTapNZHU8AajI=
|
||||
github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0 h1:HVfrLniijszjS1aiNg8JbBMO2+E1WIQ+j/gL4SQqGPg=
|
||||
github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4=
|
||||
github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 h1:zwtduBRr5SSWhqsYNgcuWO2kFlpdOZbP0+yRjmvPGys=
|
||||
github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
|
||||
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d h1:7XGaL1e6bYS1yIonGp9761ExpPPV1ui0SAC59Yube9k=
|
||||
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||
github.com/googleapis/gnostic v0.3.0 h1:CcQijm0XKekKjP/YCz28LXVSpgguuB+nCxaSjCe09y0=
|
||||
github.com/googleapis/gnostic v0.3.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
|
||||
github.com/gophercloud/gophercloud v0.6.0/go.mod h1:GICNByuaEBibcjmjvI7QvYJSZEbGkcYwAR7EZK2WMqM=
|
||||
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f h1:9oNbS1z4rVpbnkHBdPZU4jo9bSmrLpII768arSyMFgk=
|
||||
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/mux v1.6.0 h1:UykbtMB/w5No2LmE16gINgLj+r/vbziTgaoERQv6U+0=
|
||||
github.com/gorilla/mux v1.6.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/securecookie v0.0.0-20160422134519-667fe4e3466a h1:YH0IojQwndMQdeRWdw1aPT8bkbiWaYR3WD+Zf5e09DU=
|
||||
github.com/gorilla/securecookie v0.0.0-20160422134519-667fe4e3466a/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/gorilla/sessions v0.0.0-20160922145804-ca9ada445741 h1:OuuPl66BpF1q3OEkaPpp+VfzxrBBY62ATGdWqql/XX8=
|
||||
github.com/gorilla/sessions v0.0.0-20160922145804-ca9ada445741/go.mod h1:+WVp8kdw6VhyKExm03PAMRn2ZxnPtm58pV0dBVPdhHE=
|
||||
github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3 h1:JVnpOZS+qxli+rgVl98ILOXVNbW+kb5wcxeGx8ShUIw=
|
||||
github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok=
|
||||
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||
github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
|
||||
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/matoous/godox v0.0.0-20190910121045-032ad8106c86/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s=
|
||||
github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb h1:RHba4YImhrUVQDHUCe2BNSOz4tVy2yGyXhvYDvxGgeE=
|
||||
github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-ps v0.0.0-20190716172923-621e5597135b/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk=
|
||||
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/monopole/mdrip v1.0.0 h1:RFDBa+tab6mW+gX4Ww2SZDc4kS6p01FwnLtgz64Il+I=
|
||||
github.com/monopole/mdrip v1.0.0/go.mod h1:N1/ppRG9CaPeUKAUHZ3dUlfOT81lTpKZLkyhCvTETwM=
|
||||
github.com/monopole/mdrip v1.0.1 h1:LzwNWb8ge3+4iBZmxIE6VfUR5KIxhF7DKdf85t8Yvos=
|
||||
github.com/monopole/mdrip v1.0.1/go.mod h1:/7E04hlzRG9Jrp6WILZfYYm/REoJWL2l+MlsCO1eH74=
|
||||
github.com/mozilla/tls-observatory v0.0.0-20190404164649-a3c1b6cfecfd/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk=
|
||||
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||
github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d h1:AREM5mwr4u1ORQBMvzfzBgpsctsbQikCVpvC+tX285E=
|
||||
github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo=
|
||||
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME=
|
||||
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/russross/blackfriday v2.0.0+incompatible h1:cBXrhZNUf9C+La9/YpS+UHpUT8YD6Td9ZMSU9APFcsk=
|
||||
github.com/russross/blackfriday v2.0.0+incompatible/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/securego/gosec v0.0.0-20190912120752-140048b2a218/go.mod h1:q6oYAujd2qyeU4cJqIri4LBIgdHXGvxWHZ1E29HNFRE=
|
||||
github.com/securego/gosec v0.0.0-20191002120514-e680875ea14d h1:BzRvVq1EHuIjxpijCEKpAxzKUUMurOQ4sknehIATRh8=
|
||||
github.com/securego/gosec v0.0.0-20191002120514-e680875ea14d/go.mod h1:w5+eXa0mYznDkHaMCXA4XYffjlH+cy1oyKbfzJXa2Do=
|
||||
github.com/shirou/gopsutil v0.0.0-20190901111213-e4ec7b275ada/go.mod h1:WWnYX4lzhCH5h/3YBfyVA3VbLYjlMZZAQcW9ojMexNc=
|
||||
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
|
||||
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e h1:MZM7FHLqUHYI0Y/mQAt3d2aYa0SiNms/hFqC9qJYolM=
|
||||
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
|
||||
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041 h1:llrF3Fs4018ePo4+G/HV/uQUqEI1HMDjCeOf2V6puPc=
|
||||
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/sourcegraph/go-diff v0.5.1 h1:gO6i5zugwzo1RVTvgvfwCOSVegNuvnNi6bAD1QCmkHs=
|
||||
github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
|
||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU=
|
||||
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/timakin/bodyclose v0.0.0-20190721030226-87058b9bfcec/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk=
|
||||
github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e h1:RumXZ56IrCj4CL+g1b9OL/oH0QnsF976bC8xQFYUD5Q=
|
||||
github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/ultraware/funlen v0.0.2 h1:Av96YVBwwNSe4MLR7iI/BIa3VyI7/djnto/pK3Uxbdo=
|
||||
github.com/ultraware/funlen v0.0.2/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA=
|
||||
github.com/ultraware/whitespace v0.0.3/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA=
|
||||
github.com/ultraware/whitespace v0.0.4 h1:If7Va4cM03mpgrNH9k49/VOicWpGoG70XPBFFODYDsg=
|
||||
github.com/ultraware/whitespace v0.0.4/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA=
|
||||
github.com/uudashr/gocognit v0.0.0-20190926065955-1655d0de0517 h1:ChMKTho2hWKpks/nD/FL2KqM1wuVt62oJeiE8+eFpGs=
|
||||
github.com/uudashr/gocognit v0.0.0-20190926065955-1655d0de0517/go.mod h1:j44Ayx2KW4+oB6SWMv8KsmHzZrOInQav7D3cQMJ5JUM=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.2.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s=
|
||||
github.com/valyala/quicktemplate v1.2.0/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4=
|
||||
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190909003024-a7b16738d86b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI=
|
||||
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190911201528-7ad0cfa0b7b5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69 h1:rOhMmluY6kLMhdnrivzec6lLgaVbMHMn2ISQXJeJ5EM=
|
||||
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181117154741-2ddaf7f79a09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190311215038-5c2858a9cfe5/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190521203540-521d6ed310dd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190719005602-e377ae9d6386/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
|
||||
golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911230505-6bfd74cf029c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190912215617-3720d1ec3678/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190930201159-7c411dea38b0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191010075000-0337d82405ff h1:XdBG6es/oFDr1HwaxkxgVve7NB281QhxgK/i4voubFs=
|
||||
golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/russross/blackfriday.v2 v2.0.0 h1:+FlnIV8DSQnT7NZ43hcVKcdJdzZoeCmJj4Ql8gq5keA=
|
||||
gopkg.in/russross/blackfriday.v2 v2.0.0/go.mod h1:6sSBNz/GtOm/pJTuh5UmBK2ZHfmnxGbl2NZg1UliSOI=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
k8s.io/api v0.17.0 h1:H9d/lw+VkZKEVIUc8F3wgiQ+FUXTTr21M87jXLU7yqM=
|
||||
k8s.io/api v0.17.0/go.mod h1:npsyOePkeP0CPwyGfXDHxvypiYMJxBWAMpQxCaJ4ZxI=
|
||||
k8s.io/apimachinery v0.17.0 h1:xRBnuie9rXcPxUkDizUsGvPf1cnlZCFu210op7J7LJo=
|
||||
k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
|
||||
k8s.io/client-go v0.17.0 h1:8QOGvUGdqDMFrm9sD6IUFl256BcffynGoe80sxgTEDg=
|
||||
k8s.io/client-go v0.17.0/go.mod h1:TYgR6EUHs6k45hb6KWjVD6jFZvJV4gHDikv/It0xz+k=
|
||||
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
||||
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||
k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||
k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
|
||||
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
|
||||
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a h1:UcxjrRMyNx/i/y8G7kPvLyy7rfbeuf1PYyBf973pgyU=
|
||||
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
|
||||
k8s.io/utils v0.0.0-20191030222137-2b95a09bc58d/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
|
||||
k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
|
||||
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed h1:WX1yoOaKQfddO/mLzdV4wptyWgoH/6hwLs7QHTixo0I=
|
||||
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc=
|
||||
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b h1:DxJ5nJdkhDlLok9K6qO+5290kphDJbHOQO1DFFFTeBo=
|
||||
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4=
|
||||
mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f h1:Cq7MalBHYACRd6EesksG1Q8EoIAKOsiZviGKbOLIej4=
|
||||
mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZIVpwbkw+04kSxk3rAtzlimaUJw=
|
||||
sigs.k8s.io/kustomize/api v0.2.0/go.mod h1:zVtMg179jW1gr74jo9fc2Ac9dLYLTZZThc3DDb9lDW4=
|
||||
sigs.k8s.io/kustomize/pluginator/v2 v2.0.0 h1:sES7e09G19Q0VjRp4ATSYKpTXoWaX8WMSHfw6u3G2Ok=
|
||||
sigs.k8s.io/kustomize/pluginator/v2 v2.0.0/go.mod h1:zrXhTv8BAKt0egmZX/8AtMOSFUSWM9YuoHvvqz8/eHE=
|
||||
sigs.k8s.io/kustomize/pseudo/k8s v0.1.0/go.mod h1:bl/gVJgYYhJZCZdYU2BfnaKYAlqFkgbJEkpl302jEss=
|
||||
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
|
||||
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4 h1:JPJh2pk3+X4lXAkZIk2RuE/7/FoK9maXw+TNPJhVS/c=
|
||||
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
|
||||
52
api/hasher/hasher.go
Normal file
52
api/hasher/hasher.go
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package hasher
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// SortArrayAndComputeHash sorts a string array and
|
||||
// returns a hash for it
|
||||
func SortArrayAndComputeHash(s []string) (string, error) {
|
||||
sort.Strings(s)
|
||||
data, err := json.Marshal(s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return Encode(Hash(string(data)))
|
||||
}
|
||||
|
||||
// Copied from https://github.com/kubernetes/kubernetes
|
||||
// /blob/master/pkg/kubectl/util/hash/hash.go
|
||||
func Encode(hex string) (string, error) {
|
||||
if len(hex) < 10 {
|
||||
return "", fmt.Errorf(
|
||||
"input length must be at least 10")
|
||||
}
|
||||
enc := []rune(hex[:10])
|
||||
for i := range enc {
|
||||
switch enc[i] {
|
||||
case '0':
|
||||
enc[i] = 'g'
|
||||
case '1':
|
||||
enc[i] = 'h'
|
||||
case '3':
|
||||
enc[i] = 'k'
|
||||
case 'a':
|
||||
enc[i] = 'm'
|
||||
case 'e':
|
||||
enc[i] = 't'
|
||||
}
|
||||
}
|
||||
return string(enc), nil
|
||||
}
|
||||
|
||||
// Hash returns the hex form of the sha256 of the argument.
|
||||
func Hash(data string) string {
|
||||
return fmt.Sprintf("%x", sha256.Sum256([]byte(data)))
|
||||
}
|
||||
41
api/hasher/hasher_test.go
Normal file
41
api/hasher/hasher_test.go
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package hasher_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "sigs.k8s.io/kustomize/api/hasher"
|
||||
)
|
||||
|
||||
func TestSortArrayAndComputeHash(t *testing.T) {
|
||||
array1 := []string{"a", "b", "c", "d"}
|
||||
array2 := []string{"c", "b", "d", "a"}
|
||||
h1, err := SortArrayAndComputeHash(array1)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error %v", err)
|
||||
}
|
||||
if h1 == "" {
|
||||
t.Errorf("failed to hash %v", array1)
|
||||
}
|
||||
h2, err := SortArrayAndComputeHash(array2)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error %v", err)
|
||||
}
|
||||
if h2 == "" {
|
||||
t.Errorf("failed to hash %v", array2)
|
||||
}
|
||||
if h1 != h2 {
|
||||
t.Errorf("hash is not consistent with reordered list: %s %s", h1, h2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHash(t *testing.T) {
|
||||
// hash the empty string to be sure that sha256 is being used
|
||||
expect := "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
sum := Hash("")
|
||||
if expect != sum {
|
||||
t.Errorf("expected hash %q but got %q", expect, sum)
|
||||
}
|
||||
}
|
||||
95
api/ifc/ifc.go
Normal file
95
api/ifc/ifc.go
Normal file
@@ -0,0 +1,95 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package ifc holds miscellaneous interfaces used by kustomize.
|
||||
package ifc
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
)
|
||||
|
||||
// Validator provides functions to validate annotations and labels
|
||||
type Validator interface {
|
||||
MakeAnnotationValidator() func(map[string]string) error
|
||||
MakeAnnotationNameValidator() func([]string) error
|
||||
MakeLabelValidator() func(map[string]string) error
|
||||
MakeLabelNameValidator() func([]string) error
|
||||
ValidateNamespace(string) []string
|
||||
ErrIfInvalidKey(string) error
|
||||
IsEnvVarName(k string) error
|
||||
}
|
||||
|
||||
// KvLoader reads and validates KV pairs.
|
||||
type KvLoader interface {
|
||||
Validator() Validator
|
||||
Load(args types.KvPairSources) (all []types.Pair, err error)
|
||||
}
|
||||
|
||||
// Loader interface exposes methods to read bytes.
|
||||
type Loader interface {
|
||||
// Root returns the root location for this Loader.
|
||||
Root() string
|
||||
// New returns Loader located at newRoot.
|
||||
New(newRoot string) (Loader, error)
|
||||
// Load returns the bytes read from the location or an error.
|
||||
Load(location string) ([]byte, error)
|
||||
// Cleanup cleans the loader
|
||||
Cleanup() error
|
||||
}
|
||||
|
||||
// Kunstructured allows manipulation of k8s objects
|
||||
// that do not have Golang structs.
|
||||
type Kunstructured interface {
|
||||
Map() map[string]interface{}
|
||||
SetMap(map[string]interface{})
|
||||
Copy() Kunstructured
|
||||
GetFieldValue(string) (interface{}, error)
|
||||
GetString(string) (string, error)
|
||||
GetStringSlice(string) ([]string, error)
|
||||
GetBool(path string) (bool, error)
|
||||
GetFloat64(path string) (float64, error)
|
||||
GetInt64(path string) (int64, error)
|
||||
GetSlice(path string) ([]interface{}, error)
|
||||
GetStringMap(path string) (map[string]string, error)
|
||||
GetMap(path string) (map[string]interface{}, error)
|
||||
MarshalJSON() ([]byte, error)
|
||||
UnmarshalJSON([]byte) error
|
||||
GetGvk() resid.Gvk
|
||||
SetGvk(resid.Gvk)
|
||||
GetKind() string
|
||||
GetName() string
|
||||
SetName(string)
|
||||
SetNamespace(string)
|
||||
GetLabels() map[string]string
|
||||
SetLabels(map[string]string)
|
||||
GetAnnotations() map[string]string
|
||||
SetAnnotations(map[string]string)
|
||||
MatchesLabelSelector(selector string) (bool, error)
|
||||
MatchesAnnotationSelector(selector string) (bool, error)
|
||||
Patch(Kunstructured) error
|
||||
}
|
||||
|
||||
// KunstructuredFactory makes instances of Kunstructured.
|
||||
type KunstructuredFactory interface {
|
||||
SliceFromBytes([]byte) ([]Kunstructured, error)
|
||||
FromMap(m map[string]interface{}) Kunstructured
|
||||
Hasher() KunstructuredHasher
|
||||
MakeConfigMap(
|
||||
kvLdr KvLoader,
|
||||
options *types.GeneratorOptions,
|
||||
args *types.ConfigMapArgs) (Kunstructured, error)
|
||||
MakeSecret(
|
||||
kvLdr KvLoader,
|
||||
options *types.GeneratorOptions,
|
||||
args *types.SecretArgs) (Kunstructured, error)
|
||||
}
|
||||
|
||||
// KunstructuredHasher returns a hash of the argument
|
||||
// or an error.
|
||||
type KunstructuredHasher interface {
|
||||
Hash(Kunstructured) (string, error)
|
||||
}
|
||||
|
||||
// See core.v1.SecretTypeOpaque
|
||||
const SecretTypeOpaque = "Opaque"
|
||||
@@ -1,24 +1,12 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
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.
|
||||
*/
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package expansion provides functions find and replace $(FOO) style variables in strings.
|
||||
package expansion
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -36,15 +24,22 @@ func syntaxWrap(input string) string {
|
||||
// implements the expansion semantics defined in the expansion spec; it
|
||||
// returns the input string wrapped in the expansion syntax if no mapping
|
||||
// for the input is found.
|
||||
func MappingFuncFor(context ...map[string]string) func(string) string {
|
||||
return func(input string) string {
|
||||
func MappingFuncFor(
|
||||
counts map[string]int,
|
||||
context ...map[string]interface{}) func(string) interface{} {
|
||||
return func(input string) interface{} {
|
||||
for _, vars := range context {
|
||||
val, ok := vars[input]
|
||||
if ok {
|
||||
return val
|
||||
counts[input]++
|
||||
switch typedV := val.(type) {
|
||||
case string, int64, float64, bool:
|
||||
return typedV
|
||||
default:
|
||||
return syntaxWrap(input)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return syntaxWrap(input)
|
||||
}
|
||||
}
|
||||
@@ -52,7 +47,7 @@ func MappingFuncFor(context ...map[string]string) func(string) string {
|
||||
// Expand replaces variable references in the input string according to
|
||||
// the expansion spec using the given mapping function to resolve the
|
||||
// values of variables.
|
||||
func Expand(input string, mapping func(string) string) string {
|
||||
func Expand(input string, mapping func(string) interface{}) interface{} {
|
||||
var buf bytes.Buffer
|
||||
checkpoint := 0
|
||||
for cursor := 0; cursor < len(input); cursor++ {
|
||||
@@ -69,7 +64,14 @@ func Expand(input string, mapping func(string) string) string {
|
||||
// We were able to read a variable name correctly;
|
||||
// apply the mapping to the variable name and copy the
|
||||
// bytes into the buffer
|
||||
buf.WriteString(mapping(read))
|
||||
mapped := mapping(read)
|
||||
if input == syntaxWrap(read) {
|
||||
// Preserve the type of variable
|
||||
return mapped
|
||||
}
|
||||
|
||||
// Variable is used in a middle of a string
|
||||
buf.WriteString(fmt.Sprintf("%v", mapped))
|
||||
} else {
|
||||
// Not a variable name; copy the read bytes into the buffer
|
||||
buf.WriteString(read)
|
||||
365
api/internal/accumulator/expansion/expand_test.go
Normal file
365
api/internal/accumulator/expansion/expand_test.go
Normal file
@@ -0,0 +1,365 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package expansion_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
. "sigs.k8s.io/kustomize/api/internal/accumulator/expansion"
|
||||
)
|
||||
|
||||
type expected struct {
|
||||
count int
|
||||
edited string
|
||||
}
|
||||
|
||||
func TestMapReference(t *testing.T) {
|
||||
type env struct {
|
||||
Name string
|
||||
Value interface{}
|
||||
}
|
||||
envs := []env{
|
||||
{
|
||||
Name: "FOO",
|
||||
Value: "bar",
|
||||
},
|
||||
{
|
||||
Name: "ZOO",
|
||||
Value: "$(FOO)-1",
|
||||
},
|
||||
{
|
||||
Name: "BLU",
|
||||
Value: "$(ZOO)-2",
|
||||
},
|
||||
{
|
||||
Name: "INT",
|
||||
Value: 2,
|
||||
},
|
||||
{
|
||||
Name: "ZINT",
|
||||
Value: "$(INT)",
|
||||
},
|
||||
{
|
||||
Name: "BOOL",
|
||||
Value: true,
|
||||
},
|
||||
{
|
||||
Name: "ZBOOL",
|
||||
Value: "$(BOOL)",
|
||||
},
|
||||
}
|
||||
|
||||
declaredEnv := map[string]interface{}{
|
||||
"FOO": "bar",
|
||||
"ZOO": "$(FOO)-1",
|
||||
"BLU": "$(ZOO)-2",
|
||||
"INT": "2",
|
||||
"ZINT": "$(INT)",
|
||||
"BOOL": "true",
|
||||
"ZBOOL": "$(BOOL)",
|
||||
}
|
||||
|
||||
counts := make(map[string]int)
|
||||
mapping := MappingFuncFor(counts, declaredEnv)
|
||||
|
||||
for _, env := range envs {
|
||||
declaredEnv[env.Name] = Expand(fmt.Sprintf("%v", env.Value), mapping)
|
||||
}
|
||||
|
||||
expectedEnv := map[string]expected{
|
||||
"FOO": {count: 1, edited: "bar"},
|
||||
"ZOO": {count: 1, edited: "bar-1"},
|
||||
"BLU": {count: 0, edited: "bar-1-2"},
|
||||
"INT": {count: 1, edited: "2"},
|
||||
"ZINT": {count: 0, edited: "2"},
|
||||
"BOOL": {count: 1, edited: "true"},
|
||||
"ZBOOL": {count: 0, edited: "true"},
|
||||
}
|
||||
|
||||
for k, v := range expectedEnv {
|
||||
if e, a := v, declaredEnv[k]; e.edited != a || e.count != counts[k] {
|
||||
t.Errorf("Expected %v count=%d, got %v count=%d",
|
||||
e.edited, e.count, a, counts[k])
|
||||
} else {
|
||||
delete(declaredEnv, k)
|
||||
}
|
||||
}
|
||||
|
||||
if len(declaredEnv) != 0 {
|
||||
t.Errorf("Unexpected keys in declared env: %v", declaredEnv)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapping(t *testing.T) {
|
||||
context := map[string]interface{}{
|
||||
"VAR_A": "A",
|
||||
"VAR_B": "B",
|
||||
"VAR_C": "C",
|
||||
"VAR_REF": "$(VAR_A)",
|
||||
"VAR_EMPTY": "",
|
||||
}
|
||||
doExpansionTest(t, context)
|
||||
}
|
||||
|
||||
func TestMappingDual(t *testing.T) {
|
||||
context := map[string]interface{}{
|
||||
"VAR_A": "A",
|
||||
"VAR_EMPTY": "",
|
||||
}
|
||||
context2 := map[string]interface{}{
|
||||
"VAR_B": "B",
|
||||
"VAR_C": "C",
|
||||
"VAR_REF": "$(VAR_A)",
|
||||
}
|
||||
|
||||
doExpansionTest(t, context, context2)
|
||||
}
|
||||
|
||||
func doExpansionTest(t *testing.T, context ...map[string]interface{}) {
|
||||
cases := []struct {
|
||||
name string
|
||||
input string
|
||||
expected string
|
||||
counts map[string]int
|
||||
}{
|
||||
{
|
||||
name: "whole string",
|
||||
input: "$(VAR_A)",
|
||||
expected: "A",
|
||||
counts: map[string]int{"VAR_A": 1},
|
||||
},
|
||||
{
|
||||
name: "repeat",
|
||||
input: "$(VAR_A)-$(VAR_A)",
|
||||
expected: "A-A",
|
||||
counts: map[string]int{"VAR_A": 2},
|
||||
},
|
||||
{
|
||||
name: "multiple repeats",
|
||||
input: "$(VAR_A)-$(VAR_B)-$(VAR_B)-$(VAR_B)-$(VAR_A)",
|
||||
expected: "A-B-B-B-A",
|
||||
counts: map[string]int{"VAR_A": 2, "VAR_B": 3},
|
||||
},
|
||||
{
|
||||
name: "beginning",
|
||||
input: "$(VAR_A)-1",
|
||||
expected: "A-1",
|
||||
counts: map[string]int{"VAR_A": 1},
|
||||
},
|
||||
{
|
||||
name: "middle",
|
||||
input: "___$(VAR_B)___",
|
||||
expected: "___B___",
|
||||
counts: map[string]int{"VAR_B": 1},
|
||||
},
|
||||
{
|
||||
name: "end",
|
||||
input: "___$(VAR_C)",
|
||||
expected: "___C",
|
||||
counts: map[string]int{"VAR_C": 1},
|
||||
},
|
||||
{
|
||||
name: "compound",
|
||||
input: "$(VAR_A)_$(VAR_B)_$(VAR_C)",
|
||||
expected: "A_B_C",
|
||||
counts: map[string]int{"VAR_A": 1, "VAR_B": 1, "VAR_C": 1},
|
||||
},
|
||||
{
|
||||
name: "escape & expand",
|
||||
input: "$$(VAR_B)_$(VAR_A)",
|
||||
expected: "$(VAR_B)_A",
|
||||
counts: map[string]int{"VAR_A": 1},
|
||||
},
|
||||
{
|
||||
name: "compound escape",
|
||||
input: "$$(VAR_A)_$$(VAR_B)",
|
||||
expected: "$(VAR_A)_$(VAR_B)",
|
||||
},
|
||||
{
|
||||
name: "mixed in escapes",
|
||||
input: "f000-$$VAR_A",
|
||||
expected: "f000-$VAR_A",
|
||||
},
|
||||
{
|
||||
name: "backslash escape ignored",
|
||||
input: "foo\\$(VAR_C)bar",
|
||||
expected: "foo\\Cbar",
|
||||
counts: map[string]int{"VAR_C": 1},
|
||||
},
|
||||
{
|
||||
name: "backslash escape ignored",
|
||||
input: "foo\\\\$(VAR_C)bar",
|
||||
expected: "foo\\\\Cbar",
|
||||
counts: map[string]int{"VAR_C": 1},
|
||||
},
|
||||
{
|
||||
name: "lots of backslashes",
|
||||
input: "foo\\\\\\\\$(VAR_A)bar",
|
||||
expected: "foo\\\\\\\\Abar",
|
||||
counts: map[string]int{"VAR_A": 1},
|
||||
},
|
||||
{
|
||||
name: "nested var references",
|
||||
input: "$(VAR_A$(VAR_B))",
|
||||
expected: "$(VAR_A$(VAR_B))",
|
||||
},
|
||||
{
|
||||
name: "nested var references second type",
|
||||
input: "$(VAR_A$(VAR_B)",
|
||||
expected: "$(VAR_A$(VAR_B)",
|
||||
},
|
||||
{
|
||||
name: "value is a reference",
|
||||
input: "$(VAR_REF)",
|
||||
expected: "$(VAR_A)",
|
||||
counts: map[string]int{"VAR_REF": 1},
|
||||
},
|
||||
{
|
||||
name: "value is a reference x 2",
|
||||
input: "%%$(VAR_REF)--$(VAR_REF)%%",
|
||||
expected: "%%$(VAR_A)--$(VAR_A)%%",
|
||||
counts: map[string]int{"VAR_REF": 2},
|
||||
},
|
||||
{
|
||||
name: "empty var",
|
||||
input: "foo$(VAR_EMPTY)bar",
|
||||
expected: "foobar",
|
||||
counts: map[string]int{"VAR_EMPTY": 1},
|
||||
},
|
||||
{
|
||||
name: "unterminated expression",
|
||||
input: "foo$(VAR_Awhoops!",
|
||||
expected: "foo$(VAR_Awhoops!",
|
||||
},
|
||||
{
|
||||
name: "expression without operator",
|
||||
input: "f00__(VAR_A)__",
|
||||
expected: "f00__(VAR_A)__",
|
||||
},
|
||||
{
|
||||
name: "shell special vars pass through",
|
||||
input: "$?_boo_$!",
|
||||
expected: "$?_boo_$!",
|
||||
},
|
||||
{
|
||||
name: "bare operators are ignored",
|
||||
input: "$VAR_A",
|
||||
expected: "$VAR_A",
|
||||
},
|
||||
{
|
||||
name: "undefined vars are passed through",
|
||||
input: "$(VAR_DNE)",
|
||||
expected: "$(VAR_DNE)",
|
||||
},
|
||||
{
|
||||
name: "multiple (even) operators, var undefined",
|
||||
input: "$$$$$$(BIG_MONEY)",
|
||||
expected: "$$$(BIG_MONEY)",
|
||||
},
|
||||
{
|
||||
name: "multiple (even) operators, var defined",
|
||||
input: "$$$$$$(VAR_A)",
|
||||
expected: "$$$(VAR_A)",
|
||||
},
|
||||
{
|
||||
name: "multiple (odd) operators, var undefined",
|
||||
input: "$$$$$$$(GOOD_ODDS)",
|
||||
expected: "$$$$(GOOD_ODDS)",
|
||||
},
|
||||
{
|
||||
name: "multiple (odd) operators, var defined",
|
||||
input: "$$$$$$$(VAR_A)",
|
||||
expected: "$$$A",
|
||||
counts: map[string]int{"VAR_A": 1},
|
||||
},
|
||||
{
|
||||
name: "missing open expression",
|
||||
input: "$VAR_A)",
|
||||
expected: "$VAR_A)",
|
||||
},
|
||||
{
|
||||
name: "shell syntax ignored",
|
||||
input: "${VAR_A}",
|
||||
expected: "${VAR_A}",
|
||||
},
|
||||
{
|
||||
name: "trailing incomplete expression not consumed",
|
||||
input: "$(VAR_B)_______$(A",
|
||||
expected: "B_______$(A",
|
||||
counts: map[string]int{"VAR_B": 1},
|
||||
},
|
||||
{
|
||||
name: "trailing incomplete expression, no content, is not consumed",
|
||||
input: "$(VAR_C)_______$(",
|
||||
expected: "C_______$(",
|
||||
counts: map[string]int{"VAR_C": 1},
|
||||
},
|
||||
{
|
||||
name: "operator at end of input string is preserved",
|
||||
input: "$(VAR_A)foobarzab$",
|
||||
expected: "Afoobarzab$",
|
||||
counts: map[string]int{"VAR_A": 1},
|
||||
},
|
||||
{
|
||||
name: "shell escaped incomplete expr",
|
||||
input: "foo-\\$(VAR_A",
|
||||
expected: "foo-\\$(VAR_A",
|
||||
},
|
||||
{
|
||||
name: "lots of $( in middle",
|
||||
input: "--$($($($($--",
|
||||
expected: "--$($($($($--",
|
||||
},
|
||||
{
|
||||
name: "lots of $( in beginning",
|
||||
input: "$($($($($--foo$(",
|
||||
expected: "$($($($($--foo$(",
|
||||
},
|
||||
{
|
||||
name: "lots of $( at end",
|
||||
input: "foo0--$($($($(",
|
||||
expected: "foo0--$($($($(",
|
||||
},
|
||||
{
|
||||
name: "escaped operators in variable names are not escaped",
|
||||
input: "$(foo$$var)",
|
||||
expected: "$(foo$$var)",
|
||||
},
|
||||
{
|
||||
name: "newline not expanded",
|
||||
input: "\n",
|
||||
expected: "\n",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
counts := make(map[string]int)
|
||||
mapping := MappingFuncFor(counts, context...)
|
||||
expanded := Expand(fmt.Sprintf("%v", tc.input), mapping)
|
||||
if e, a := tc.expected, expanded; e != a {
|
||||
t.Errorf("%v: expected %q, got %q", tc.name, e, a)
|
||||
}
|
||||
if len(counts) != len(tc.counts) {
|
||||
t.Errorf("%v: len(counts)=%d != len(tc.counts)=%d",
|
||||
tc.name, len(counts), len(tc.counts))
|
||||
}
|
||||
if len(tc.counts) > 0 {
|
||||
for k, expectedCount := range tc.counts {
|
||||
c, ok := counts[k]
|
||||
if ok {
|
||||
if c != expectedCount {
|
||||
t.Errorf(
|
||||
"%v: k=%s, expected count %d, got %d",
|
||||
tc.name, k, expectedCount, c)
|
||||
}
|
||||
} else {
|
||||
t.Errorf(
|
||||
"%v: k=%s, expected count %d, got zero",
|
||||
tc.name, k, expectedCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
188
api/internal/accumulator/loadconfigfromcrds.go
Normal file
188
api/internal/accumulator/loadconfigfromcrds.go
Normal file
@@ -0,0 +1,188 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package accumulator
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/go-openapi/spec"
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/kube-openapi/pkg/common"
|
||||
"sigs.k8s.io/kustomize/api/filesys"
|
||||
"sigs.k8s.io/kustomize/api/ifc"
|
||||
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
type myProperties map[string]spec.Schema
|
||||
type nameToApiMap map[string]common.OpenAPIDefinition
|
||||
|
||||
// LoadConfigFromCRDs parse CRD schemas from paths into a TransformerConfig
|
||||
func LoadConfigFromCRDs(
|
||||
ldr ifc.Loader, paths []string) (*builtinconfig.TransformerConfig, error) {
|
||||
tc := builtinconfig.MakeEmptyConfig()
|
||||
for _, path := range paths {
|
||||
content, err := ldr.Load(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m, err := makeNameToApiMap(content)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "unable to parse open API definition from '%s'", path)
|
||||
}
|
||||
otherTc, err := makeConfigFromApiMap(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tc, err = tc.Merge(otherTc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return tc, nil
|
||||
}
|
||||
|
||||
func makeNameToApiMap(content []byte) (result nameToApiMap, err error) {
|
||||
if content[0] == '{' {
|
||||
err = json.Unmarshal(content, &result)
|
||||
} else {
|
||||
err = yaml.Unmarshal(content, &result)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func makeConfigFromApiMap(m nameToApiMap) (*builtinconfig.TransformerConfig, error) {
|
||||
result := builtinconfig.MakeEmptyConfig()
|
||||
for name, api := range m {
|
||||
if !looksLikeAk8sType(api.Schema.SchemaProps.Properties) {
|
||||
continue
|
||||
}
|
||||
tc := builtinconfig.MakeEmptyConfig()
|
||||
err := loadCrdIntoConfig(
|
||||
tc, makeGvkFromTypeName(name), m, name, []string{})
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
result, err = result.Merge(tc)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// TODO: Get Group and Version for CRD from the
|
||||
// openAPI definition once
|
||||
// "x-kubernetes-group-version-kind" is available in CRD
|
||||
func makeGvkFromTypeName(n string) resid.Gvk {
|
||||
names := strings.Split(n, filesys.SelfDir)
|
||||
kind := names[len(names)-1]
|
||||
return resid.Gvk{Kind: kind}
|
||||
}
|
||||
|
||||
func looksLikeAk8sType(properties myProperties) bool {
|
||||
_, ok := properties["kind"]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
_, ok = properties["apiVersion"]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
_, ok = properties["metadata"]
|
||||
return ok
|
||||
}
|
||||
|
||||
const (
|
||||
// "x-kubernetes-annotation": ""
|
||||
xAnnotation = "x-kubernetes-annotation"
|
||||
|
||||
// "x-kubernetes-label-selector": ""
|
||||
xLabelSelector = "x-kubernetes-label-selector"
|
||||
|
||||
// "x-kubernetes-identity": ""
|
||||
xIdentity = "x-kubernetes-identity"
|
||||
|
||||
// "x-kubernetes-object-ref-api-version": <apiVersion name>
|
||||
xVersion = "x-kubernetes-object-ref-api-version"
|
||||
|
||||
// "x-kubernetes-object-ref-kind": <kind name>
|
||||
xKind = "x-kubernetes-object-ref-kind"
|
||||
|
||||
// "x-kubernetes-object-ref-name-key": "name"
|
||||
// default is "name"
|
||||
xNameKey = "x-kubernetes-object-ref-name-key"
|
||||
)
|
||||
|
||||
// loadCrdIntoConfig loads a CRD spec into a TransformerConfig
|
||||
func loadCrdIntoConfig(
|
||||
theConfig *builtinconfig.TransformerConfig, theGvk resid.Gvk, theMap nameToApiMap,
|
||||
typeName string, path []string) (err error) {
|
||||
api, ok := theMap[typeName]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
for propName, property := range api.Schema.SchemaProps.Properties {
|
||||
_, annotate := property.Extensions.GetString(xAnnotation)
|
||||
if annotate {
|
||||
err = theConfig.AddAnnotationFieldSpec(
|
||||
makeFs(theGvk, append(path, propName)))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
_, label := property.Extensions.GetString(xLabelSelector)
|
||||
if label {
|
||||
err = theConfig.AddLabelFieldSpec(
|
||||
makeFs(theGvk, append(path, propName)))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
_, identity := property.Extensions.GetString(xIdentity)
|
||||
if identity {
|
||||
err = theConfig.AddPrefixFieldSpec(
|
||||
makeFs(theGvk, append(path, propName)))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
version, ok := property.Extensions.GetString(xVersion)
|
||||
if ok {
|
||||
kind, ok := property.Extensions.GetString(xKind)
|
||||
if ok {
|
||||
nameKey, ok := property.Extensions.GetString(xNameKey)
|
||||
if !ok {
|
||||
nameKey = "name"
|
||||
}
|
||||
err = theConfig.AddNamereferenceFieldSpec(
|
||||
builtinconfig.NameBackReferences{
|
||||
Gvk: resid.Gvk{Kind: kind, Version: version},
|
||||
FieldSpecs: []types.FieldSpec{
|
||||
makeFs(theGvk, append(path, propName, nameKey))},
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
if property.Ref.GetURL() != nil {
|
||||
loadCrdIntoConfig(
|
||||
theConfig, theGvk, theMap,
|
||||
property.Ref.String(), append(path, propName))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeFs(in resid.Gvk, path []string) types.FieldSpec {
|
||||
return types.FieldSpec{
|
||||
CreateIfNotPresent: false,
|
||||
Gvk: in,
|
||||
Path: strings.Join(path, "/"),
|
||||
}
|
||||
}
|
||||
181
api/internal/accumulator/loadconfigfromcrds_test.go
Normal file
181
api/internal/accumulator/loadconfigfromcrds_test.go
Normal file
@@ -0,0 +1,181 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package accumulator_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/filesys"
|
||||
"sigs.k8s.io/kustomize/api/loader"
|
||||
|
||||
. "sigs.k8s.io/kustomize/api/internal/accumulator"
|
||||
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
)
|
||||
|
||||
// This defines two CRD's: Bee and MyKind.
|
||||
//
|
||||
// Bee is boring, it's spec has no dependencies.
|
||||
//
|
||||
// MyKind, however, has a spec that contains
|
||||
// a Bee and a (k8s native) Secret.
|
||||
const (
|
||||
crdContent = `
|
||||
{
|
||||
"github.com/example/pkg/apis/jingfang/v1beta1.Bee": {
|
||||
"Schema": {
|
||||
"description": "Bee",
|
||||
"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": {
|
||||
"$ref": "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"
|
||||
},
|
||||
"spec": {
|
||||
"$ref": "github.com/example/pkg/apis/jingfang/v1beta1.BeeSpec"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "github.com/example/pkg/apis/jingfang/v1beta1.BeeStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Dependencies": [
|
||||
"github.com/example/pkg/apis/jingfang/v1beta1.BeeSpec",
|
||||
"github.com/example/pkg/apis/jingfang/v1beta1.BeeStatus",
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"
|
||||
]
|
||||
},
|
||||
"github.com/example/pkg/apis/jingfang/v1beta1.BeeSpec": {
|
||||
"Schema": {
|
||||
"description": "BeeSpec defines the desired state of Bee"
|
||||
},
|
||||
"Dependencies": []
|
||||
},
|
||||
"github.com/example/pkg/apis/jingfang/v1beta1.BeeStatus": {
|
||||
"Schema": {
|
||||
"description": "BeeStatus defines the observed state of Bee"
|
||||
},
|
||||
"Dependencies": []
|
||||
},
|
||||
"github.com/example/pkg/apis/jingfang/v1beta1.MyKind": {
|
||||
"Schema": {
|
||||
"description": "MyKind",
|
||||
"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": {
|
||||
"$ref": "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"
|
||||
},
|
||||
"spec": {
|
||||
"$ref": "github.com/example/pkg/apis/jingfang/v1beta1.MyKindSpec"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "github.com/example/pkg/apis/jingfang/v1beta1.MyKindStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Dependencies": [
|
||||
"github.com/example/pkg/apis/jingfang/v1beta1.MyKindSpec",
|
||||
"github.com/example/pkg/apis/jingfang/v1beta1.MyKindStatus",
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"
|
||||
]
|
||||
},
|
||||
"github.com/example/pkg/apis/jingfang/v1beta1.MyKindSpec": {
|
||||
"Schema": {
|
||||
"description": "MyKindSpec defines the desired state of MyKind",
|
||||
"properties": {
|
||||
"beeRef": {
|
||||
"x-kubernetes-object-ref-api-version": "v1beta1",
|
||||
"x-kubernetes-object-ref-kind": "Bee",
|
||||
"$ref": "github.com/example/pkg/apis/jingfang/v1beta1.Bee"
|
||||
},
|
||||
"secretRef": {
|
||||
"description": "If defined, we use this secret for configuring the MYSQL_ROOT_PASSWORD
|
||||
If it is not set we generate a secret dynamically",
|
||||
"x-kubernetes-object-ref-api-version": "v1",
|
||||
"x-kubernetes-object-ref-kind": "Secret",
|
||||
"$ref": "k8s.io/api/core/v1.LocalObjectReference"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Dependencies": [
|
||||
"github.com/example/pkg/apis/jingfang/v1beta1.Bee",
|
||||
"k8s.io/api/core/v1.LocalObjectReference"
|
||||
]
|
||||
},
|
||||
"github.com/example/pkg/apis/jingfang/v1beta1.MyKindStatus": {
|
||||
"Schema": {
|
||||
"description": "MyKindStatus defines the observed state of MyKind"
|
||||
},
|
||||
"Dependencies": []
|
||||
}
|
||||
}
|
||||
`
|
||||
)
|
||||
|
||||
func TestLoadCRDs(t *testing.T) {
|
||||
nbrs := []builtinconfig.NameBackReferences{
|
||||
{
|
||||
Gvk: resid.Gvk{Kind: "Secret", Version: "v1"},
|
||||
FieldSpecs: []types.FieldSpec{
|
||||
{
|
||||
CreateIfNotPresent: false,
|
||||
Gvk: resid.Gvk{Kind: "MyKind"},
|
||||
Path: "spec/secretRef/name",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Gvk: resid.Gvk{Kind: "Bee", Version: "v1beta1"},
|
||||
FieldSpecs: []types.FieldSpec{
|
||||
{
|
||||
CreateIfNotPresent: false,
|
||||
Gvk: resid.Gvk{Kind: "MyKind"},
|
||||
Path: "spec/beeRef/name",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expectedTc := &builtinconfig.TransformerConfig{
|
||||
NameReference: nbrs,
|
||||
}
|
||||
|
||||
fSys := filesys.MakeFsInMemory()
|
||||
fSys.WriteFile("/testpath/crd.json", []byte(crdContent))
|
||||
ldr, err := loader.NewLoader(loader.RestrictionRootOnly, "/testpath", fSys)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error:%v", err)
|
||||
}
|
||||
|
||||
actualTc, err := LoadConfigFromCRDs(ldr, []string{"crd.json"})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error:%v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(actualTc, expectedTc) {
|
||||
t.Fatalf("expected\n %v\n but got\n %v\n", expectedTc, actualTc)
|
||||
}
|
||||
}
|
||||
269
api/internal/accumulator/namereferencetransformer.go
Normal file
269
api/internal/accumulator/namereferencetransformer.go
Normal file
@@ -0,0 +1,269 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package accumulator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/resource"
|
||||
"sigs.k8s.io/kustomize/api/transform"
|
||||
)
|
||||
|
||||
type nameReferenceTransformer struct {
|
||||
backRefs []builtinconfig.NameBackReferences
|
||||
}
|
||||
|
||||
var _ resmap.Transformer = &nameReferenceTransformer{}
|
||||
|
||||
// newNameReferenceTransformer constructs a nameReferenceTransformer
|
||||
// with a given slice of NameBackReferences.
|
||||
func newNameReferenceTransformer(br []builtinconfig.NameBackReferences) resmap.Transformer {
|
||||
if br == nil {
|
||||
log.Fatal("backrefs not expected to be nil")
|
||||
}
|
||||
return &nameReferenceTransformer{backRefs: br}
|
||||
}
|
||||
|
||||
// Transform updates name references in resource A that
|
||||
// refer to resource B, given that B's name may have
|
||||
// changed.
|
||||
//
|
||||
// For example, a HorizontalPodAutoscaler (HPA)
|
||||
// necessarily refers to a Deployment, the thing that
|
||||
// the HPA scales. The Deployment name might change
|
||||
// (e.g. prefix added), and the reference in the HPA
|
||||
// has to be fixed.
|
||||
//
|
||||
// In the outer loop over the ResMap below, say we
|
||||
// encounter a specific HPA. Then, in scanning backrefs,
|
||||
// we encounter an entry like
|
||||
//
|
||||
// - kind: Deployment
|
||||
// fieldSpecs:
|
||||
// - kind: HorizontalPodAutoscaler
|
||||
// path: spec/scaleTargetRef/name
|
||||
//
|
||||
// This entry says that an HPA, via its
|
||||
// 'spec/scaleTargetRef/name' field, may refer to a
|
||||
// Deployment. This match to HPA means we may need to
|
||||
// modify the value in its 'spec/scaleTargetRef/name'
|
||||
// field, by searching for the thing it refers to,
|
||||
// and getting its new name.
|
||||
//
|
||||
// As a filter, and search optimization, we compute a
|
||||
// subset of all resources that the HPA could refer to,
|
||||
// by excluding objects from other namespaces, and
|
||||
// excluding objects that don't have the same prefix-
|
||||
// suffix mods as the HPA.
|
||||
//
|
||||
// We look in this subset for all Deployment objects
|
||||
// with a resId that has a Name matching the field value
|
||||
// present in the HPA. If no match do nothing; if more
|
||||
// than one match, it's an error.
|
||||
//
|
||||
// We overwrite the HPA name field with the value found
|
||||
// in the Deployment's name field (the name in the raw
|
||||
// object - the modified name - not the unmodified name
|
||||
// in the Deployment's resId).
|
||||
//
|
||||
// This process assumes that the name stored in a ResId
|
||||
// (the ResMap key) isn't modified by name transformers.
|
||||
// Name transformers should only modify the name in the
|
||||
// body of the resource object (the value in the ResMap).
|
||||
//
|
||||
func (o *nameReferenceTransformer) Transform(m resmap.ResMap) error {
|
||||
// TODO: Too much looping, here and in transitive calls.
|
||||
for _, referrer := range m.Resources() {
|
||||
var candidates resmap.ResMap
|
||||
for _, target := range o.backRefs {
|
||||
for _, fSpec := range target.FieldSpecs {
|
||||
if referrer.OrgId().IsSelected(&fSpec.Gvk) {
|
||||
if candidates == nil {
|
||||
candidates = m.SubsetThatCouldBeReferencedByResource(referrer)
|
||||
}
|
||||
err := transform.MutateField(
|
||||
referrer.Map(),
|
||||
fSpec.PathSlice(),
|
||||
fSpec.CreateIfNotPresent,
|
||||
o.getNewNameFunc(
|
||||
// referrer could be an HPA instance,
|
||||
// target could be Gvk for Deployment,
|
||||
// candidate a list of resources "reachable"
|
||||
// from the HPA.
|
||||
referrer, target.Gvk, candidates))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// selectReferral picks the referral among a subset of candidates.
|
||||
// It returns the current name and namespace of the selected candidate.
|
||||
// Note that the content of the referricalCandidateSubset slice is most of the time
|
||||
// identical to the referralCandidates resmap. Still in some cases, such
|
||||
// as ClusterRoleBinding, the subset only contains the resources of a specific
|
||||
// namespace.
|
||||
func (o *nameReferenceTransformer) selectReferral(
|
||||
oldName string,
|
||||
referrer *resource.Resource,
|
||||
target resid.Gvk,
|
||||
referralCandidates resmap.ResMap,
|
||||
referralCandidateSubset []*resource.Resource) (interface{}, interface{}, error) {
|
||||
|
||||
for _, res := range referralCandidateSubset {
|
||||
id := res.OrgId()
|
||||
if id.IsSelected(&target) && res.GetOriginalName() == oldName {
|
||||
matches := referralCandidates.GetMatchingResourcesByOriginalId(id.Equals)
|
||||
// If there's more than one match, there's no way
|
||||
// to know which one to pick, so emit error.
|
||||
if len(matches) > 1 {
|
||||
return nil, nil, fmt.Errorf(
|
||||
"multiple matches for %s:\n %v",
|
||||
id, getIds(matches))
|
||||
}
|
||||
// In the resource, note that it is referenced
|
||||
// by the referrer.
|
||||
res.AppendRefBy(referrer.CurId())
|
||||
// Return transformed name of the object,
|
||||
// complete with prefixes, hashes, etc.
|
||||
return res.GetName(), res.GetNamespace(), nil
|
||||
}
|
||||
}
|
||||
|
||||
return oldName, nil, nil
|
||||
}
|
||||
|
||||
// utility function to replace a simple string by the new name
|
||||
func (o *nameReferenceTransformer) getSimpleNameField(
|
||||
oldName string,
|
||||
referrer *resource.Resource,
|
||||
target resid.Gvk,
|
||||
referralCandidates resmap.ResMap,
|
||||
referralCandidateSubset []*resource.Resource) (interface{}, error) {
|
||||
|
||||
newName, _, err := o.selectReferral(oldName, referrer, target,
|
||||
referralCandidates, referralCandidateSubset)
|
||||
|
||||
return newName, err
|
||||
}
|
||||
|
||||
// utility function to replace name field within a map[string]interface{}
|
||||
// and leverage the namespace field.
|
||||
func (o *nameReferenceTransformer) getNameAndNsStruct(
|
||||
inMap map[string]interface{},
|
||||
referrer *resource.Resource,
|
||||
target resid.Gvk,
|
||||
referralCandidates resmap.ResMap) (interface{}, error) {
|
||||
|
||||
// Example:
|
||||
if _, ok := inMap["name"]; !ok {
|
||||
return nil, fmt.Errorf(
|
||||
"%#v is expected to contain a name field", inMap)
|
||||
}
|
||||
oldName, ok := inMap["name"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(
|
||||
"%#v is expected to contain a name field of type string", oldName)
|
||||
}
|
||||
|
||||
subset := referralCandidates.Resources()
|
||||
if namespacevalue, ok := inMap["namespace"]; ok {
|
||||
namespace := namespacevalue.(string)
|
||||
bynamespace := referralCandidates.GroupedByOriginalNamespace()
|
||||
if _, ok := bynamespace[namespace]; !ok {
|
||||
return inMap, nil
|
||||
}
|
||||
subset = bynamespace[namespace]
|
||||
}
|
||||
|
||||
newname, newnamespace, err := o.selectReferral(oldName, referrer, target,
|
||||
referralCandidates, subset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if (newname == oldName) && (newnamespace == nil) {
|
||||
// no candidate found.
|
||||
return inMap, nil
|
||||
}
|
||||
|
||||
inMap["name"] = newname
|
||||
if newnamespace != "" {
|
||||
// We don't want value "" to replace value "default" since
|
||||
// the empty string is handled as a wild card here not default namespace
|
||||
// by kubernetes.
|
||||
inMap["namespace"] = newnamespace
|
||||
}
|
||||
return inMap, nil
|
||||
|
||||
}
|
||||
|
||||
func (o *nameReferenceTransformer) getNewNameFunc(
|
||||
referrer *resource.Resource,
|
||||
target resid.Gvk,
|
||||
referralCandidates resmap.ResMap) func(in interface{}) (interface{}, error) {
|
||||
return func(in interface{}) (interface{}, error) {
|
||||
switch thing := in.(type) {
|
||||
case string:
|
||||
return o.getSimpleNameField(thing, referrer, target,
|
||||
referralCandidates, referralCandidates.Resources())
|
||||
case map[string]interface{}:
|
||||
// Kind: ValidatingWebhookConfiguration
|
||||
// FieldSpec is webhooks/clientConfig/service
|
||||
return o.getNameAndNsStruct(thing, referrer, target,
|
||||
referralCandidates)
|
||||
case []interface{}:
|
||||
for idx, item := range thing {
|
||||
switch value := item.(type) {
|
||||
case string:
|
||||
// Kind: Role/ClusterRole
|
||||
// FieldSpec is rules.resourceNames
|
||||
newName, err := o.getSimpleNameField(value, referrer, target,
|
||||
referralCandidates, referralCandidates.Resources())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
thing[idx] = newName
|
||||
case map[string]interface{}:
|
||||
// Kind: RoleBinding/ClusterRoleBinding
|
||||
// FieldSpec is subjects
|
||||
// Note: The corresponding fieldSpec had been changed from
|
||||
// from path: subjects/name to just path: subjects. This is
|
||||
// what get mutatefield to request the mapping of the whole
|
||||
// map containing namespace and name instead of just a simple
|
||||
// string field containing the name
|
||||
newMap, err := o.getNameAndNsStruct(value, referrer, target,
|
||||
referralCandidates)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
thing[idx] = newMap
|
||||
default:
|
||||
return nil, fmt.Errorf(
|
||||
"%#v is expected to be either a []string or a []map[string]interface{}", in)
|
||||
}
|
||||
}
|
||||
return in, nil
|
||||
default:
|
||||
return nil, fmt.Errorf(
|
||||
"%#v is expected to be either a string or a []interface{}", in)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getIds(rs []*resource.Resource) []string {
|
||||
var result []string
|
||||
for _, r := range rs {
|
||||
result = append(result, r.CurId().String()+"\n")
|
||||
}
|
||||
return result
|
||||
}
|
||||
1061
api/internal/accumulator/namereferencetransformer_test.go
Normal file
1061
api/internal/accumulator/namereferencetransformer_test.go
Normal file
File diff suppressed because it is too large
Load Diff
113
api/internal/accumulator/refvartransformer.go
Normal file
113
api/internal/accumulator/refvartransformer.go
Normal file
@@ -0,0 +1,113 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package accumulator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
expansion2 "sigs.k8s.io/kustomize/api/internal/accumulator/expansion"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/transform"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
)
|
||||
|
||||
type refVarTransformer struct {
|
||||
varMap map[string]interface{}
|
||||
replacementCounts map[string]int
|
||||
fieldSpecs []types.FieldSpec
|
||||
mappingFunc func(string) interface{}
|
||||
}
|
||||
|
||||
// newRefVarTransformer returns a new refVarTransformer
|
||||
// that replaces $(VAR) style variables with values.
|
||||
// The fieldSpecs are the places to look for occurrences of $(VAR).
|
||||
func newRefVarTransformer(
|
||||
varMap map[string]interface{}, fs []types.FieldSpec) *refVarTransformer {
|
||||
return &refVarTransformer{
|
||||
varMap: varMap,
|
||||
fieldSpecs: fs,
|
||||
}
|
||||
}
|
||||
|
||||
// replaceVars accepts as 'in' a string, or string array, which can have
|
||||
// embedded instances of $VAR style variables, e.g. a container command string.
|
||||
// The function returns the string with the variables expanded to their final
|
||||
// values.
|
||||
func (rv *refVarTransformer) replaceVars(in interface{}) (interface{}, error) {
|
||||
switch vt := in.(type) {
|
||||
case []interface{}:
|
||||
var xs []interface{}
|
||||
for _, a := range in.([]interface{}) {
|
||||
xs = append(xs, expansion2.Expand(a.(string), rv.mappingFunc))
|
||||
}
|
||||
return xs, nil
|
||||
case map[string]interface{}:
|
||||
inMap := in.(map[string]interface{})
|
||||
xs := make(map[string]interface{}, len(inMap))
|
||||
for k, v := range inMap {
|
||||
s, ok := v.(string)
|
||||
if !ok {
|
||||
// This field not contain a $(VAR) since it is not
|
||||
// of string type. For instance .spec.replicas: 3 in
|
||||
// a Deployment object
|
||||
xs[k] = v
|
||||
} else {
|
||||
// This field can potentially contains a $(VAR) since it is
|
||||
// of string type. For instance .spec.replicas: $(REPLICAS)
|
||||
// in a Deployment object
|
||||
xs[k] = expansion2.Expand(s, rv.mappingFunc)
|
||||
}
|
||||
}
|
||||
return xs, nil
|
||||
case interface{}:
|
||||
s, ok := in.(string)
|
||||
if !ok {
|
||||
// This field not contain a $(VAR) since it is not of string type.
|
||||
return in, nil
|
||||
}
|
||||
// This field can potentially contain a $(VAR) since it is
|
||||
// of string type.
|
||||
return expansion2.Expand(s, rv.mappingFunc), nil
|
||||
// staticcheck erroneously claims that `case nil`
|
||||
// is unreachable here, so suppressing it.
|
||||
//nolint:staticcheck
|
||||
case nil:
|
||||
return nil, nil
|
||||
default:
|
||||
return "", fmt.Errorf("invalid type encountered %T", vt)
|
||||
}
|
||||
}
|
||||
|
||||
// UnusedVars returns slice of Var names that were unused
|
||||
// after a Transform run.
|
||||
func (rv *refVarTransformer) UnusedVars() []string {
|
||||
var unused []string
|
||||
for k := range rv.varMap {
|
||||
_, ok := rv.replacementCounts[k]
|
||||
if !ok {
|
||||
unused = append(unused, k)
|
||||
}
|
||||
}
|
||||
return unused
|
||||
}
|
||||
|
||||
// Transform replaces $(VAR) style variables with values.
|
||||
func (rv *refVarTransformer) Transform(m resmap.ResMap) error {
|
||||
rv.replacementCounts = make(map[string]int)
|
||||
rv.mappingFunc = expansion2.MappingFuncFor(
|
||||
rv.replacementCounts, rv.varMap)
|
||||
for _, res := range m.Resources() {
|
||||
for _, fieldSpec := range rv.fieldSpecs {
|
||||
if res.OrgId().IsSelected(&fieldSpec.Gvk) {
|
||||
if err := transform.MutateField(
|
||||
res.Map(), fieldSpec.PathSlice(),
|
||||
false, rv.replaceVars); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
137
api/internal/accumulator/refvartransformer_test.go
Normal file
137
api/internal/accumulator/refvartransformer_test.go
Normal file
@@ -0,0 +1,137 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package accumulator
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/k8sdeps/kunstruct"
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/resource"
|
||||
resmaptest_test "sigs.k8s.io/kustomize/api/testutils/resmaptest"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
)
|
||||
|
||||
func TestRefVarTransformer(t *testing.T) {
|
||||
type given struct {
|
||||
varMap map[string]interface{}
|
||||
fs []types.FieldSpec
|
||||
res resmap.ResMap
|
||||
}
|
||||
type expected struct {
|
||||
res resmap.ResMap
|
||||
unused []string
|
||||
}
|
||||
testCases := []struct {
|
||||
description string
|
||||
given given
|
||||
expected expected
|
||||
}{
|
||||
{
|
||||
description: "var replacement in map[string]",
|
||||
given: given{
|
||||
varMap: map[string]interface{}{
|
||||
"FOO": "replacementForFoo",
|
||||
"BAR": "replacementForBar",
|
||||
"BAZ": int64(5),
|
||||
"BOO": true,
|
||||
},
|
||||
fs: []types.FieldSpec{
|
||||
{Gvk: resid.Gvk{Version: "v1", Kind: "ConfigMap"}, Path: "data/map"},
|
||||
{Gvk: resid.Gvk{Version: "v1", Kind: "ConfigMap"}, Path: "data/slice"},
|
||||
{Gvk: resid.Gvk{Version: "v1", Kind: "ConfigMap"}, Path: "data/interface"},
|
||||
{Gvk: resid.Gvk{Version: "v1", Kind: "ConfigMap"}, Path: "data/nil"},
|
||||
{Gvk: resid.Gvk{Version: "v1", Kind: "ConfigMap"}, Path: "data/num"},
|
||||
},
|
||||
res: resmaptest_test.NewRmBuilder(
|
||||
t, resource.NewFactory(kunstruct.NewKunstructuredFactoryImpl())).
|
||||
Add(map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "cm1",
|
||||
},
|
||||
"data": map[string]interface{}{
|
||||
"map": map[string]interface{}{
|
||||
"item1": "$(FOO)",
|
||||
"item2": "bla",
|
||||
"item3": "$(BAZ)",
|
||||
"item4": "$(BAZ)+$(BAZ)",
|
||||
"item5": "$(BOO)",
|
||||
"item6": "if $(BOO)",
|
||||
"item7": 2019,
|
||||
},
|
||||
"slice": []interface{}{
|
||||
"$(FOO)",
|
||||
"bla",
|
||||
"$(BAZ)",
|
||||
"$(BAZ)+$(BAZ)",
|
||||
"$(BOO)",
|
||||
"if $(BOO)",
|
||||
},
|
||||
"interface": "$(FOO)",
|
||||
"nil": nil,
|
||||
"num": 2019,
|
||||
}}).ResMap(),
|
||||
},
|
||||
expected: expected{
|
||||
res: resmaptest_test.NewRmBuilder(
|
||||
t, resource.NewFactory(kunstruct.NewKunstructuredFactoryImpl())).
|
||||
Add(map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "cm1",
|
||||
},
|
||||
"data": map[string]interface{}{
|
||||
"map": map[string]interface{}{
|
||||
"item1": "replacementForFoo",
|
||||
"item2": "bla",
|
||||
"item3": int64(5),
|
||||
"item4": "5+5",
|
||||
"item5": true,
|
||||
"item6": "if true",
|
||||
"item7": 2019,
|
||||
},
|
||||
"slice": []interface{}{
|
||||
"replacementForFoo",
|
||||
"bla",
|
||||
int64(5),
|
||||
"5+5",
|
||||
true,
|
||||
"if true",
|
||||
},
|
||||
"interface": "replacementForFoo",
|
||||
"nil": nil,
|
||||
"num": 2019,
|
||||
}}).ResMap(),
|
||||
unused: []string{"BAR"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.description, func(t *testing.T) {
|
||||
// arrange
|
||||
tr := newRefVarTransformer(tc.given.varMap, tc.given.fs)
|
||||
|
||||
// act
|
||||
err := tr.Transform(tc.given.res)
|
||||
|
||||
// assert
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
a, e := tc.given.res, tc.expected.res
|
||||
if !reflect.DeepEqual(a, e) {
|
||||
err = e.ErrorIfNotEqualLists(a)
|
||||
t.Fatalf("actual doesn't match expected: \nACTUAL:\n%v\nEXPECTED:\n%v\nERR: %v", a, e, err)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
166
api/internal/accumulator/resaccumulator.go
Normal file
166
api/internal/accumulator/resaccumulator.go
Normal file
@@ -0,0 +1,166 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package accumulator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
)
|
||||
|
||||
// ResAccumulator accumulates resources and the rules
|
||||
// used to customize those resources. It's a ResMap
|
||||
// plus stuff needed to modify the ResMap.
|
||||
type ResAccumulator struct {
|
||||
resMap resmap.ResMap
|
||||
tConfig *builtinconfig.TransformerConfig
|
||||
varSet types.VarSet
|
||||
}
|
||||
|
||||
func MakeEmptyAccumulator() *ResAccumulator {
|
||||
ra := &ResAccumulator{}
|
||||
ra.resMap = resmap.New()
|
||||
ra.tConfig = &builtinconfig.TransformerConfig{}
|
||||
ra.varSet = types.NewVarSet()
|
||||
return ra
|
||||
}
|
||||
|
||||
// ResMap returns a copy of the internal resMap.
|
||||
func (ra *ResAccumulator) ResMap() resmap.ResMap {
|
||||
return ra.resMap.ShallowCopy()
|
||||
}
|
||||
|
||||
// Vars returns a copy of underlying vars.
|
||||
func (ra *ResAccumulator) Vars() []types.Var {
|
||||
return ra.varSet.AsSlice()
|
||||
}
|
||||
|
||||
func (ra *ResAccumulator) AppendAll(
|
||||
resources resmap.ResMap) error {
|
||||
return ra.resMap.AppendAll(resources)
|
||||
}
|
||||
|
||||
func (ra *ResAccumulator) AbsorbAll(
|
||||
resources resmap.ResMap) error {
|
||||
return ra.resMap.AbsorbAll(resources)
|
||||
}
|
||||
|
||||
func (ra *ResAccumulator) MergeConfig(
|
||||
tConfig *builtinconfig.TransformerConfig) (err error) {
|
||||
ra.tConfig, err = ra.tConfig.Merge(tConfig)
|
||||
return err
|
||||
}
|
||||
|
||||
func (ra *ResAccumulator) GetTransformerConfig() *builtinconfig.TransformerConfig {
|
||||
return ra.tConfig
|
||||
}
|
||||
|
||||
func (ra *ResAccumulator) MergeVars(incoming []types.Var) error {
|
||||
for _, v := range incoming {
|
||||
targetId := resid.NewResIdWithNamespace(v.ObjRef.GVK(), v.ObjRef.Name, v.ObjRef.Namespace)
|
||||
idMatcher := targetId.GvknEquals
|
||||
if targetId.Namespace != "" || !targetId.IsNamespaceableKind() {
|
||||
// Preserve backward compatibility. An empty namespace means
|
||||
// wildcard search on the namespace hence we still use GvknEquals
|
||||
idMatcher = targetId.Equals
|
||||
}
|
||||
matched := ra.resMap.GetMatchingResourcesByOriginalId(idMatcher)
|
||||
if len(matched) > 1 {
|
||||
return fmt.Errorf(
|
||||
"found %d resId matches for var %s "+
|
||||
"(unable to disambiguate)",
|
||||
len(matched), v)
|
||||
}
|
||||
if len(matched) == 1 {
|
||||
matched[0].AppendRefVarName(v)
|
||||
}
|
||||
}
|
||||
return ra.varSet.MergeSlice(incoming)
|
||||
}
|
||||
|
||||
func (ra *ResAccumulator) MergeAccumulator(other *ResAccumulator) (err error) {
|
||||
err = ra.AppendAll(other.resMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ra.MergeConfig(other.tConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ra.varSet.MergeSet(other.varSet)
|
||||
}
|
||||
|
||||
func (ra *ResAccumulator) findVarValueFromResources(v types.Var) (interface{}, error) {
|
||||
for _, res := range ra.resMap.Resources() {
|
||||
for _, varName := range res.GetRefVarNames() {
|
||||
if varName == v.Name {
|
||||
s, err := res.GetFieldValue(v.FieldRef.FieldPath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf(
|
||||
"field specified in var '%v' "+
|
||||
"not found in corresponding resource", v)
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf(
|
||||
"var '%v' cannot be mapped to a field "+
|
||||
"in the set of known resources", v)
|
||||
}
|
||||
|
||||
// makeVarReplacementMap returns a map of Var names to
|
||||
// their final values. The values are strings intended
|
||||
// for substitution wherever the $(var.Name) occurs.
|
||||
func (ra *ResAccumulator) makeVarReplacementMap() (map[string]interface{}, error) {
|
||||
result := map[string]interface{}{}
|
||||
for _, v := range ra.Vars() {
|
||||
s, err := ra.findVarValueFromResources(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result[v.Name] = s
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (ra *ResAccumulator) Transform(t resmap.Transformer) error {
|
||||
return t.Transform(ra.resMap)
|
||||
}
|
||||
|
||||
func (ra *ResAccumulator) ResolveVars() error {
|
||||
replacementMap, err := ra.makeVarReplacementMap()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(replacementMap) == 0 {
|
||||
return nil
|
||||
}
|
||||
t := newRefVarTransformer(
|
||||
replacementMap, ra.tConfig.VarReference)
|
||||
err = ra.Transform(t)
|
||||
if len(t.UnusedVars()) > 0 {
|
||||
log.Printf(
|
||||
"well-defined vars that were never replaced: %s\n",
|
||||
strings.Join(t.UnusedVars(), ","))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (ra *ResAccumulator) FixBackReferences() (err error) {
|
||||
if ra.tConfig.NameReference == nil {
|
||||
return nil
|
||||
}
|
||||
return ra.Transform(newNameReferenceTransformer(
|
||||
ra.tConfig.NameReference))
|
||||
}
|
||||
417
api/internal/accumulator/resaccumulator_test.go
Normal file
417
api/internal/accumulator/resaccumulator_test.go
Normal file
@@ -0,0 +1,417 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package accumulator_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
. "sigs.k8s.io/kustomize/api/internal/accumulator"
|
||||
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
||||
"sigs.k8s.io/kustomize/api/k8sdeps/kunstruct"
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/resource"
|
||||
resmaptest_test "sigs.k8s.io/kustomize/api/testutils/resmaptest"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
)
|
||||
|
||||
func makeResAccumulator(t *testing.T) (*ResAccumulator, *resource.Factory) {
|
||||
ra := MakeEmptyAccumulator()
|
||||
err := ra.MergeConfig(builtinconfig.MakeDefaultConfig())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
rf := resource.NewFactory(
|
||||
kunstruct.NewKunstructuredFactoryImpl())
|
||||
err = ra.AppendAll(
|
||||
resmaptest_test.NewRmBuilder(t, rf).
|
||||
Add(map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "deploy1",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"template": map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"containers": []interface{}{
|
||||
map[string]interface{}{
|
||||
"command": []interface{}{
|
||||
"myserver",
|
||||
"--somebackendService $(SERVICE_ONE)",
|
||||
"--yetAnother $(SERVICE_TWO)",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}}).
|
||||
Add(map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Service",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "backendOne",
|
||||
}}).
|
||||
Add(map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Service",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "backendTwo",
|
||||
}}).ResMap())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
return ra, rf
|
||||
}
|
||||
|
||||
func TestResolveVarsHappy(t *testing.T) {
|
||||
ra, _ := makeResAccumulator(t)
|
||||
err := ra.MergeVars([]types.Var{
|
||||
{
|
||||
Name: "SERVICE_ONE",
|
||||
ObjRef: types.Target{
|
||||
Gvk: resid.Gvk{Version: "v1", Kind: "Service"},
|
||||
Name: "backendOne"},
|
||||
},
|
||||
{
|
||||
Name: "SERVICE_TWO",
|
||||
ObjRef: types.Target{
|
||||
Gvk: resid.Gvk{Version: "v1", Kind: "Service"},
|
||||
Name: "backendTwo"},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
err = ra.ResolveVars()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
c := getCommand(find("deploy1", ra.ResMap()))
|
||||
if c != "myserver --somebackendService backendOne --yetAnother backendTwo" {
|
||||
t.Fatalf("unexpected command: %s", c)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveVarsOneUnused(t *testing.T) {
|
||||
ra, _ := makeResAccumulator(t)
|
||||
err := ra.MergeVars([]types.Var{
|
||||
{
|
||||
Name: "SERVICE_ONE",
|
||||
ObjRef: types.Target{
|
||||
Gvk: resid.Gvk{Version: "v1", Kind: "Service"},
|
||||
Name: "backendOne"},
|
||||
},
|
||||
{
|
||||
Name: "SERVICE_UNUSED",
|
||||
ObjRef: types.Target{
|
||||
Gvk: resid.Gvk{Version: "v1", Kind: "Service"},
|
||||
Name: "backendTwo"},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
log.SetOutput(&buf)
|
||||
defer func() {
|
||||
log.SetOutput(os.Stderr)
|
||||
}()
|
||||
err = ra.ResolveVars()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
expectLog(t, buf, "well-defined vars that were never replaced: SERVICE_UNUSED")
|
||||
c := getCommand(find("deploy1", ra.ResMap()))
|
||||
if c != "myserver --somebackendService backendOne --yetAnother $(SERVICE_TWO)" {
|
||||
t.Fatalf("unexpected command: %s", c)
|
||||
}
|
||||
}
|
||||
|
||||
func expectLog(t *testing.T, log bytes.Buffer, expect string) {
|
||||
if !strings.Contains(log.String(), expect) {
|
||||
t.Fatalf("expected log containing '%s', got '%s'", expect, log.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveVarsVarNeedsDisambiguation(t *testing.T) {
|
||||
ra, rf := makeResAccumulator(t)
|
||||
|
||||
rm0 := resmap.New()
|
||||
err := rm0.Append(
|
||||
rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Service",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "backendOne",
|
||||
"namespace": "fooNamespace",
|
||||
},
|
||||
}))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
err = ra.AppendAll(rm0)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
|
||||
err = ra.MergeVars([]types.Var{
|
||||
{
|
||||
Name: "SERVICE_ONE",
|
||||
ObjRef: types.Target{
|
||||
Gvk: resid.Gvk{Version: "v1", Kind: "Service"},
|
||||
Name: "backendOne",
|
||||
},
|
||||
},
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatalf("expected error")
|
||||
}
|
||||
if !strings.Contains(
|
||||
err.Error(), "unable to disambiguate") {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func makeNamespacedConfigMapWithDataProviderValue(
|
||||
namespace string,
|
||||
value string,
|
||||
) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "environment",
|
||||
"namespace": namespace,
|
||||
},
|
||||
"data": map[string]interface{}{
|
||||
"provider": value,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func makeVarToNamepaceAndPath(
|
||||
name string,
|
||||
namespace string,
|
||||
path string,
|
||||
) types.Var {
|
||||
return types.Var{
|
||||
Name: name,
|
||||
ObjRef: types.Target{
|
||||
Gvk: resid.Gvk{Version: "v1", Kind: "ConfigMap"},
|
||||
Name: "environment",
|
||||
Namespace: namespace,
|
||||
},
|
||||
FieldRef: types.FieldSelector{FieldPath: path},
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveVarConflicts(t *testing.T) {
|
||||
rf := resource.NewFactory(kunstruct.NewKunstructuredFactoryImpl())
|
||||
|
||||
// create configmaps in foo and bar namespaces with `data.provider` values.
|
||||
fooAws := makeNamespacedConfigMapWithDataProviderValue("foo", "aws")
|
||||
barAws := makeNamespacedConfigMapWithDataProviderValue("bar", "aws")
|
||||
barGcp := makeNamespacedConfigMapWithDataProviderValue("bar", "gcp")
|
||||
|
||||
// create two variables with (apparently) conflicting names that point to
|
||||
// fieldpaths that could be generalized.
|
||||
varFoo := makeVarToNamepaceAndPath("PROVIDER", "foo", "data.provider")
|
||||
varBar := makeVarToNamepaceAndPath("PROVIDER", "bar", "data.provider")
|
||||
|
||||
// create accumulators holding apparently conflicting vars that are not
|
||||
// actually in conflict because they point to the same concrete value.
|
||||
rm0 := resmap.New()
|
||||
rm0.Append(rf.FromMap(fooAws))
|
||||
ac0 := MakeEmptyAccumulator()
|
||||
ac0.AppendAll(rm0)
|
||||
ac0.MergeVars([]types.Var{varFoo})
|
||||
|
||||
rm1 := resmap.New()
|
||||
rm1.Append(rf.FromMap(barAws))
|
||||
ac1 := MakeEmptyAccumulator()
|
||||
ac1.AppendAll(rm1)
|
||||
ac1.MergeVars([]types.Var{varBar})
|
||||
|
||||
// validate that two vars of the same name which reference the same concrete
|
||||
// value do not produce a conflict.
|
||||
err := ac0.MergeAccumulator(ac1)
|
||||
if err == nil {
|
||||
t.Fatalf("see bug gh-1600")
|
||||
}
|
||||
|
||||
// create an accumulator will have an actually conflicting value with the
|
||||
// two above (because it contains a variable whose name is used in the other
|
||||
// accumulators AND whose concrete values are different).
|
||||
rm2 := resmap.New()
|
||||
rm2.Append(rf.FromMap(barGcp))
|
||||
ac2 := MakeEmptyAccumulator()
|
||||
ac2.AppendAll(rm2)
|
||||
ac2.MergeVars([]types.Var{varBar})
|
||||
err = ac1.MergeAccumulator(ac2)
|
||||
if err == nil {
|
||||
t.Fatalf("dupe vars w/ different concrete values should conflict")
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveVarsGoodResIdBadField(t *testing.T) {
|
||||
ra, _ := makeResAccumulator(t)
|
||||
err := ra.MergeVars([]types.Var{
|
||||
{
|
||||
Name: "SERVICE_ONE",
|
||||
ObjRef: types.Target{
|
||||
Gvk: resid.Gvk{Version: "v1", Kind: "Service"},
|
||||
Name: "backendOne"},
|
||||
FieldRef: types.FieldSelector{FieldPath: "nope_nope_nope"},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
err = ra.ResolveVars()
|
||||
if err == nil {
|
||||
t.Fatalf("expected error")
|
||||
}
|
||||
if !strings.Contains(
|
||||
err.Error(),
|
||||
"not found in corresponding resource") {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveVarsUnmappableVar(t *testing.T) {
|
||||
ra, _ := makeResAccumulator(t)
|
||||
err := ra.MergeVars([]types.Var{
|
||||
{
|
||||
Name: "SERVICE_THREE",
|
||||
ObjRef: types.Target{
|
||||
Gvk: resid.Gvk{Version: "v1", Kind: "Service"},
|
||||
Name: "doesNotExist"},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
err = ra.ResolveVars()
|
||||
if err == nil {
|
||||
t.Fatalf("expected error")
|
||||
}
|
||||
if !strings.Contains(
|
||||
err.Error(),
|
||||
"cannot be mapped to a field in the set of known resources") {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveVarsWithNoambiguation(t *testing.T) {
|
||||
ra1, rf := makeResAccumulator(t)
|
||||
err := ra1.MergeVars([]types.Var{
|
||||
{
|
||||
Name: "SERVICE_ONE",
|
||||
ObjRef: types.Target{
|
||||
Gvk: resid.Gvk{Version: "v1", Kind: "Service"},
|
||||
Name: "backendOne",
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
|
||||
// Create another accumulator having a resource with different prefix
|
||||
ra2 := MakeEmptyAccumulator()
|
||||
|
||||
m := resmaptest_test.NewRmBuilder(t, rf).
|
||||
Add(map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "deploy2",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"template": map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"containers": []interface{}{
|
||||
map[string]interface{}{
|
||||
"command": []interface{}{
|
||||
"myserver",
|
||||
"--somebackendService $(SUB_SERVICE_ONE)",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}}).
|
||||
Add(map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Service",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "backendOne",
|
||||
}}).ResMap()
|
||||
|
||||
// Make it seem like this resource
|
||||
// went through a prefix transformer.
|
||||
r := m.GetByIndex(1)
|
||||
r.AddNamePrefix("sub-")
|
||||
r.SetName("sub-backendOne") // original name remains "backendOne"
|
||||
|
||||
err = ra2.AppendAll(m)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
|
||||
err = ra2.MergeVars([]types.Var{
|
||||
{
|
||||
Name: "SUB_SERVICE_ONE",
|
||||
ObjRef: types.Target{
|
||||
Gvk: resid.Gvk{Version: "v1", Kind: "Service"},
|
||||
Name: "backendOne",
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
err = ra1.MergeAccumulator(ra2)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
|
||||
err = ra1.ResolveVars()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func find(name string, resMap resmap.ResMap) *resource.Resource {
|
||||
for _, r := range resMap.Resources() {
|
||||
if r.GetName() == name {
|
||||
return r
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Assumes arg is a deployment, returns the command of first container.
|
||||
func getCommand(r *resource.Resource) string {
|
||||
var m map[string]interface{}
|
||||
var c []interface{}
|
||||
m, _ = r.Map()["spec"].(map[string]interface{})
|
||||
m, _ = m["template"].(map[string]interface{})
|
||||
m, _ = m["spec"].(map[string]interface{})
|
||||
c, _ = m["containers"].([]interface{})
|
||||
m, _ = c[0].(map[string]interface{})
|
||||
|
||||
cmd, _ := m["command"].([]interface{})
|
||||
n := make([]string, len(cmd))
|
||||
for i, v := range cmd {
|
||||
n[i] = v.(string)
|
||||
}
|
||||
return strings.Join(n, " ")
|
||||
}
|
||||
428
api/internal/crawl/README.md
Normal file
428
api/internal/crawl/README.md
Normal file
@@ -0,0 +1,428 @@
|
||||
## What is this?
|
||||
### In short
|
||||
Be the GoDoc.org of k8s configuration files.
|
||||
|
||||
### More explicitly
|
||||
Support k8s document indexing from open-source configurations in order to make
|
||||
it easy for people to learn to use a new feature, explore k8s configs in a
|
||||
central hub, and see some metrics about kustomize use.
|
||||
|
||||
We want people to be able to support three main classes of queries:
|
||||
|
||||
1. Structured document queries: how should I use the following fields
|
||||
- Grace periods: `spec:template:spec:terminationGracePeriod`?
|
||||
- Kustomize inline patch: `patches:patch`?
|
||||
|
||||
2. Key value queries: how should I use this more specific use case of a
|
||||
structure configuration.
|
||||
- HorizontalPodAutoScalers: `kind=HorizontalPodAutoScaler`?
|
||||
- Patches on StatefulSets: `patches:target:kind=StatefulSet`?
|
||||
|
||||
3. Full text search: search the comments and the document text from any
|
||||
type of k8s config file.
|
||||
|
||||
## Road map
|
||||
There is a lot that can be added in order to improve the state of this
|
||||
application. Some more details along with general thoughts and comments can be
|
||||
found in the Roadmap.md file in this directory. This README contains only
|
||||
what can be considered as mostly complete and iterable parts of this project.
|
||||
|
||||
## Running this project
|
||||
Everything is configured using kubernetes, so it should be easy for people to
|
||||
spin this up on any k8s cluster. Everything should just work (TM).
|
||||
|
||||
The config files live in the `config` directory.
|
||||
|
||||
```
|
||||
config
|
||||
├── base
|
||||
│ └── kustomization.yaml
|
||||
├── crawler
|
||||
│ ├── base
|
||||
│ │ ├── github_api_secret.txt
|
||||
│ │ └── kustomization.yaml
|
||||
│ ├── cronjob
|
||||
│ │ ├── cronjob.yaml
|
||||
│ │ └── kustomization.yaml
|
||||
│ └── job
|
||||
│ ├── job.yaml
|
||||
│ └── kustomization.yaml
|
||||
├── elastic
|
||||
│ └── ...
|
||||
├── redis
|
||||
│ ├── document_keystore
|
||||
│ │ ├── kustomization.yaml
|
||||
│ │ ├── redis.yaml
|
||||
│ │ └── service.yaml
|
||||
│ └── http_cache
|
||||
│ ├── kustomization.yaml
|
||||
│ ├── redis.yaml
|
||||
│ └── service.yaml
|
||||
├── webapp
|
||||
│ ├── backend
|
||||
│ │ ├── deployment.yaml
|
||||
│ │ ├── kustomization.yaml
|
||||
│ │ └── service.yaml
|
||||
│ └── frontend
|
||||
│ ├── deployment.yaml
|
||||
│ ├── kustomization.yaml
|
||||
│ └── service.yaml
|
||||
└── schema_files
|
||||
└── kustomization_index
|
||||
├── es_index_mappings.json
|
||||
└── es_index_settings.json
|
||||
```
|
||||
|
||||
To get everything up and running you have to:
|
||||
|
||||
1. Get some instance of elasticsearch working... and configure the
|
||||
configmapGenerator in `config/base` to point to the right endpoint(s). The
|
||||
configurations that need this value to be populated are the following:
|
||||
- `config/crawler/cronjob` to run periodic crawls.
|
||||
- `config/crawler/job` to run crawls on demand.
|
||||
- `config/webapp/backend` to run the search server.
|
||||
|
||||
2. Configure the elasticsearch indices:
|
||||
```
|
||||
kustomize build config/schema_files/kustomization_index | kubectl apply -f -
|
||||
```
|
||||
This will run a `curl` command that reads json data from a ConfigMap. This will
|
||||
setup the schema. If you want to make more complex modifications to the
|
||||
schema, you should refer to the elastic docs to figure out whether the mapping
|
||||
can be added to the current index, or whether you will need to copy the
|
||||
existing index into a different one with the appropriate mappings. Modifications
|
||||
can be made by using the elasticsearch go library and writing a simple program,
|
||||
or it can be made with any http command to the appropriate server endpoint from
|
||||
within the cluster. Unfortunately I did not have the time to write a few helper
|
||||
tools for this. Feel free to contact me if you need help with modifying
|
||||
elasticsearch configs, I'm by no means an expert, but I can try to help.
|
||||
|
||||
3. (Optional) run the redis http chache for the crawler:
|
||||
```
|
||||
kubectl apply -k config/redis/http_cache
|
||||
```
|
||||
This will create a deployment for the cache, and a service. The crawler should
|
||||
be configured to connect to the `http_cache` if it exists, but you can always
|
||||
check the logs to make sure it connects, and that the identifiers match in the
|
||||
crawler configuration and for the service endpoint.
|
||||
|
||||
The please be aware that the cache does not have a persistent volume.
|
||||
|
||||
4. Configure the main redis instance:
|
||||
```
|
||||
kubectl apply -k config/redis/document_keystore
|
||||
```
|
||||
This will create a StatefulSet with a volume of 4GiB for a redis instance.
|
||||
|
||||
5. Get an access token from GitHub.
|
||||
|
||||
To be able to kindly ask GitHub for it's data on k8s config files, you'll need
|
||||
to create an access\_token. From my understanding, this is the only way to do
|
||||
these code search queries (without first specifying a repository).
|
||||
|
||||
To generate a token, go to your GitHub's account in Settings > Developer
|
||||
Settings > Personal access tokens. It should look like this.
|
||||
|
||||

|
||||
|
||||
From here you want to generate a new token and have the following
|
||||
configuration:
|
||||
|
||||

|
||||
|
||||
If you have uses for any other data from this token, (org data, or something
|
||||
else) you can pick and choose, but be careful since it can grant this
|
||||
application access to your notifications, etc. However, any such extension
|
||||
is explicitly a non-goal and would not be maintained by this project.
|
||||
|
||||
6. Launch the crawler:
|
||||
```
|
||||
kustomize build config/crawler/cronjob | kubectl apply -f -
|
||||
```
|
||||
This will periodically run the crawler every day according to the cron timing
|
||||
rules in the cronjob.yaml file.
|
||||
|
||||
Instead, to get the crawler running now, you can run:
|
||||
```
|
||||
kustomize build config/crawler/cronjob | kubectl apply -f -
|
||||
```
|
||||
which will launch a non-periodic version of the crawler. It will take a few
|
||||
minutes for the crawler to split the search, but then config files should
|
||||
start to get populated within 20 minutes. It may take a while to do the
|
||||
first crawl, since it has to fetch rate-limited endpoints for each new file it
|
||||
finds. It should get significantly faster to update in the future.
|
||||
|
||||
5. Launch the search backend
|
||||
```
|
||||
kustomize build config/webapp/backend | kubectl apply -f -
|
||||
```
|
||||
|
||||
6. Launch the search frontend
|
||||
```
|
||||
kustomize build config/webapp/frontend | kubectl apply -f -
|
||||
```
|
||||
|
||||
## Notes about the components
|
||||
|
||||
### Elasticsearch
|
||||
I will add a basic working setup soon. I just did the lazy thing and used an
|
||||
already packaged solution. Most clouds will provide their own elastic
|
||||
environments, however, Elasticsearch is also working on their own
|
||||
implementation of a
|
||||
, which might
|
||||
be worth checking out. Please note that it comes with its own license
|
||||
agreement.
|
||||
|
||||
### Redis
|
||||
There are two Redis instances that are used in this application.
|
||||
|
||||
One of them is configured to have on disk persistence, so make sure to have
|
||||
that set up in your kubernetes cluster. Also note that it is running on a
|
||||
single master node (i.e. it does not automatically shard keys to multiple head
|
||||
nodes as part of a highly available cluster). Since it's storing a sparse
|
||||
graph, I can't imagine this being much of an issue, but it's probably worth
|
||||
mentioning.
|
||||
|
||||
The other Redis instance is running as a HTTP (RFC 7234) cache for etags from
|
||||
GitHub (or any other document store from which we could crawl/index). This one
|
||||
does not require full persistent storage on disk. The caching strategy is an
|
||||
LRU cache which is probably a good starting point. It might be worth it to
|
||||
investigate other cache policies, but I think LRU will work well since
|
||||
documents may or may not expire anyway, and the amount of memory allocated for
|
||||
keys is fairly large, so eviction of frequently used documents seems unlikely
|
||||
anyway.
|
||||
|
||||
### Nginx + Angular
|
||||
There is a Dockerfile included for generating the container image with Nginx
|
||||
(using the default package) and adding all of the supporting compiled angular
|
||||
files. Any modifications to the code-base should be compatible with this setup,
|
||||
so all that's needed is to rebuild the container image, and possibly modify
|
||||
the image tags in the k8s file.
|
||||
|
||||
### Supporting Go binaries
|
||||
There are a few go binaries that each have their own Dockerfile to build
|
||||
containers in which to run them on k8s, namely the crawler and the search
|
||||
service. Their configurations are not optimal (read: needs to be cleaned up),
|
||||
but they are functional.
|
||||
|
||||
## Technical details
|
||||
|
||||
### Overall design and imlpementation
|
||||
|
||||
There are a few components that are all running together in order to get
|
||||
the overall application to work smoothly. This section will provide a brief
|
||||
overview of each component with the following sections going into more details.
|
||||
|
||||
The overall structure is outlined in the following figure:
|
||||

|
||||
|
||||
#### Crawler
|
||||
The leftmost component consists of a crawler with an http cache of GitHub
|
||||
queries does two things, it first looks at the list of documents in
|
||||
elasticsearch and tries to update them. In doing so, it maintains a set of
|
||||
newly updated files to exclude them from other parts of the crawl.
|
||||
|
||||
To find newly added documents, the crawler crawls any new dependencies
|
||||
introduced in the document updating step and it also queries GitHub for the
|
||||
most recently indexed kustomization.\* files. Each new file will be processed
|
||||
for efficient text queries and put into the document index. Any new dependency
|
||||
will also incur more crawl operations. Finally, a graphical
|
||||
representation of the documents and their dependencies is built in Redis to be
|
||||
used for graph algorithms such as PageRank and component analysis.
|
||||
|
||||
#### Data library
|
||||
There are a few helper libaries for dealing with Elasticsearch, Redis and
|
||||
documents. This is not persistent, nor is it centralized. They act as small
|
||||
components that help to package common pieces of code. Eventually it may make
|
||||
sense to merge all of it together and make a proper persistent model around
|
||||
this while providing an external API for document insertion/deletion. But
|
||||
that is definitely out of scope in terms of getting this to run. However
|
||||
there are limitations with the current model in terms of minimizing the
|
||||
API surface for the different components of the application. For now this
|
||||
problem is mostly mitigated by having the query server only connected to
|
||||
a data node of the Elasticsearch cluster, but the problem of knowing what
|
||||
is accessible and what isn't is left to the programmer instead of being
|
||||
clearly and explicitly supported by the API.
|
||||
|
||||
#### Server
|
||||
Uses the data library to communicate with the data store and answer queries.
|
||||
Processes the user entered text queries into somewhat optimized elasticsearch
|
||||
queries. Provides a few endpoints to get different metrics and to eventually
|
||||
allow for registration of remote repositories.
|
||||
|
||||
This application has an exposing service in order to allow users of the
|
||||
application access to queries and the results.
|
||||
|
||||
#### Nginx + Angular
|
||||
Communicates directly with the backend server to forward user queries and
|
||||
their results. Presents the results on an interface. It's still pretty simple
|
||||
looking but it seems usable (to me).
|
||||
|
||||
|
||||
### Crawling GitHub
|
||||
With the use of API keys, GitHub allows account owners to search for files
|
||||
using their API.
|
||||
|
||||
The search endpoints allow for the use of metadata search
|
||||
that is fairly useful/powerful. For instance they provide a `filename:` keyword
|
||||
that permits us to look for `kustomization.yaml`, `kustomization.yml`, etc.
|
||||
This enables the fetching of a list of kustomization documents, from which
|
||||
we can get the actual content from another endpoint
|
||||
(raw.githubusercontent.com).
|
||||
|
||||
However, the search API is fairly limited. There is a restriction to the number
|
||||
of documents that can be retrieved from this method. One possible way to
|
||||
mitigate this would be to periodically query GitHub for results, sorted by the
|
||||
last indexed time. This would allow you to collect most documents from this
|
||||
point forwards. The downside to this is that it may require a large number of
|
||||
requests to their API since you cannot know when new files will be added.
|
||||
Furthermore, there is a possibility that you would not be able to get all of
|
||||
files either, depending on the velocity of growth.
|
||||
|
||||
The approach that was taken to mitigate this is to use the `filesize:` keyword
|
||||
and to shard the search space into contiguous buckets of appropriate size in
|
||||
order to get all of the documents. This is fairly efficient, since you can find
|
||||
a good enough way to shard the documents in
|
||||
`lg(max file size) * number of documents / 1000` API queries. Moreover, since
|
||||
queries are paginated with at most 100 results per query, this solution is
|
||||
competitive with getting the optimal (non-contiguous) sharding of result sets.
|
||||
Furthermore, filesize queries can be cached to minimize the total number of
|
||||
queries called to the API in order to shard the search space. This is done by
|
||||
querying for file size intervals that always start with 0..X and binary
|
||||
searching over the `filesize:` space. This will allow you to reuse a lot of
|
||||
queries when you're looking for the next range, since it is upper bounded and
|
||||
lower bounded to a smaller number of queries within a range that has also been
|
||||
queried. I think this is only true because filesizes are power law distributed,
|
||||
so searches will typically require less queries as they progress from left to
|
||||
right.
|
||||
|
||||
However, this method in no way depends on intervals of the form 0..X, as
|
||||
the number of documents in the many intervals of the range search could be
|
||||
added together to also make this work. This approach just seemed simpler to
|
||||
implement, maintain, and debug so it was preferred.
|
||||
|
||||
To get an idea of how efficient this method is, to shard the search space of
|
||||
7000 documents, it will only take ~90 API range queries which should only take
|
||||
a few minutes. While actually fetching the documents and their relevant
|
||||
metadata (creation time, etc.) will take several hours. Furthermore, this
|
||||
could be made more efficient if a prior distribution is approximated.
|
||||
This prior could be scaled to the number of documents that need to be fetched,
|
||||
and then finding a shard that has an adequate number of requests, will only
|
||||
take a few queries per shard. It could probably be supported in a constant
|
||||
number of size queries if the size of each shard is halved which shouldn't
|
||||
have terrible performance impact for the retrieval. However, there where
|
||||
more pressing things to implement. I might revisit this later.
|
||||
|
||||
### Document Indexing and Processing
|
||||
In order to support simple text queries the structured documents must be
|
||||
processed in some way that makes searching them easy. The current method
|
||||
is to recursively traverse the map of configurations to generate each sub-path
|
||||
and each key-value pair for the leaf nodes of the recursion tree.
|
||||
|
||||
However, note that this means that a document has to be valid yaml/json
|
||||
format in order for indexing to happen. The rest of the document is treated
|
||||
as mostly text and uses default text settings from Elasticsearch.
|
||||
|
||||
What this means is that for the following yaml document:
|
||||
|
||||
```yaml
|
||||
resources:
|
||||
- service.yaml
|
||||
- deployment.yaml
|
||||
|
||||
configmapGenerator:
|
||||
- name: app-configuration
|
||||
files:
|
||||
- config.yaml
|
||||
|
||||
patchesJson6902:
|
||||
- target:
|
||||
version: v1
|
||||
kind: StatefulSet
|
||||
name: ss-name
|
||||
path: ss-patch.yaml
|
||||
- target:
|
||||
version: v1
|
||||
kind: Deployment
|
||||
name: dep-name
|
||||
path: dep-patch.yaml
|
||||
```
|
||||
|
||||
the following flattened structure would look like:
|
||||
```
|
||||
{
|
||||
"identifiers": [
|
||||
"resources",
|
||||
"configmapGenerator",
|
||||
"configmapGenerator:name",
|
||||
"configmapGenerator:files",
|
||||
"patchesJson6902",
|
||||
"patchesJson6902:target",
|
||||
"patchesJson6902:target:version",
|
||||
"patchesJson6902:target:kind",
|
||||
"patchesJson6902:target:name",
|
||||
"patchesJson6902:path",
|
||||
],
|
||||
"values": [
|
||||
"resources=service.yaml",
|
||||
"resources=deployment.yaml",
|
||||
"configmapGenerator:name=app-configuration",
|
||||
"configmapGenerator:files=config.yaml",
|
||||
"patchesJson6902:target:version=v1",
|
||||
"patchesJson6902:target:kind=StatefulSet",
|
||||
"patchesJson6902:target:name=ss-name",
|
||||
"patchesJson6902:path=ss-patch.yaml",
|
||||
"patchesJson6902:target:kind=Deployment",
|
||||
"patchesJson6902:target:name=dep-name",
|
||||
"patchesJson6902:path=dep-patch.yaml",
|
||||
],
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Note that unique paths and values are deduplicated.
|
||||
|
||||
On the search side, exact queries will be prioritized, but the document paths
|
||||
and key=value pairs will also be analyzed with 3-grams to have some amount of
|
||||
fuzzy search. The reason that a Levenshtein-Distance was not used instead, is due
|
||||
to searching multiple fields at the same time, which is a use case where
|
||||
Elasticsearch does not support proper fuzzy searching.
|
||||
|
||||
### Document Search
|
||||
Given a text query, each token is considered separately. Each token will be fed
|
||||
through a handful of analyzers on the Elasticsearch side, and will be compared
|
||||
with the reverse document index of each document fields. It will then determine
|
||||
the best matching documents. Text ordering is largely insignificant. This makes
|
||||
sense for the structured search, but may leave room for improvement for the
|
||||
text only search within the document.
|
||||
|
||||
Each token _must_ be matched, so each white space character acts as a
|
||||
conjunction of individual queries. There are also ways of telling
|
||||
Elasticsearch that some things _should_ match, but I think for now it makes
|
||||
more sense to leave it as is.
|
||||
|
||||
I think this behavior is sufficient to make the search feel fairly intuitive
|
||||
while providing support for fairly complex use cases.
|
||||
|
||||
### Metrics Computation
|
||||
From the each kustomization document that is indexed, we can find it's
|
||||
resources that are publicly available. This includes other kustomizations.
|
||||
From this, we can build a directed graph of dependencies and reverse
|
||||
dependencies.
|
||||
|
||||
This opens up the possibility to add a plethora of graph metrics that can
|
||||
give the project maintainers feedback and insight into how people are using
|
||||
their tools.
|
||||
|
||||
Some of these are useful such as getting an idea for how large the dependency
|
||||
graphs actually grow in practice, and can be used to find _popular_
|
||||
kustomizations within the corpus. This lends itself to implementing PageRank
|
||||
to help bubble up popular results as good search results. I unfortunately
|
||||
did not have the time to implement the algorithm, but I do plan to revisit
|
||||
this sometime soon to add a few good and efficient implementations of useful
|
||||
graph algorithms that would be useful to have. See the Roadmap.md for a more
|
||||
complete list of features that could be added and how I think they could be
|
||||
implemented.
|
||||
176
api/internal/crawl/ROADMAP.md
Normal file
176
api/internal/crawl/ROADMAP.md
Normal file
@@ -0,0 +1,176 @@
|
||||
# Road map and comments about this work
|
||||
|
||||
From working on this project, here is a collection of thoughts and suggestions
|
||||
for future improvements. For any questions about this, or to request help do
|
||||
not hesitate to contact @damienr74 on GitHub, my email should be listed.
|
||||
|
||||
I think this project has the potential for the K8s community to promote best
|
||||
practices. If this becomes popular, It could become easier to find
|
||||
*subjectively good* configurations. This can act as a way to guide newcomers
|
||||
to k8s config features that are easy to maintain, practical, and tested in some
|
||||
real world environment. However, a lot of work remains to be made if this is
|
||||
to happen. Extracting and ranking semantic-level information from the open
|
||||
source configuration files, is definitely not trivial, and will require a lot of
|
||||
though and consideration from the experts and the patterns that successful k8s
|
||||
project follow. This, is outside of my scope having little to no experience with
|
||||
k8s other than working on this project; however, if you have ideas I can
|
||||
probably suggest approaches in order to implement it, having worked a lot on
|
||||
this project.
|
||||
|
||||
### Improving configuration files and container configs
|
||||
I did not have a lot of time to refactor the images to use configmaps for
|
||||
everything. This is a good thing to improve, should be fairly easy. Another
|
||||
thing that could make the user experience of launcing this could be to make all
|
||||
of the go utilities be subcommands to the same binary/container image. This
|
||||
would reduce the number of things that would have to be rebuilt, in order to get
|
||||
it running, and it would make the application (and its components) more self
|
||||
contained. (also has some disadvantages, so I'll let someone else decide.
|
||||
|
||||
### Adding graph metrics
|
||||
From the Redis graph representation, we are able to run a multitude of graph
|
||||
algorithms (not all of which are implemented).
|
||||
|
||||
The simplest one would be to run kruskal's algorithm to find connected
|
||||
components, and to compute graph metrics on each component. Here are some of the
|
||||
metrics that may be useful:
|
||||
|
||||
+ Average size and histograms of the sizes of each components.
|
||||
|
||||
+ Average size and histograms of the node with the highest in degree (rdeps) of
|
||||
each component.
|
||||
|
||||
+ Average size and histograms of the number of repositories in a connected
|
||||
component.
|
||||
|
||||
+ Any other metric that may be helpful to measure the scale of the kustomize
|
||||
import graph.
|
||||
|
||||
Another cool thing that may be helpful, would be to output the graph
|
||||
representation of deps/rdeps. This should be fairly easy to do with graphviz/dot
|
||||
so if anyone really wants this, I (damienr74) should be able to do it. Feel free
|
||||
to send me an email or to @ mention me in an issue.
|
||||
|
||||
Note: dfs could also be used to find connected components, but I think union
|
||||
find is preferable, since the results can be stored and modified very
|
||||
efficiently. The only challenging part would be to implement deleting of edges
|
||||
and nodes from a component efficiently, but I know it is possible to support
|
||||
these operations with a union find structure.
|
||||
|
||||
### Implementing PageRank
|
||||
The graph is set up to be able to efficiently compute PageRank since the edge
|
||||
weights are real valued, and the graph representation is sparse which means that
|
||||
it will fit in the memory of a single machine which will make the processing
|
||||
much more efficient.
|
||||
|
||||
It could also be implemented as a Redis script, but I feel like there's
|
||||
something fundamentally wrong with implementing PageRank in lua. :P
|
||||
|
||||
### Implement feature tracking
|
||||
Each day, when the crawler finds and indexes these structured documents,
|
||||
it should insert aggregate data to a separate index. This data could look like the
|
||||
following:
|
||||
|
||||
```
|
||||
{
|
||||
"kind": "kustomization",
|
||||
"added_identifiers": [
|
||||
{
|
||||
"identifier": "some:new:k8s:feature",
|
||||
"addedIn": [
|
||||
"docID1",
|
||||
"docID100",
|
||||
"docID45",
|
||||
...
|
||||
],
|
||||
},
|
||||
{
|
||||
"identifier": "another:k8s:feature",
|
||||
"documents": [
|
||||
...
|
||||
],
|
||||
},
|
||||
...
|
||||
]
|
||||
|
||||
"removed_identifiers": [
|
||||
{
|
||||
"identifier": "some:deprecated:field",
|
||||
"documents": [
|
||||
...
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
This would make it fairly easy to get deep insight into:
|
||||
- the speed at which things can effectively be deprecated.
|
||||
- how many people are migrating to current best practices.
|
||||
- how many documents get updated frequently/rarely.
|
||||
- detailed cross sections of growth/regression over conjunctions of features.
|
||||
- a world of possibilities.
|
||||
|
||||
This is also something that I would be interested to work on sometime soon, so
|
||||
feel free to contact me (damienr74) or ask questions about this.
|
||||
|
||||
As needed, it could be a good idea to also aggregate past data with a larger
|
||||
granularity. for instance each month, the past 30 days can be aggregated into
|
||||
weekish durations, And every year these weekly aggregations can be converted
|
||||
into monthly summaries depending on how much data this ends up being, and how
|
||||
much you want to pay for the storage of this data.
|
||||
|
||||
Another cool way to compress this data would be to dynamically compress this
|
||||
data into a logarithmic number of buckets with decreasing granularity. But it
|
||||
seems like overkill for the amount of data that we'd likely get.
|
||||
|
||||
### The UI probably needs a lot of work
|
||||
I'm not much of a UI/UX person and have little to no experience in developing
|
||||
these types of applications. If anyone with Angular experience wants to dive in
|
||||
and completely restructure the app to make the UI/UX/Code health better that
|
||||
would be greatly appreciated.
|
||||
|
||||
### Query tuning probably still has to be adjusted
|
||||
I'm also not an expert in Elasticsearch. From what I could read in the docs,
|
||||
I think I've made sane decisions in converting user queries into meaningful
|
||||
Elasticsearch queries, but I'm sure there are a lot of improvements that remain
|
||||
to be done in order to get more accurate results.
|
||||
|
||||
|
||||
### Some other signals that indicate the presence of a good configuration file
|
||||
There are lots of heuristics that could be used to achieve this. Here are a
|
||||
couple in no particular order:
|
||||
|
||||
+ Penalize for the number of yaml `---` document splits. I'm not sure what the
|
||||
general consensus is, but I think it's better to separate them, since it
|
||||
makes git commits less noisy, it's a trivial transformation, and it makes
|
||||
config files smaller. However, I can understand the argument that its somewhat
|
||||
practical to keep an overall view of the configurations together (maybe).
|
||||
|
||||
+ Penalize the number of unique identifiers in a structured document. I think
|
||||
this makes sense, since we don't want to have someone game the search engine
|
||||
to match documents with every possible path from the k8s docs. PageRank might
|
||||
help with this to some extent, but with a small corpus it would be fairly easy
|
||||
to game.
|
||||
|
||||
+ Assign weights to the usefulness of certain fields. It would be good to
|
||||
promote documents that use `keyRefFromConfigMap`, liveness probes, etc.
|
||||
|
||||
These are the main ones I can think of, but I'm sure there are a *ton* of
|
||||
ways to achieve this.
|
||||
|
||||
If the corpus gets large enough, we might even be able to use *blockchains*,
|
||||
*machine learning*, and maybe even self-driving cars.
|
||||
|
||||
### Add more support for indexing of other k8s/kustomize related data
|
||||
One thing that jumps to mind is the use of kustomize plugins. They are easy
|
||||
to track since they all have an unused global variable: `var KustomizePluggin`
|
||||
it would be easy to run the pluginator command and generate godocs for each
|
||||
go file with this unique identifier.
|
||||
|
||||
For the sake of completeness, here is the full GitHub query that we can use to
|
||||
find these:
|
||||
`api.github.com/search/code?q=var+KustomizePlugin+extension%3A.go&access_token=access_token`
|
||||
|
||||
Godoc will not show much, since most packages will be using package main, but
|
||||
using pluginator we can make it a properly named package such that Godoc would
|
||||
actually generate the relevant documentation.
|
||||
195
api/internal/crawl/backend/search_backend.go
Normal file
195
api/internal/crawl/backend/search_backend.go
Normal file
@@ -0,0 +1,195 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/rs/cors"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/internal/crawl/index"
|
||||
)
|
||||
|
||||
type kustomizeSearch struct {
|
||||
ctx context.Context
|
||||
// Eventually pIndex *index.PlugginIndex
|
||||
idx *index.KustomizeIndex
|
||||
router *mux.Router
|
||||
log *log.Logger
|
||||
}
|
||||
|
||||
// New server. Creating a server does not launch it. To launch simply:
|
||||
// srv, _ := NewKustomizeSearch(context.Backgroud())
|
||||
// err := srv.Serve()
|
||||
// if err != nil {
|
||||
// // Handle server issues.
|
||||
// }
|
||||
//
|
||||
// The server has three enpoints, two of which are functional:
|
||||
//
|
||||
// /search: processes the ?q= parameter for a text query and
|
||||
// returns a list of 10 resutls starting from the ?from= value provided,
|
||||
// with the default being zero.
|
||||
//
|
||||
// /metrics: returns overall metrics about the files indexed. Returns
|
||||
// timeseries data for kustomization files, and returns breakdown of file
|
||||
// counts by their 'kind' fields
|
||||
//
|
||||
// /register: not implemented, but meant as an endpoint for adding new
|
||||
// kustomization files to the corpus.
|
||||
func NewKustomizeSearch(ctx context.Context) (*kustomizeSearch, error) {
|
||||
idx, err := index.NewKustomizeIndex(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ks := &kustomizeSearch{
|
||||
ctx: ctx,
|
||||
idx: idx,
|
||||
router: mux.NewRouter(),
|
||||
log: log.New(os.Stdout, "Kustomize server: ",
|
||||
log.LstdFlags|log.Llongfile|log.LUTC),
|
||||
}
|
||||
|
||||
return ks, nil
|
||||
}
|
||||
|
||||
// Set up common middleware and the routes for the server.
|
||||
func (ks *kustomizeSearch) routes() {
|
||||
|
||||
// Setup middleware.
|
||||
ks.router.Use(func(handler http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
handler.ServeHTTP(w, r)
|
||||
})
|
||||
})
|
||||
|
||||
ks.router.HandleFunc("/liveness", ks.liveness()).Methods(http.MethodGet)
|
||||
ks.router.HandleFunc("/readiness", ks.readiness()).Methods(http.MethodGet)
|
||||
ks.router.HandleFunc("/search", ks.search()).Methods(http.MethodGet)
|
||||
ks.router.HandleFunc("/metrics", ks.metrics()).Methods(http.MethodGet)
|
||||
ks.router.HandleFunc("/register", ks.register()).Methods(http.MethodPost)
|
||||
}
|
||||
|
||||
// Start listening and serving on the provided port.
|
||||
func (ks *kustomizeSearch) Serve(port int) error {
|
||||
ks.routes()
|
||||
handler := cors.Default().Handler(ks.router)
|
||||
s := &http.Server{
|
||||
Addr: fmt.Sprintf(":%d", port),
|
||||
Handler: handler,
|
||||
// Timeouts/Limits
|
||||
}
|
||||
|
||||
return s.ListenAndServe()
|
||||
}
|
||||
|
||||
// /liveness endpoint
|
||||
func (ks *kustomizeSearch) liveness() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
}
|
||||
|
||||
// /readyness endpoint
|
||||
func (ks *kustomizeSearch) readiness() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
opt := index.KustomizeSearchOptions{}
|
||||
_, err := ks.idx.Search("", opt)
|
||||
if err != nil {
|
||||
http.Error(w,
|
||||
`{ "error": "could not connect to database" }`,
|
||||
http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
}
|
||||
|
||||
// /register endpoint.
|
||||
func (ks *kustomizeSearch) register() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, "not implemented", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
// /search endpoint.
|
||||
func (ks *kustomizeSearch) search() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
values := r.URL.Query()
|
||||
|
||||
queries := values["q"]
|
||||
ks.log.Println("Query: ", values)
|
||||
|
||||
var from int
|
||||
fromParam := values["from"]
|
||||
if len(fromParam) > 0 {
|
||||
from, _ = strconv.Atoi(fromParam[0])
|
||||
if from < 0 {
|
||||
from = 0
|
||||
}
|
||||
}
|
||||
_, noKinds := values["nokinds"]
|
||||
|
||||
opt := index.KustomizeSearchOptions{
|
||||
SearchOptions: index.SearchOptions{
|
||||
Size: 10,
|
||||
From: from,
|
||||
},
|
||||
KindAggregation: !noKinds,
|
||||
}
|
||||
|
||||
results, err := ks.idx.Search(strings.Join(queries, " "), opt)
|
||||
if err != nil {
|
||||
ks.log.Println("Error: ", err)
|
||||
http.Error(w, fmt.Sprintf(
|
||||
`{ "error": "could not complete the query" }`),
|
||||
http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
enc := json.NewEncoder(w)
|
||||
setIndent(enc)
|
||||
if err = enc.Encode(results); err != nil {
|
||||
http.Error(w, `{ "error": "failed to send back results" }`,
|
||||
http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// metrics endpoint.
|
||||
func (ks *kustomizeSearch) metrics() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
res, err := ks.idx.Search("", index.KustomizeSearchOptions{
|
||||
KindAggregation: true,
|
||||
TimeseriesAggregation: true,
|
||||
})
|
||||
if err != nil {
|
||||
http.Error(w, `{ "error": "could not perform the search."}`,
|
||||
http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
enc := json.NewEncoder(w)
|
||||
setIndent(enc)
|
||||
if err := enc.Encode(res); err != nil {
|
||||
http.Error(w, `{ "error": "could not format return value" }`,
|
||||
http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// make json response human readable.
|
||||
func setIndent(e *json.Encoder) {
|
||||
e.SetIndent("", " ")
|
||||
}
|
||||
14
api/internal/crawl/cmd/backend/Dockerfile
Normal file
14
api/internal/crawl/cmd/backend/Dockerfile
Normal file
@@ -0,0 +1,14 @@
|
||||
FROM golang:1.11 AS build
|
||||
|
||||
ARG GO111MODULE=on
|
||||
|
||||
WORKDIR /go/src/sigs.k8s.io/kustomize/api/internal/crawl
|
||||
COPY . /go/src/sigs.k8s.io/kustomize/api/internal/crawl
|
||||
|
||||
RUN go mod download
|
||||
RUN CGO_ENABLED=0 go install sigs.k8s.io/kustomize/api/internal/crawl/cmd/backend/
|
||||
|
||||
FROM scratch
|
||||
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||
COPY --from=build /go/bin/backend /
|
||||
ENTRYPOINT ["/backend"]
|
||||
29
api/internal/crawl/cmd/backend/main.go
Normal file
29
api/internal/crawl/cmd/backend/main.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"os"
|
||||
server "sigs.k8s.io/kustomize/api/internal/crawl/backend"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func main() {
|
||||
portStr := os.Getenv("PORT")
|
||||
port, err := strconv.Atoi(portStr)
|
||||
if portStr == "" || err != nil {
|
||||
log.Fatalf("$PORT(%s) must be set to an integer\n", portStr)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
ks, err := server.NewKustomizeSearch(ctx)
|
||||
if err != nil {
|
||||
log.Fatalf("Error creating kustomize server: %v", ks)
|
||||
}
|
||||
|
||||
err = ks.Serve(port)
|
||||
if err != nil {
|
||||
log.Fatalf("Error while running server: %v", err)
|
||||
}
|
||||
}
|
||||
15
api/internal/crawl/cmd/crawler/Dockerfile
Normal file
15
api/internal/crawl/cmd/crawler/Dockerfile
Normal file
@@ -0,0 +1,15 @@
|
||||
FROM golang:1.11 AS build
|
||||
|
||||
ARG GO111MODULE=on
|
||||
|
||||
WORKDIR /go/src/sigs.k8s.io/kustomize/api/internal/crawl
|
||||
COPY . /go/src/sigs.k8s.io/kustomize//api/internal/crawl
|
||||
|
||||
RUN go mod download
|
||||
RUN CGO_ENABLED=0 go install -v ./cmd/crawler/crawler.go
|
||||
|
||||
FROM scratch
|
||||
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||
COPY --from=build /go/bin/crawler /
|
||||
ENTRYPOINT ["/crawler"]
|
||||
CMD []
|
||||
122
api/internal/crawl/cmd/crawler/crawler.go
Normal file
122
api/internal/crawl/cmd/crawler/crawler.go
Normal file
@@ -0,0 +1,122 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/internal/crawl/crawler"
|
||||
"sigs.k8s.io/kustomize/api/internal/crawl/crawler/github"
|
||||
"sigs.k8s.io/kustomize/api/internal/crawl/doc"
|
||||
"sigs.k8s.io/kustomize/api/internal/crawl/httpclient"
|
||||
"sigs.k8s.io/kustomize/api/internal/crawl/index"
|
||||
|
||||
"github.com/gomodule/redigo/redis"
|
||||
)
|
||||
|
||||
const (
|
||||
githubAccessTokenVar = "GITHUB_ACCESS_TOKEN"
|
||||
redisCacheURL = "REDIS_CACHE_URL"
|
||||
redisKeyURL = "REDIS_KEY_URL"
|
||||
retryCount = 3
|
||||
githubUserEnv = "GITHUB_USER"
|
||||
githubRepoEnv = "GITHUB_REPO"
|
||||
)
|
||||
|
||||
func main() {
|
||||
githubUser := os.Getenv(githubUserEnv)
|
||||
githubRepo := os.Getenv(githubRepoEnv)
|
||||
|
||||
githubToken := os.Getenv(githubAccessTokenVar)
|
||||
if githubToken == "" {
|
||||
fmt.Printf("Must set the variable '%s' to make github requests.\n",
|
||||
githubAccessTokenVar)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
idx, err := index.NewKustomizeIndex(ctx)
|
||||
if err != nil {
|
||||
fmt.Printf("Could not create an index: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
seedDocs := make(crawler.CrawlSeed, 0)
|
||||
|
||||
cacheURL := os.Getenv(redisCacheURL)
|
||||
cache, err := redis.DialURL(cacheURL)
|
||||
clientCache := &http.Client{}
|
||||
if err != nil {
|
||||
fmt.Printf("Error: redis could not make a connection: %v\n", err)
|
||||
} else {
|
||||
clientCache = httpclient.NewClient(cache)
|
||||
}
|
||||
|
||||
// docConverter takes in a plain document and processes it for the index.
|
||||
docConverter := func(d *doc.Document) (crawler.CrawledDocument, error) {
|
||||
kdoc := doc.KustomizationDocument{
|
||||
Document: *d,
|
||||
}
|
||||
|
||||
err := kdoc.ParseYAML()
|
||||
return &kdoc, err
|
||||
}
|
||||
|
||||
// Index updates the value in the index.
|
||||
index := func(cdoc crawler.CrawledDocument, crwlr crawler.Crawler) error {
|
||||
switch d := cdoc.(type) {
|
||||
case *doc.KustomizationDocument:
|
||||
fmt.Println("Inserting: ", d)
|
||||
_, err := idx.Put(d.ID(), d)
|
||||
return err
|
||||
default:
|
||||
return fmt.Errorf("type %T not supported", d)
|
||||
}
|
||||
}
|
||||
|
||||
// seen tracks the IDs of all the documents in the index.
|
||||
// This helps avoid indexing a given document multiple times.
|
||||
seen := make(map[string]struct{})
|
||||
|
||||
var ghCrawler crawler.Crawler
|
||||
|
||||
if githubRepo != "" {
|
||||
ghCrawler = github.NewCrawler(githubToken, retryCount, clientCache,
|
||||
github.QueryWith(
|
||||
github.Filename("kustomization.yaml"),
|
||||
github.Filename("kustomization.yml"),
|
||||
github.Repo(githubRepo)),
|
||||
)
|
||||
} else if githubUser != "" {
|
||||
ghCrawler = github.NewCrawler(githubToken, retryCount, clientCache,
|
||||
github.QueryWith(
|
||||
github.Filename("kustomization.yaml"),
|
||||
github.Filename("kustomization.yml"),
|
||||
github.User(githubUser)),
|
||||
)
|
||||
} else {
|
||||
ghCrawler = github.NewCrawler(githubToken, retryCount, clientCache,
|
||||
github.QueryWith(
|
||||
github.Filename("kustomization.yaml"),
|
||||
github.Filename("kustomization.yml")),
|
||||
)
|
||||
|
||||
// get all the documents in the index
|
||||
query := []byte(`{ "query":{ "match_all":{} } }`)
|
||||
it := idx.IterateQuery(query, 10000, 60*time.Second)
|
||||
for it.Next() {
|
||||
for _, hit := range it.Value().Hits.Hits {
|
||||
seedDocs = append(seedDocs, hit.Document.Copy())
|
||||
}
|
||||
}
|
||||
if err := it.Err(); err != nil {
|
||||
fmt.Printf("Error iterating: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
crawlers := []crawler.Crawler{ghCrawler}
|
||||
crawler.CrawlFromSeed(ctx, seedDocs, crawlers, docConverter, index, seen)
|
||||
crawler.CrawlGithub(ctx, crawlers, docConverter, index, seen)
|
||||
}
|
||||
6
api/internal/crawl/config/base/kustomization.yaml
Normal file
6
api/internal/crawl/config/base/kustomization.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
configmapGenerator:
|
||||
- name: elasticsearch-config
|
||||
literals:
|
||||
- es-url="http://esbasic-master:9200"
|
||||
- kustomize-index-name="kustomize"
|
||||
- plugin-index-name="plugin"
|
||||
@@ -0,0 +1,2 @@
|
||||
<ADD YOUR GITHUB PERSONAL ACCESS TOKEN HERE WITHOUT A TRAILING NEWLINE>
|
||||
Run: printf "<your-token>" > github_api_secret.txt
|
||||
15
api/internal/crawl/config/crawler/base/kustomization.yaml
Normal file
15
api/internal/crawl/config/crawler/base/kustomization.yaml
Normal file
@@ -0,0 +1,15 @@
|
||||
resources:
|
||||
- ../../base
|
||||
|
||||
configmapGenerator:
|
||||
- name: crawler-http-cache
|
||||
literals:
|
||||
- redis-cache-url="redis://redis-http-cache:6379"
|
||||
- name: redis-keystore
|
||||
literals:
|
||||
- keystore-url="redis://redis-docs-keystore:6379"
|
||||
|
||||
secretGenerator:
|
||||
- name: github-access-token
|
||||
files:
|
||||
- token=github_api_secret.txt
|
||||
31
api/internal/crawl/config/crawler/cronjob/cronjob.yaml
Normal file
31
api/internal/crawl/config/crawler/cronjob/cronjob.yaml
Normal file
@@ -0,0 +1,31 @@
|
||||
apiVersion: batch/v1beta1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: crawler
|
||||
spec:
|
||||
schedule: "5 0 * * */1"
|
||||
jobTemplate:
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
restartPolicy: OnFailure
|
||||
containers:
|
||||
- name: crawler
|
||||
image: gcr.io/kustomize-search/crawler:latest
|
||||
imagePullPolicy: Always
|
||||
env:
|
||||
- name: GITHUB_ACCESS_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: github-access-token
|
||||
key: token
|
||||
- name: ELASTICSEARCH_URL
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: elasticsearch-config
|
||||
key: es-url
|
||||
- name: REDIS_URL
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: crawler-http-cache
|
||||
key: redis-cache-url
|
||||
@@ -0,0 +1,3 @@
|
||||
resources:
|
||||
- ../base
|
||||
- cronjob.yaml
|
||||
33
api/internal/crawl/config/crawler/job/job.yaml
Normal file
33
api/internal/crawl/config/crawler/job/job.yaml
Normal file
@@ -0,0 +1,33 @@
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: crawler
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
restartPolicy: OnFailure
|
||||
containers:
|
||||
- name: crawler
|
||||
image: gcr.io/kustomize-search/crawler:latest
|
||||
imagePullPolicy: Always
|
||||
env:
|
||||
- name: GITHUB_ACCESS_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: github-access-token
|
||||
key: token
|
||||
- name: ELASTICSEARCH_URL
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: elasticsearch-config
|
||||
key: es-url
|
||||
- name: REDIS_CACHE_URL
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: crawler-http-cache
|
||||
key: redis-cache-url
|
||||
- name: REDIS_KEY_URL
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: redis-keystore
|
||||
key: keystore-url
|
||||
3
api/internal/crawl/config/crawler/job/kustomization.yaml
Normal file
3
api/internal/crawl/config/crawler/job/kustomization.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
resources:
|
||||
- ../base
|
||||
- job.yaml
|
||||
43
api/internal/crawl/config/elastic/escluster.yaml
Normal file
43
api/internal/crawl/config/elastic/escluster.yaml
Normal file
@@ -0,0 +1,43 @@
|
||||
apiVersion: elasticsearch.cloud.google.com/v1alpha1
|
||||
kind: ESCluster
|
||||
metadata:
|
||||
name: esbasic
|
||||
spec:
|
||||
plugin:
|
||||
pluginList:
|
||||
- repository-gcs
|
||||
- ingest-user-agent
|
||||
- ingest-geoip
|
||||
config:
|
||||
env:
|
||||
example: test
|
||||
nodegroups:
|
||||
- name: di
|
||||
replicas: 2
|
||||
data: true
|
||||
ingest: true
|
||||
config:
|
||||
jvm:
|
||||
- Djava.net.preferIPv4Stack=true
|
||||
- Xms2g
|
||||
- Xmx2g
|
||||
es:
|
||||
path.repo: '["/tmp/es_backup_basic"]'
|
||||
affinity:
|
||||
podAntiAffinity:
|
||||
requiredDuringSchedulingIgnoredDuringExecution:
|
||||
- topologyKey: kubernetes.io/hostname
|
||||
labelSelector:
|
||||
matchLabels:
|
||||
es/nodegroup: di
|
||||
resources:
|
||||
requests:
|
||||
memory: 3Gi
|
||||
limits:
|
||||
memory: 3Gi
|
||||
- name: m
|
||||
replicas: 2
|
||||
master: true
|
||||
config:
|
||||
es:
|
||||
path.repo: '["/tmp/es_backup_basic"]'
|
||||
16
api/internal/crawl/config/elastic/service.yaml
Normal file
16
api/internal/crawl/config/elastic/service.yaml
Normal file
@@ -0,0 +1,16 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: elasticsearch
|
||||
spec:
|
||||
selector:
|
||||
custom-resource: v1alpha1.ESCluster
|
||||
custom-resource-name: esbasic
|
||||
custom-resource-namespace: default
|
||||
es/data: "true"
|
||||
using: escluster.Cluster
|
||||
ports:
|
||||
- protocol: "TCP"
|
||||
port: 9200
|
||||
type: LoadBalancer
|
||||
loadBalancerIP: ""
|
||||
@@ -0,0 +1,7 @@
|
||||
resources:
|
||||
- redis.yaml
|
||||
- service.yaml
|
||||
|
||||
commonLabels:
|
||||
app: redis
|
||||
tier: document-keystore
|
||||
37
api/internal/crawl/config/redis/document_keystore/redis.yaml
Normal file
37
api/internal/crawl/config/redis/document_keystore/redis.yaml
Normal file
@@ -0,0 +1,37 @@
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: redis-docs-keystore
|
||||
spec:
|
||||
serviceName: "redis-docs-keystore"
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: redis
|
||||
image: redis:5-alpine
|
||||
imagePullPolicy: Always
|
||||
args:
|
||||
- "--save"
|
||||
- "900"
|
||||
- "1"
|
||||
- "--save"
|
||||
- "30"
|
||||
- "100"
|
||||
- "--appendonly"
|
||||
- "yes"
|
||||
ports:
|
||||
- name: redis-docs-port
|
||||
containerPort: 6379
|
||||
volumeMounts:
|
||||
- mountPath: /data
|
||||
name: redis-docs-keystore-data
|
||||
restartPolicy: Always
|
||||
volumeClaimTemplates:
|
||||
- metadata:
|
||||
name: redis-docs-keystore-data
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 4Gi
|
||||
@@ -0,0 +1,10 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: redis-docs-keystore
|
||||
spec:
|
||||
clusterIP: None
|
||||
ports:
|
||||
- protocol: "TCP"
|
||||
port: 6379
|
||||
targetPort: redis-docs-port
|
||||
@@ -0,0 +1,7 @@
|
||||
resources:
|
||||
- redis.yaml
|
||||
- service.yaml
|
||||
|
||||
commonLabels:
|
||||
app: redis
|
||||
tier: http-cache
|
||||
16
api/internal/crawl/config/redis/http_cache/redis.yaml
Normal file
16
api/internal/crawl/config/redis/http_cache/redis.yaml
Normal file
@@ -0,0 +1,16 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: redis-http-cache
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: redis
|
||||
image: redis:5-alpine
|
||||
imagePullPolicy: Always
|
||||
# see redis.io/topics/lru-cache for other policy options.
|
||||
args: ["--maxmemory", "1gb", "--maxmemory-policy", "allkeys-lru"]
|
||||
ports:
|
||||
- name: http-cache-port
|
||||
containerPort: 6379
|
||||
10
api/internal/crawl/config/redis/http_cache/service.yaml
Normal file
10
api/internal/crawl/config/redis/http_cache/service.yaml
Normal file
@@ -0,0 +1,10 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: redis-http-cache
|
||||
spec:
|
||||
clusterIP: None
|
||||
ports:
|
||||
- protocol: "TCP"
|
||||
port: 6379
|
||||
targetPort: http-cache-port
|
||||
35
api/internal/crawl/config/webapp/backend/deployment.yaml
Normal file
35
api/internal/crawl/config/webapp/backend/deployment.yaml
Normal file
@@ -0,0 +1,35 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: kustomize-search
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: kustomize-search
|
||||
tier: backend
|
||||
replicas: 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: kustomize-search
|
||||
tier: backend
|
||||
spec:
|
||||
containers:
|
||||
- name: kustomize-search
|
||||
image: gcr.io/kustomize-search/backend:latest
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /liveness
|
||||
port: backend-port
|
||||
ports:
|
||||
- name: backend-port
|
||||
containerPort: 8080
|
||||
env:
|
||||
- name: ELASTICSEARCH_URL
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: elasticsearch-config
|
||||
key: es-url
|
||||
- name: PORT
|
||||
value: "8080"
|
||||
@@ -0,0 +1,4 @@
|
||||
resources:
|
||||
- ../../base
|
||||
- deployment.yaml
|
||||
- service.yaml
|
||||
14
api/internal/crawl/config/webapp/backend/service.yaml
Normal file
14
api/internal/crawl/config/webapp/backend/service.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: kustomize-search
|
||||
spec:
|
||||
selector:
|
||||
app: kustomize-search
|
||||
tier: backend
|
||||
ports:
|
||||
- protocol: "TCP"
|
||||
port: 80
|
||||
targetPort: backend-port
|
||||
type: LoadBalancer
|
||||
loadBalancerIP: ""
|
||||
23
api/internal/crawl/config/webapp/frontend/deployment.yaml
Normal file
23
api/internal/crawl/config/webapp/frontend/deployment.yaml
Normal file
@@ -0,0 +1,23 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: kustomize-search-ui
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: kustomize-search
|
||||
tier: frontend
|
||||
replicas: 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: kustomize-search
|
||||
tier: frontend
|
||||
spec:
|
||||
containers:
|
||||
- name: frontend
|
||||
image: gcr.io/kustomize-search/frontend:latest
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- name: frontend-port
|
||||
containerPort: 80
|
||||
@@ -0,0 +1,4 @@
|
||||
resources:
|
||||
- ../../base
|
||||
- deployment.yaml
|
||||
- service.yaml
|
||||
14
api/internal/crawl/config/webapp/frontend/service.yaml
Normal file
14
api/internal/crawl/config/webapp/frontend/service.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: kustomize-search-ui
|
||||
spec:
|
||||
selector:
|
||||
app: kustomize-search
|
||||
tier: frontend
|
||||
ports:
|
||||
- protocol: "TCP"
|
||||
port: 80
|
||||
targetPort: frontend-port
|
||||
type: LoadBalancer
|
||||
loadBalancerIP: ""
|
||||
262
api/internal/crawl/crawler/crawler.go
Normal file
262
api/internal/crawl/crawler/crawler.go
Normal file
@@ -0,0 +1,262 @@
|
||||
// Package crawler provides helper methods and defines an interface for lauching
|
||||
// source repository crawlers that retrieve files from a source and forwards
|
||||
// to a channel for indexing and retrieval.
|
||||
package crawler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
_ "github.com/gomodule/redigo/redis"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/internal/crawl/doc"
|
||||
)
|
||||
|
||||
var (
|
||||
logger = log.New(os.Stdout, "Crawler: ", log.LstdFlags|log.LUTC|log.Llongfile)
|
||||
)
|
||||
|
||||
// Crawler forwards documents from source repositories to index and store them
|
||||
// for searching. Each crawler is responsible for querying it's source of
|
||||
// information, and forwarding files that have not been seen before or that need
|
||||
// updating.
|
||||
type Crawler interface {
|
||||
// Crawl returns when it is done processing. This method does not take
|
||||
// ownership of the channel. The channel is write only, and it
|
||||
// designates where the crawler should forward the documents.
|
||||
Crawl(ctx context.Context, output chan<- CrawledDocument) error
|
||||
|
||||
// Get the document data given the FilePath, Repo, and Ref/Tag/Branch.
|
||||
FetchDocument(context.Context, *doc.Document) error
|
||||
// Write to the document what the created time is.
|
||||
SetCreated(context.Context, *doc.Document) error
|
||||
|
||||
Match(*doc.Document) bool
|
||||
}
|
||||
|
||||
type CrawledDocument interface {
|
||||
ID() string
|
||||
GetDocument() *doc.Document
|
||||
// Get all the Documents directly referred in a Document.
|
||||
GetResources() ([]*doc.Document, error)
|
||||
WasCached() bool
|
||||
}
|
||||
|
||||
type CrawlSeed []*doc.Document
|
||||
|
||||
type IndexFunc func(CrawledDocument, Crawler) error
|
||||
type Converter func(*doc.Document) (CrawledDocument, error)
|
||||
|
||||
func logIfErr(err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
logger.Println("error: ", err)
|
||||
}
|
||||
|
||||
func findMatch(d *doc.Document, crawlers []Crawler) Crawler {
|
||||
for _, crawl := range crawlers {
|
||||
if crawl.Match(d) {
|
||||
return crawl
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func addBranches(cdoc CrawledDocument, match Crawler, indx IndexFunc,
|
||||
seen map[string]struct{}, stack *CrawlSeed) {
|
||||
|
||||
seen[cdoc.ID()] = struct{}{}
|
||||
|
||||
// Insert into index
|
||||
err := indx(cdoc, match)
|
||||
logIfErr(err)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
deps, err := cdoc.GetResources()
|
||||
logIfErr(err)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, dep := range deps {
|
||||
if _, ok := seen[dep.ID()]; ok {
|
||||
continue
|
||||
}
|
||||
*stack = append(*stack, dep)
|
||||
}
|
||||
}
|
||||
|
||||
func doCrawl(ctx context.Context, docsPtr *CrawlSeed, crawlers []Crawler, conv Converter, indx IndexFunc,
|
||||
seen map[string]struct{}, stack *CrawlSeed) {
|
||||
docCount := 0
|
||||
// During the execution of the for loop, more Documents may be added into (*docsPtr).
|
||||
for len(*docsPtr) > 0 {
|
||||
// get the last Document in (*docPtr), which will be crawled in this iteration.
|
||||
tail := (*docsPtr)[len(*docsPtr)-1]
|
||||
|
||||
// remove the last Document in (*docPtr)
|
||||
*docsPtr = (*docsPtr)[:(len(*docsPtr) - 1)]
|
||||
|
||||
if _, ok := seen[tail.ID()]; ok {
|
||||
continue
|
||||
}
|
||||
docCount++
|
||||
|
||||
match := findMatch(tail, crawlers)
|
||||
if match == nil {
|
||||
logIfErr(fmt.Errorf(
|
||||
"%v could not match any crawler", tail))
|
||||
continue
|
||||
}
|
||||
|
||||
logger.Println("Crawling ", tail.RepositoryURL, tail.FilePath)
|
||||
err := match.FetchDocument(ctx, tail)
|
||||
logIfErr(err)
|
||||
// If there was no change or there is an error, we don't have
|
||||
// to branch out, since the dependencies are already in the
|
||||
// index, or we cannot find the document.
|
||||
if err != nil || tail.WasCached() {
|
||||
if tail.WasCached() {
|
||||
logger.Println(tail.RepositoryURL, tail.FilePath, "is cached already")
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
logIfErr(match.SetCreated(ctx, tail))
|
||||
|
||||
cdoc, err := conv(tail)
|
||||
logIfErr(err)
|
||||
|
||||
addBranches(cdoc, match, indx, seen, stack)
|
||||
}
|
||||
logger.Printf("%d documents were crawled by doCrawl\n", docCount)
|
||||
}
|
||||
|
||||
// CrawlFromSeed updates all the documents in seed, and crawls all the new
|
||||
// documents referred in the seed.
|
||||
func CrawlFromSeed(ctx context.Context, seed CrawlSeed, crawlers []Crawler,
|
||||
conv Converter, indx IndexFunc, seen map[string]struct{}) {
|
||||
|
||||
// stack tracks the documents directly referred in other documents.
|
||||
stack := make(CrawlSeed, 0)
|
||||
|
||||
// Exploit seed to update bulk of corpus.
|
||||
logger.Printf("updating %d documents from seed\n", len(seed))
|
||||
// each unique document in seed will be crawled once.
|
||||
doCrawl(ctx, &seed, crawlers, conv, indx, seen, &stack)
|
||||
|
||||
// Traverse any new documents added while updating corpus.
|
||||
logger.Printf("crawling %d new documents found in the seed\n", len(stack))
|
||||
// While crawling each document in stack, the documents directly referred in the document
|
||||
// will be added into stack.
|
||||
// After this statement is done, stack will become empty.
|
||||
doCrawl(ctx, &stack, crawlers, conv, indx, seen, &stack)
|
||||
}
|
||||
|
||||
// CrawlGithubRunner is a blocking function and only returns once all of the
|
||||
// crawlers are finished with execution.
|
||||
//
|
||||
// This function uses the output channel to forward kustomization documents
|
||||
// from a list of crawlers. The output is to be consumed by a database/search
|
||||
// indexer for later retrieval.
|
||||
//
|
||||
// The return value is an array of errors in which each index represents the
|
||||
// index of the crawler that emitted the error. Although the errors themselves
|
||||
// can be nil, the array will always be exactly the size of the crawlers array.
|
||||
//
|
||||
// CrawlGithubRunner takes in a seed, which represents the documents stored in an
|
||||
// index somewhere. The document data is not required to be populated. If there
|
||||
// are many documents, this is preferable. The order of iteration over the seed
|
||||
// is not guaranteed, but the CrawlGithub does guarantee that every element
|
||||
// from the seed will be processed before any other documents from the
|
||||
// crawlers.
|
||||
func CrawlGithubRunner(ctx context.Context, output chan<- CrawledDocument,
|
||||
crawlers []Crawler) []error {
|
||||
|
||||
errs := make([]error, len(crawlers))
|
||||
wg := sync.WaitGroup{}
|
||||
|
||||
for i, crawler := range crawlers {
|
||||
// Crawler implementations get their own channels to prevent a
|
||||
// crawler from closing the main output channel.
|
||||
docs := make(chan CrawledDocument)
|
||||
wg.Add(2)
|
||||
|
||||
// Forward all of the documents from this crawler's channel to
|
||||
// the main output channel.
|
||||
go func(docs <-chan CrawledDocument) {
|
||||
defer wg.Done()
|
||||
for d := range docs {
|
||||
output <- d
|
||||
}
|
||||
}(docs)
|
||||
|
||||
// Run this crawler and capture its returned error.
|
||||
go func(idx int, crawler Crawler,
|
||||
docs chan<- CrawledDocument) {
|
||||
|
||||
defer func() {
|
||||
wg.Done()
|
||||
if r := recover(); r != nil {
|
||||
errs[idx] = fmt.Errorf(
|
||||
"%+v panicked: %v, additional error %v",
|
||||
crawler, r, errs[idx],
|
||||
)
|
||||
}
|
||||
}()
|
||||
defer close(docs)
|
||||
errs[idx] = crawler.Crawl(ctx, docs)
|
||||
}(i, crawler, docs) // Copies the index and the crawler
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
return errs
|
||||
}
|
||||
|
||||
// CrawlGithub crawls all the kustomization files on Github.
|
||||
func CrawlGithub(ctx context.Context, crawlers []Crawler, conv Converter,
|
||||
indx IndexFunc, seen map[string]struct{}) {
|
||||
// stack tracks the documents directly referred in other documents.
|
||||
stack := make(CrawlSeed, 0)
|
||||
|
||||
// ch is channel where all the crawlers sends the crawled documents to.
|
||||
ch := make(chan CrawledDocument, 1<<10)
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for cdoc := range ch {
|
||||
if _, ok := seen[cdoc.ID()]; ok {
|
||||
continue
|
||||
}
|
||||
match := findMatch(cdoc.GetDocument(), crawlers)
|
||||
if match == nil {
|
||||
logIfErr(fmt.Errorf(
|
||||
"%v could not match any crawler", cdoc))
|
||||
continue
|
||||
}
|
||||
addBranches(cdoc, match, indx, seen, &stack)
|
||||
}
|
||||
}()
|
||||
|
||||
logger.Println("processing the documents found from crawling github")
|
||||
if errs := CrawlGithubRunner(ctx, ch, crawlers); errs != nil {
|
||||
for _, err := range errs {
|
||||
logIfErr(err)
|
||||
}
|
||||
}
|
||||
close(ch)
|
||||
wg.Wait()
|
||||
|
||||
// Handle deps of newly discovered documents.
|
||||
logger.Printf("crawling the %d new documents referred by other documents",
|
||||
len(stack))
|
||||
doCrawl(ctx, &stack, crawlers, conv, indx, seen, &stack)
|
||||
}
|
||||
335
api/internal/crawl/crawler/crawler_test.go
Normal file
335
api/internal/crawl/crawler/crawler_test.go
Normal file
@@ -0,0 +1,335 @@
|
||||
package crawler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/internal/crawl/doc"
|
||||
"sigs.k8s.io/kustomize/api/konfig"
|
||||
)
|
||||
|
||||
const (
|
||||
kustomizeRepo = "https://github.com/kubernetes-sigs/kustomize"
|
||||
)
|
||||
|
||||
// Simple crawler that forwards it's list of documents to a provided channel and
|
||||
// returns it's error to the caller.
|
||||
type testCrawler struct {
|
||||
matchPrefix string
|
||||
err error
|
||||
docs []doc.KustomizationDocument
|
||||
lukp map[string]int
|
||||
}
|
||||
|
||||
func (c testCrawler) Match(d *doc.Document) bool {
|
||||
return d != nil
|
||||
}
|
||||
|
||||
func (c testCrawler) FetchDocument(_ context.Context, d *doc.Document) error {
|
||||
if i, ok := c.lukp[d.ID()]; ok {
|
||||
d.DocumentData = c.docs[i].DocumentData
|
||||
return nil
|
||||
}
|
||||
for _, suffix := range konfig.RecognizedKustomizationFileNames() {
|
||||
savedFilePath := d.FilePath
|
||||
d.FilePath += "/" + suffix
|
||||
i, ok := c.lukp[d.ID()]
|
||||
if !ok {
|
||||
d.FilePath = savedFilePath
|
||||
continue
|
||||
}
|
||||
d.DocumentData = c.docs[i].DocumentData
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("document %v does not exist for matcher: %s",
|
||||
d, c.matchPrefix)
|
||||
}
|
||||
|
||||
func (c testCrawler) SetCreated(_ context.Context, d *doc.Document) error {
|
||||
d.CreationTime = &time.Time{}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newCrawler(matchPrefix string, err error,
|
||||
docs []doc.KustomizationDocument) testCrawler {
|
||||
c := testCrawler{
|
||||
matchPrefix: matchPrefix,
|
||||
err: err,
|
||||
docs: docs,
|
||||
lukp: make(map[string]int),
|
||||
}
|
||||
for i, d := range docs {
|
||||
c.lukp[d.ID()] = i
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// Crawl implements the Crawler interface for testing.
|
||||
func (c testCrawler) Crawl(_ context.Context,
|
||||
output chan<- CrawledDocument) error {
|
||||
|
||||
for i, d := range c.docs {
|
||||
isResource := true
|
||||
for _, suffix := range konfig.RecognizedKustomizationFileNames() {
|
||||
if strings.HasSuffix(d.FilePath, suffix) {
|
||||
isResource = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if isResource {
|
||||
continue
|
||||
}
|
||||
output <- &c.docs[i]
|
||||
}
|
||||
return c.err
|
||||
}
|
||||
|
||||
// Used to make sure that we're comparing documents in order. This is needed
|
||||
// since these documents will be sent concurrently.
|
||||
type sortableDocs []doc.KustomizationDocument
|
||||
|
||||
func (s sortableDocs) Less(i, j int) bool {
|
||||
return s[i].FilePath < s[j].FilePath
|
||||
}
|
||||
|
||||
func (s sortableDocs) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
|
||||
func (s sortableDocs) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
func TestCrawlGithubRunner(t *testing.T) {
|
||||
fmt.Println("testing CrawlGithubRunner")
|
||||
tests := []struct {
|
||||
tc []Crawler
|
||||
errs []error
|
||||
docs sortableDocs
|
||||
}{
|
||||
{
|
||||
tc: []Crawler{
|
||||
testCrawler{
|
||||
docs: []doc.KustomizationDocument{
|
||||
{Document: doc.Document{
|
||||
FilePath: "crawler1/doc1/kustomization.yaml",
|
||||
}},
|
||||
{Document: doc.Document{
|
||||
FilePath: "crawler1/doc2/kustomization.yaml",
|
||||
}},
|
||||
{Document: doc.Document{
|
||||
FilePath: "crawler1/doc3/kustomization.yaml",
|
||||
}},
|
||||
},
|
||||
},
|
||||
testCrawler{err: errors.New("crawler2")},
|
||||
testCrawler{},
|
||||
testCrawler{
|
||||
docs: []doc.KustomizationDocument{
|
||||
{Document: doc.Document{
|
||||
FilePath: "crawler4/doc1/kustomization.yaml",
|
||||
}},
|
||||
{Document: doc.Document{
|
||||
FilePath: "crawler4/doc2/kustomization.yaml",
|
||||
}},
|
||||
},
|
||||
err: errors.New("crawler4"),
|
||||
},
|
||||
},
|
||||
errs: []error{
|
||||
nil,
|
||||
errors.New("crawler2"),
|
||||
nil,
|
||||
errors.New("crawler4"),
|
||||
},
|
||||
docs: sortableDocs{
|
||||
{Document: doc.Document{
|
||||
FilePath: "crawler1/doc1/kustomization.yaml",
|
||||
}},
|
||||
{Document: doc.Document{
|
||||
FilePath: "crawler1/doc2/kustomization.yaml",
|
||||
}},
|
||||
{Document: doc.Document{
|
||||
FilePath: "crawler1/doc3/kustomization.yaml",
|
||||
}},
|
||||
{Document: doc.Document{
|
||||
FilePath: "crawler4/doc1/kustomization.yaml",
|
||||
}},
|
||||
{Document: doc.Document{
|
||||
FilePath: "crawler4/doc2/kustomization.yaml",
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
output := make(chan CrawledDocument)
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
|
||||
// Run the Crawler runner with a list of crawlers.
|
||||
go func() {
|
||||
defer close(output)
|
||||
defer wg.Done()
|
||||
|
||||
errs := CrawlGithubRunner(context.Background(),
|
||||
output, test.tc)
|
||||
|
||||
// Check that errors are returned as they should be.
|
||||
if !reflect.DeepEqual(errs, test.errs) {
|
||||
t.Errorf("Expected errs (%v) to equal (%v)",
|
||||
errs, test.errs)
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
// Iterate over the output channel of Crawler runner.
|
||||
returned := make(sortableDocs, 0, len(test.docs))
|
||||
for o := range output {
|
||||
d, ok := o.(*doc.KustomizationDocument)
|
||||
if !ok || d == nil {
|
||||
t.Errorf("%T not expected type (%T)",
|
||||
o, d)
|
||||
}
|
||||
returned = append(returned, *d)
|
||||
}
|
||||
|
||||
// Check that all documents are received.
|
||||
sort.Sort(returned)
|
||||
if !reflect.DeepEqual(returned, test.docs) {
|
||||
t.Errorf("Expected docs (%v) to equal (%v)\n",
|
||||
returned, test.docs)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCrawlFromSeed(t *testing.T) {
|
||||
fmt.Println("testing CrawlFromSeed")
|
||||
|
||||
tests := []struct {
|
||||
seed CrawlSeed
|
||||
matcher string
|
||||
corpus []doc.KustomizationDocument
|
||||
}{
|
||||
{
|
||||
seed: CrawlSeed{
|
||||
{
|
||||
RepositoryURL: kustomizeRepo,
|
||||
FilePath: "examples/helloWorld/kustomization.yaml",
|
||||
},
|
||||
{
|
||||
RepositoryURL: kustomizeRepo,
|
||||
FilePath: "examples/other/kustomization.yaml",
|
||||
},
|
||||
},
|
||||
matcher: kustomizeRepo,
|
||||
corpus: []doc.KustomizationDocument{
|
||||
// Visited from the seed, will be ignored in the crawl.
|
||||
{Document: doc.Document{
|
||||
RepositoryURL: kustomizeRepo,
|
||||
FilePath: "examples/helloWorld/kustomization.yaml",
|
||||
DocumentData: `
|
||||
resources:
|
||||
- deployment.yaml
|
||||
`,
|
||||
}},
|
||||
// Also visited from the seed as a relative resource.
|
||||
{Document: doc.Document{
|
||||
RepositoryURL: kustomizeRepo,
|
||||
FilePath: "examples/helloWorld/deployment.yaml",
|
||||
DocumentData: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: hello
|
||||
`,
|
||||
}},
|
||||
// Visited from the seed. Has a remote import.
|
||||
{Document: doc.Document{
|
||||
RepositoryURL: kustomizeRepo,
|
||||
FilePath: "examples/other/kustomization.yaml",
|
||||
DocumentData: `
|
||||
resources:
|
||||
- https://github.com/kubernetes-sigs/kustomize/examples/other/overlay
|
||||
- service.yaml
|
||||
`,
|
||||
}},
|
||||
// Imported as a base from the seed.
|
||||
{Document: doc.Document{
|
||||
RepositoryURL: kustomizeRepo,
|
||||
FilePath: "examples/other/overlay/kustomization.yaml",
|
||||
DocumentData: `
|
||||
resources:
|
||||
- https://github.com/kubernetes-sigs/kustomize/examples/seedcrawl1
|
||||
- https://github.com/kubernetes-sigs/kustomize/examples/seedcrawl2
|
||||
`,
|
||||
}},
|
||||
// Imported as a resource from the seed.
|
||||
{Document: doc.Document{
|
||||
RepositoryURL: kustomizeRepo,
|
||||
FilePath: "examples/other/service.yaml",
|
||||
}},
|
||||
// Visited from crawling seed.
|
||||
{Document: doc.Document{
|
||||
RepositoryURL: kustomizeRepo,
|
||||
FilePath: "examples/seedcrawl1/kustomization.yml",
|
||||
}},
|
||||
// Visited from crawling seed.
|
||||
{Document: doc.Document{
|
||||
RepositoryURL: kustomizeRepo,
|
||||
FilePath: "examples/seedcrawl2/kustomization.yaml",
|
||||
DocumentData: `
|
||||
resources:
|
||||
- ../base
|
||||
- job.yaml
|
||||
`,
|
||||
}},
|
||||
// Visited from crawling seed.
|
||||
{Document: doc.Document{
|
||||
RepositoryURL: kustomizeRepo,
|
||||
FilePath: "examples/base/kustomization.yml",
|
||||
}},
|
||||
// Visited from crawling seed imported as resource.
|
||||
{Document: doc.Document{
|
||||
RepositoryURL: kustomizeRepo,
|
||||
FilePath: "examples/seedcrawl2/job.yaml",
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
cr := newCrawler(tc.matcher, nil, tc.corpus)
|
||||
visited := make(map[string]int)
|
||||
CrawlFromSeed(context.Background(), tc.seed, []Crawler{cr},
|
||||
func(d *doc.Document) (CrawledDocument, error) {
|
||||
return &doc.KustomizationDocument{
|
||||
Document: *d,
|
||||
}, nil
|
||||
},
|
||||
func(d CrawledDocument, cr Crawler) error {
|
||||
visited[d.ID()]++
|
||||
return nil
|
||||
},
|
||||
make(map[string]struct{}),
|
||||
)
|
||||
if lv, lc := len(visited), len(tc.corpus); lv != lc {
|
||||
t.Errorf("error: %d of %d documents visited.", lv, lc)
|
||||
t.Errorf("\nvisited (%v)\nexpected (%v).", visited, cr.lukp)
|
||||
}
|
||||
for id, cnt := range visited {
|
||||
if cnt != 1 {
|
||||
t.Errorf("%s not visited once (%d)", id, cnt)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
597
api/internal/crawl/crawler/github/crawler.go
Normal file
597
api/internal/crawl/crawler/github/crawler.go
Normal file
@@ -0,0 +1,597 @@
|
||||
// Package github implements the crawler.Crawler interface, getting data
|
||||
// from the Github search API.
|
||||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/internal/crawl/crawler"
|
||||
"sigs.k8s.io/kustomize/api/internal/crawl/doc"
|
||||
"sigs.k8s.io/kustomize/api/internal/crawl/httpclient"
|
||||
"sigs.k8s.io/kustomize/api/internal/git"
|
||||
"sigs.k8s.io/kustomize/api/konfig"
|
||||
)
|
||||
|
||||
var logger = log.New(os.Stdout, "Github Crawler: ",
|
||||
log.LstdFlags|log.LUTC|log.Llongfile)
|
||||
|
||||
// Implements crawler.Crawler.
|
||||
type githubCrawler struct {
|
||||
client GhClient
|
||||
query Query
|
||||
}
|
||||
|
||||
type GhClient struct {
|
||||
RequestConfig
|
||||
retryCount uint64
|
||||
client *http.Client
|
||||
accessToken string
|
||||
}
|
||||
|
||||
func NewCrawler(accessToken string, retryCount uint64, client *http.Client,
|
||||
query Query) githubCrawler {
|
||||
|
||||
return githubCrawler{
|
||||
client: GhClient{
|
||||
retryCount: retryCount,
|
||||
client: client,
|
||||
RequestConfig: RequestConfig{
|
||||
perPage: githubMaxPageSize,
|
||||
},
|
||||
accessToken: accessToken,
|
||||
},
|
||||
query: query,
|
||||
}
|
||||
}
|
||||
|
||||
// Implements crawler.Crawler.
|
||||
func (gc githubCrawler) Crawl(
|
||||
ctx context.Context, output chan<- crawler.CrawledDocument) error {
|
||||
|
||||
noETagClient := GhClient{
|
||||
RequestConfig: gc.client.RequestConfig,
|
||||
client: &http.Client{Timeout: gc.client.client.Timeout},
|
||||
retryCount: gc.client.retryCount,
|
||||
accessToken: gc.client.accessToken,
|
||||
}
|
||||
|
||||
// Since Github returns a max of 1000 results per query, we can use
|
||||
// multiple queries that split the search space into chunks of at most
|
||||
// 1000 files to get all of the data.
|
||||
ranges, err := FindRangesForRepoSearch(newCache(noETagClient, gc.query))
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not split %v into ranges, %v\n",
|
||||
gc.query, err)
|
||||
}
|
||||
|
||||
logger.Println("ranges: ", ranges)
|
||||
|
||||
// Query each range for files.
|
||||
errs := make(multiError, 0)
|
||||
for _, query := range ranges {
|
||||
err := processQuery(ctx, gc.client, query, output)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gc githubCrawler) FetchDocument(_ context.Context, d *doc.Document) error {
|
||||
repoURL := d.RepositoryURL + "/" + d.FilePath + "?ref=" + d.DefaultBranch
|
||||
repoSpec, err := git.NewRepoSpecFromUrl(repoURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid repospec: %v", err)
|
||||
}
|
||||
|
||||
url := "https://raw.githubusercontent.com/" + repoSpec.OrgRepo +
|
||||
"/" + repoSpec.Ref + "/" + repoSpec.Path
|
||||
|
||||
handle := func(resp *http.Response, err error, path string) error {
|
||||
if err == nil && resp.StatusCode == http.StatusOK {
|
||||
d.IsSame = httpclient.FromCache(resp.Header)
|
||||
defer resp.Body.Close()
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.DocumentData = string(data)
|
||||
d.FilePath = d.FilePath + path
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
resp, err := gc.client.GetRawUserContent(url)
|
||||
if err := handle(resp, err, ""); err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, file := range konfig.RecognizedKustomizationFileNames() {
|
||||
resp, err = gc.client.GetRawUserContent(url + "/" + file)
|
||||
err := handle(resp, err, "/"+file)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("file not found: %s, error: %v", url, err)
|
||||
}
|
||||
|
||||
func (gc githubCrawler) SetCreated(_ context.Context, d *doc.Document) error {
|
||||
fs := GhFileSpec{
|
||||
Path: d.FilePath,
|
||||
Repository: GitRepository{
|
||||
FullName: d.RepositoryFullName(),
|
||||
},
|
||||
}
|
||||
creationTime, err := gc.client.GetFileCreationTime(fs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.CreationTime = &creationTime
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gc githubCrawler) Match(d *doc.Document) bool {
|
||||
url := d.RepositoryURL + "/" + d.FilePath + "?ref=" + "/" +
|
||||
d.DefaultBranch
|
||||
repoSpec, err := git.NewRepoSpecFromUrl(url)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return strings.Contains(repoSpec.Host, "github.com")
|
||||
}
|
||||
|
||||
// processQuery follows all of the pages in a query, and updates/adds the
|
||||
// documents from the crawl to the datastore/index.
|
||||
func processQuery(ctx context.Context, gcl GhClient, query string,
|
||||
output chan<- crawler.CrawledDocument) error {
|
||||
|
||||
queryPages := make(chan GhResponseInfo)
|
||||
|
||||
go func() {
|
||||
// Forward the document metadata to the retrieval channel.
|
||||
// This separation allows for concurrent requests for the code
|
||||
// search, and the retrieval portions of the API.
|
||||
err := gcl.ForwardPaginatedQuery(ctx, query, queryPages)
|
||||
if err != nil {
|
||||
// TODO(damienr74) handle this error with redis?
|
||||
logger.Println(err)
|
||||
}
|
||||
close(queryPages)
|
||||
}()
|
||||
|
||||
errs := make(multiError, 0)
|
||||
errorCnt := 0
|
||||
totalCnt := 0
|
||||
for page := range queryPages {
|
||||
if page.Error != nil {
|
||||
errs = append(errs, page.Error)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, file := range page.Parsed.Items {
|
||||
k, err := kustomizationResultAdapter(gcl, file)
|
||||
if err != nil {
|
||||
logger.Printf("kustomizationResultAdapter failed: %v", err)
|
||||
errs = append(errs, err)
|
||||
errorCnt++
|
||||
}
|
||||
if k != nil {
|
||||
output <- k
|
||||
}
|
||||
totalCnt++
|
||||
}
|
||||
|
||||
logger.Printf("got %d files out of %d from API. %d of %d had errors\n",
|
||||
totalCnt, page.Parsed.TotalCount, errorCnt, totalCnt)
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
func kustomizationResultAdapter(gcl GhClient, k GhFileSpec) (
|
||||
crawler.CrawledDocument, error) {
|
||||
|
||||
data, err := gcl.GetFileData(k)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
url := gcl.ReposRequest(k.Repository.FullName)
|
||||
defaultBranch, err := gcl.GetDefaultBranch(url)
|
||||
if err != nil {
|
||||
logger.Printf(
|
||||
"(error: %v) setting default_branch to master\n", err)
|
||||
defaultBranch = "master"
|
||||
}
|
||||
|
||||
d := doc.KustomizationDocument{
|
||||
Document: doc.Document{
|
||||
DocumentData: string(data),
|
||||
FilePath: k.Path,
|
||||
DefaultBranch: defaultBranch,
|
||||
RepositoryURL: k.Repository.URL,
|
||||
},
|
||||
}
|
||||
creationTime, err := gcl.GetFileCreationTime(k)
|
||||
if err != nil {
|
||||
logger.Printf("GetFileCreationTime failed: %v", err)
|
||||
return &d, err
|
||||
}
|
||||
d.CreationTime = &creationTime
|
||||
|
||||
if err := d.ParseYAML(); err != nil {
|
||||
logger.Printf("ParseYAML failed: %v", err)
|
||||
return &d, err
|
||||
}
|
||||
|
||||
return &d, nil
|
||||
}
|
||||
|
||||
// ForwardPaginatedQuery follows the links to the next pages and performs all of
|
||||
// the queries for a given search query, relaying the data from each request
|
||||
// back to an output channel.
|
||||
func (gcl GhClient) ForwardPaginatedQuery(ctx context.Context, query string,
|
||||
output chan<- GhResponseInfo) error {
|
||||
|
||||
logger.Println("querying: ", query)
|
||||
response := gcl.parseGithubResponse(query)
|
||||
|
||||
if response.Error != nil {
|
||||
return response.Error
|
||||
}
|
||||
|
||||
output <- response
|
||||
|
||||
for response.LastURL != "" && response.NextURL != "" {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
default:
|
||||
response = gcl.parseGithubResponse(response.NextURL)
|
||||
if response.Error != nil {
|
||||
return response.Error
|
||||
}
|
||||
|
||||
output <- response
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetFileData gets the bytes from a file.
|
||||
func (gcl GhClient) GetFileData(k GhFileSpec) ([]byte, error) {
|
||||
|
||||
url := gcl.ContentsRequest(k.Repository.FullName, k.Path)
|
||||
|
||||
resp, err := gcl.GetReposData(url)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%+v: could not get '%s' metadata: %v",
|
||||
k, url, err)
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%+v: could not read '%s' metadata: %v",
|
||||
k, url, err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
type githubContentRawURL struct {
|
||||
DownloadURL string `json:"download_url,omitempty"`
|
||||
}
|
||||
var rawURL githubContentRawURL
|
||||
err = json.Unmarshal(data, &rawURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"%+v: could not get 'download_url' from '%s' response: %v",
|
||||
k, data, err)
|
||||
}
|
||||
|
||||
resp, err = gcl.GetRawUserContent(rawURL.DownloadURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%+v: could not fetch file raw data '%s': %v",
|
||||
k, rawURL.DownloadURL, err)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
data, err = ioutil.ReadAll(resp.Body)
|
||||
return data, err
|
||||
}
|
||||
|
||||
func (gcl GhClient) GetDefaultBranch(url string) (string, error) {
|
||||
resp, err := gcl.GetReposData(url)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf(
|
||||
"'%s' could not get default_branch: %v", url, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf(
|
||||
"could not read default_branch: %v", err)
|
||||
}
|
||||
|
||||
type defaultBranch struct {
|
||||
DefaultBranch string `json:"default_branch,omitempty"`
|
||||
}
|
||||
var branch defaultBranch
|
||||
err = json.Unmarshal(data, &branch)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf(
|
||||
"default_branch json malformed: %v", err)
|
||||
}
|
||||
|
||||
return branch.DefaultBranch, nil
|
||||
}
|
||||
|
||||
// GetFileCreationTime gets the earliest date of a file.
|
||||
func (gcl GhClient) GetFileCreationTime(
|
||||
k GhFileSpec) (time.Time, error) {
|
||||
|
||||
url := gcl.CommitsRequest(k.Repository.FullName, k.Path)
|
||||
|
||||
defaultTime := time.Now()
|
||||
|
||||
resp, err := gcl.GetReposData(url)
|
||||
if err != nil {
|
||||
return defaultTime, fmt.Errorf(
|
||||
"%+v: '%s' could not get metadata: %v", k, url, err)
|
||||
}
|
||||
|
||||
type DateSpec struct {
|
||||
Commit struct {
|
||||
Author struct {
|
||||
Date string `json:"date,omitempty"`
|
||||
} `json:"author,omitempty"`
|
||||
} `json:"commit,omitempty"`
|
||||
}
|
||||
|
||||
_, lastURL := parseGithubLinkFormat(resp.Header.Get("link"))
|
||||
if lastURL != "" {
|
||||
resp, err = gcl.GetReposData(lastURL)
|
||||
if err != nil {
|
||||
return defaultTime, fmt.Errorf(
|
||||
"%+v: '%s' could not get metadata: %v",
|
||||
k, lastURL, err)
|
||||
}
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return defaultTime, fmt.Errorf(
|
||||
"%+v: failed to read metadata: %v", k, err)
|
||||
}
|
||||
var earliestDate []DateSpec
|
||||
err = json.Unmarshal(data, &earliestDate)
|
||||
size := len(earliestDate)
|
||||
if err != nil || size == 0 {
|
||||
return defaultTime, fmt.Errorf(
|
||||
"%+v: server response '%s' not in expected format: %v",
|
||||
k, data, err)
|
||||
}
|
||||
|
||||
return time.Parse(time.RFC3339, earliestDate[size-1].Commit.Author.Date)
|
||||
}
|
||||
|
||||
// TODO(damienr74) change the tickers to actually check api rate limits, reset
|
||||
// times, and throttle requests dynamically based off of current utilization,
|
||||
// instead of hardcoding the documented values, these calls are not quota'd.
|
||||
// This is now especially important, since caching the API requests will reduce
|
||||
// API quota use (so we can actually make more requests in the allotted time
|
||||
// period).
|
||||
//
|
||||
// See https://developer.github.com/v3/rate_limit/ for details.
|
||||
var (
|
||||
searchRateTicker = time.NewTicker(time.Second * 2)
|
||||
contentRateTicker = time.NewTicker(time.Second * 1)
|
||||
)
|
||||
|
||||
func throttleSearchAPI() {
|
||||
<-searchRateTicker.C
|
||||
}
|
||||
|
||||
func throttleRepoAPI() {
|
||||
<-contentRateTicker.C
|
||||
}
|
||||
|
||||
type multiError []error
|
||||
|
||||
func (e multiError) Error() string {
|
||||
size := len(e) + 2
|
||||
strs := make([]string, size)
|
||||
strs[0] = "Errors ["
|
||||
for i, err := range e {
|
||||
strs[i+1] = "\t" + err.Error()
|
||||
}
|
||||
strs[size-1] = "]"
|
||||
return strings.Join(strs, "\n")
|
||||
}
|
||||
|
||||
type GitRepository struct {
|
||||
API string `json:"url,omitempty"`
|
||||
URL string `json:"html_url,omitempty"`
|
||||
FullName string `json:"full_name,omitempty"`
|
||||
}
|
||||
|
||||
type GhFileSpec struct {
|
||||
Path string `json:"path,omitempty"`
|
||||
Repository GitRepository `json:"repository,omitempty"`
|
||||
}
|
||||
|
||||
type githubResponse struct {
|
||||
// MaxUint is reserved as a sentinel value.
|
||||
// This is the number of files that match the query.
|
||||
TotalCount uint64 `json:"total_count,omitempty"`
|
||||
|
||||
// Github representation of a file.
|
||||
Items []GhFileSpec `json:"items,omitempty"`
|
||||
}
|
||||
|
||||
type GhResponseInfo struct {
|
||||
*http.Response
|
||||
Parsed *githubResponse
|
||||
Error error
|
||||
NextURL string
|
||||
LastURL string
|
||||
}
|
||||
|
||||
func parseGithubLinkFormat(links string) (string, string) {
|
||||
const (
|
||||
linkNext = "next"
|
||||
linkLast = "last"
|
||||
linkInfoURL = 1
|
||||
linkInfoRel = 2
|
||||
)
|
||||
|
||||
next, last := "", ""
|
||||
linkInfo := regexp.MustCompile(`<(.*)>.*; rel="(last|next)"`)
|
||||
|
||||
for _, link := range strings.Split(links, ",") {
|
||||
linkParse := linkInfo.FindStringSubmatch(link)
|
||||
if len(linkParse) != 3 {
|
||||
continue
|
||||
}
|
||||
|
||||
url := linkParse[linkInfoURL]
|
||||
switch linkParse[linkInfoRel] {
|
||||
case linkNext:
|
||||
next = url
|
||||
case linkLast:
|
||||
last = url
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
return next, last
|
||||
}
|
||||
|
||||
func (gcl GhClient) parseGithubResponse(getRequest string) GhResponseInfo {
|
||||
resp, err := gcl.SearchGithubAPI(getRequest)
|
||||
requestInfo := GhResponseInfo{
|
||||
Response: resp,
|
||||
Error: err,
|
||||
Parsed: nil,
|
||||
}
|
||||
|
||||
if err != nil || resp == nil {
|
||||
return requestInfo
|
||||
}
|
||||
|
||||
var data []byte
|
||||
defer resp.Body.Close()
|
||||
data, requestInfo.Error = ioutil.ReadAll(resp.Body)
|
||||
if requestInfo.Error != nil {
|
||||
return requestInfo
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
logger.Println("query: ", getRequest)
|
||||
logger.Println("status not OK at the source")
|
||||
logger.Println("header dump", resp.Header)
|
||||
logger.Println("body dump", string(data))
|
||||
requestInfo.Error = fmt.Errorf("request rejected, status '%s'",
|
||||
resp.Status)
|
||||
return requestInfo
|
||||
}
|
||||
|
||||
requestInfo.NextURL, requestInfo.LastURL =
|
||||
parseGithubLinkFormat(resp.Header.Get("link"))
|
||||
|
||||
resultCount := githubResponse{
|
||||
TotalCount: math.MaxUint64,
|
||||
}
|
||||
requestInfo.Error = json.Unmarshal(data, &resultCount)
|
||||
if requestInfo.Error != nil {
|
||||
return requestInfo
|
||||
}
|
||||
|
||||
requestInfo.Parsed = &resultCount
|
||||
|
||||
return requestInfo
|
||||
|
||||
}
|
||||
|
||||
// SearchGithubAPI performs a search query and handles rate limitting for
|
||||
// the 'search/code?' endpoint as well as timed retries in the case of abuse
|
||||
// prevention.
|
||||
func (gcl GhClient) SearchGithubAPI(query string) (*http.Response, error) {
|
||||
throttleSearchAPI()
|
||||
return gcl.getWithRetry(query)
|
||||
}
|
||||
|
||||
// GetReposData performs a search query and handles rate limitting for
|
||||
// the '/repos' endpoint as well as timed retries in the case of abuse
|
||||
// prevention.
|
||||
func (gcl GhClient) GetReposData(query string) (*http.Response, error) {
|
||||
throttleRepoAPI()
|
||||
return gcl.getWithRetry(query)
|
||||
}
|
||||
|
||||
// User content (file contents) is not API rate limited, so there's no use in
|
||||
// throttling this call.
|
||||
func (gcl GhClient) GetRawUserContent(query string) (*http.Response, error) {
|
||||
return gcl.getWithRetry(query)
|
||||
}
|
||||
|
||||
func (gcl GhClient) Do(query string) (*http.Response, error) {
|
||||
req, err := http.NewRequest("GET", query, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Add("Authorization", fmt.Sprintf("token %s", gcl.accessToken))
|
||||
return gcl.client.Do(req)
|
||||
}
|
||||
|
||||
func (gcl GhClient) getWithRetry(
|
||||
query string) (resp *http.Response, err error) {
|
||||
|
||||
resp, err = gcl.Do(query)
|
||||
|
||||
retryCount := gcl.retryCount
|
||||
|
||||
for err == nil &&
|
||||
resp.StatusCode == http.StatusForbidden &&
|
||||
retryCount > 0 {
|
||||
|
||||
retryTime := resp.Header.Get("Retry-After")
|
||||
i, err := strconv.Atoi(retryTime)
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf(
|
||||
"query '%s' forbidden without 'Retry-After'", query)
|
||||
}
|
||||
logger.Printf(
|
||||
"status forbidden, retring %d more times\n", retryCount)
|
||||
|
||||
logger.Printf("waiting %d seconds before retrying\n", i)
|
||||
time.Sleep(time.Second * time.Duration(i))
|
||||
retryCount--
|
||||
resp, err = gcl.Do(query)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("query '%s' could not be processed, %v",
|
||||
query, err)
|
||||
}
|
||||
|
||||
return resp, err
|
||||
}
|
||||
227
api/internal/crawl/crawler/github/queries.go
Normal file
227
api/internal/crawl/crawler/github/queries.go
Normal file
@@ -0,0 +1,227 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
perPageArg = "per_page"
|
||||
)
|
||||
|
||||
const githubMaxPageSize = 100
|
||||
|
||||
// Implementation detail, not important to external API.
|
||||
type queryField struct {
|
||||
name string
|
||||
value interface{}
|
||||
}
|
||||
|
||||
// Formats a query field.
|
||||
func (qf queryField) String() string {
|
||||
var value string
|
||||
switch v := qf.value.(type) {
|
||||
case string:
|
||||
value = v
|
||||
case rangeFormatter:
|
||||
value = v.RangeString()
|
||||
default:
|
||||
value = fmt.Sprint(v)
|
||||
}
|
||||
|
||||
if qf.name == "" {
|
||||
return value
|
||||
}
|
||||
return fmt.Sprint(qf.name, ":", value)
|
||||
}
|
||||
|
||||
// Example of formating a query:
|
||||
// QueryWith(
|
||||
// Filename("kustomization.yaml"),
|
||||
// Filesize(RangeWithin{64, 192}),
|
||||
// Keyword("copyright"),
|
||||
// Keyword("2019"),
|
||||
// ).String()
|
||||
//
|
||||
// Outputs "q=filename:kustomization.yaml+size:64..192+copyright+2018" which
|
||||
// would search for files that have [64, 192] bytes (inclusive range) and that
|
||||
// contain the keywords 'copyright' and '2019' somewhere in the file.
|
||||
type Query []queryField
|
||||
|
||||
func QueryWith(qfs ...queryField) Query {
|
||||
return qfs
|
||||
}
|
||||
|
||||
func (q Query) String() string {
|
||||
strs := make([]string, 0, len(q))
|
||||
for _, elem := range q {
|
||||
str := elem.String()
|
||||
if str == "" {
|
||||
continue
|
||||
}
|
||||
strs = append(strs, str)
|
||||
}
|
||||
|
||||
query := strings.Join(strs, "+")
|
||||
if query == "" {
|
||||
return query
|
||||
}
|
||||
return "q=" + query
|
||||
}
|
||||
|
||||
// Keyword takes a single word, and formats it according to the Github API.
|
||||
func Keyword(k string) queryField {
|
||||
return queryField{value: k}
|
||||
}
|
||||
|
||||
// Filesize takes a rangeFormatter and formats it according to the Github API.
|
||||
func Filesize(r rangeFormatter) queryField {
|
||||
return queryField{name: "size", value: r}
|
||||
}
|
||||
|
||||
// Filename takes a filename and formats it according to the Github API.
|
||||
func Filename(f string) queryField {
|
||||
return queryField{name: "filename", value: f}
|
||||
}
|
||||
|
||||
// Path takes a filepath and formats it according to the Github API.
|
||||
func Path(p string) queryField {
|
||||
return queryField{name: "path", value: p}
|
||||
}
|
||||
|
||||
// Repo takes a repository (i.e., kubernetes-sigs/kustomize) and formats
|
||||
// it according to the Github API.
|
||||
func Repo(r string) queryField {
|
||||
return queryField{name: "repo", value: r}
|
||||
}
|
||||
|
||||
// Path takes a github username and formats it according to the Github API.
|
||||
func User(u string) queryField {
|
||||
return queryField{name: "user", value: u}
|
||||
}
|
||||
|
||||
// RequestConfig stores common variables that must be present for the queries.
|
||||
// - CodeSearchRequests: ask Github to check the code indices given a query.
|
||||
// - ContentsRequests: ask Github where to download a resource given a repo and a
|
||||
// file path.
|
||||
// - CommitsRequests: asks Github to list commits made one a file. Useful to
|
||||
// determine the date of a file.
|
||||
type RequestConfig struct {
|
||||
perPage uint64
|
||||
}
|
||||
|
||||
// CodeSearchRequestWith given a list of query parameters that specify the
|
||||
// (patial) query, returns a request object with the (parital) query. Must call
|
||||
// the URL method to get the string value of the URL. See request.CopyWith, to
|
||||
// understand why the request object is useful.
|
||||
func (rc RequestConfig) CodeSearchRequestWith(query Query) request {
|
||||
req := rc.makeRequest("search/code", query)
|
||||
req.vals.Set("sort", "indexed")
|
||||
req.vals.Set("order", "desc")
|
||||
return req
|
||||
}
|
||||
|
||||
// ContentsRequest given the repo name, and the filepath returns a formatted
|
||||
// query for the Github API to find the dowload information of this filepath.
|
||||
func (rc RequestConfig) ContentsRequest(fullRepoName, path string) string {
|
||||
uri := fmt.Sprintf("repos/%s/contents/%s", fullRepoName, path)
|
||||
return rc.makeRequest(uri, Query{}).URL()
|
||||
}
|
||||
|
||||
func (rc RequestConfig) ReposRequest(fullRepoName string) string {
|
||||
uri := fmt.Sprintf("repos/%s", fullRepoName)
|
||||
return rc.makeRequest(uri, Query{}).URL()
|
||||
}
|
||||
|
||||
func escapeSpace(s string) string {
|
||||
return strings.Replace(s, " ", "%20", -1)
|
||||
}
|
||||
|
||||
// CommitsRequest given the repo name, and a filepath returns a formatted query
|
||||
// for the Github API to find the commits that affect this file.
|
||||
func (rc RequestConfig) CommitsRequest(fullRepoName, path string) string {
|
||||
uri := fmt.Sprintf("repos/%s/commits", fullRepoName)
|
||||
return rc.makeRequest(uri, Query{Path(escapeSpace(path))}).URL()
|
||||
}
|
||||
|
||||
func (rc RequestConfig) makeRequest(path string, query Query) request {
|
||||
vals := url.Values{}
|
||||
vals.Set(perPageArg, fmt.Sprint(rc.perPage))
|
||||
|
||||
return request{
|
||||
url: url.URL{
|
||||
Scheme: "https",
|
||||
Host: "api.github.com",
|
||||
Path: path,
|
||||
},
|
||||
vals: vals,
|
||||
query: query,
|
||||
}
|
||||
}
|
||||
|
||||
type request struct {
|
||||
url url.URL
|
||||
vals url.Values
|
||||
query Query
|
||||
}
|
||||
|
||||
// CopyWith copies the requests and adds the extra query parameters. Usefull
|
||||
// for dynamically adding sizes to a filename only query without modifying it.
|
||||
func (r request) CopyWith(queryParams ...queryField) request {
|
||||
cpy := r
|
||||
cpy.query = append(cpy.query, queryParams...)
|
||||
return cpy
|
||||
}
|
||||
|
||||
// URL encodes the variables and the URL representation into a string.
|
||||
func (r request) URL() string {
|
||||
// Github does not handle URL encoding properly in its API for the
|
||||
// q='...', so the query parameter is added without any encoding
|
||||
// manually.
|
||||
encoded := r.vals.Encode()
|
||||
query := r.query.String()
|
||||
sep := "&"
|
||||
if query == "" {
|
||||
sep = ""
|
||||
}
|
||||
if encoded == "" && query != "" {
|
||||
sep = "?"
|
||||
}
|
||||
r.url.RawQuery = query + sep + encoded
|
||||
return r.url.String()
|
||||
}
|
||||
|
||||
// Allows to define a range of numbers and print it in the github range
|
||||
// query format https://help.github.com/en/articles/understanding-the-search-syntax.
|
||||
type rangeFormatter interface {
|
||||
RangeString() string
|
||||
}
|
||||
|
||||
// RangeLessThan is a range of values strictly less than (<) size.
|
||||
type RangeLessThan struct {
|
||||
size uint64
|
||||
}
|
||||
|
||||
func (r RangeLessThan) RangeString() string {
|
||||
return fmt.Sprintf("<%d", r.size)
|
||||
}
|
||||
|
||||
// RangeLessThan is a range of values strictly greater than (>) size.
|
||||
type RangeGreaterThan struct {
|
||||
size uint64
|
||||
}
|
||||
|
||||
func (r RangeGreaterThan) RangeString() string {
|
||||
return fmt.Sprintf(">%d", r.size)
|
||||
}
|
||||
|
||||
// RangeWithin is an inclusive range from start to end.
|
||||
type RangeWithin struct {
|
||||
start uint64
|
||||
end uint64
|
||||
}
|
||||
|
||||
func (r RangeWithin) RangeString() string {
|
||||
return fmt.Sprintf("%d..%d", r.start, r.end)
|
||||
}
|
||||
140
api/internal/crawl/crawler/github/queries_test.go
Normal file
140
api/internal/crawl/crawler/github/queries_test.go
Normal file
@@ -0,0 +1,140 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestQueryFields(t *testing.T) {
|
||||
testCases := []struct {
|
||||
formatter queryField
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
formatter: Keyword("keyword"),
|
||||
expected: "keyword",
|
||||
},
|
||||
{
|
||||
formatter: Filesize(RangeLessThan{23}),
|
||||
expected: "size:<23",
|
||||
},
|
||||
{
|
||||
formatter: Filesize(RangeWithin{24, 64}),
|
||||
expected: "size:24..64",
|
||||
},
|
||||
{
|
||||
formatter: Filesize(RangeGreaterThan{64}),
|
||||
expected: "size:>64",
|
||||
},
|
||||
{
|
||||
formatter: Path("some/path/to/file"),
|
||||
expected: "path:some/path/to/file",
|
||||
},
|
||||
{
|
||||
formatter: Filename("kustomization.yaml"),
|
||||
expected: "filename:kustomization.yaml",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
if result := test.formatter.String(); result != test.expected {
|
||||
t.Errorf("got (%#v = %s), expected %s", test.formatter, result, test.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueryType(t *testing.T) {
|
||||
testCases := []struct {
|
||||
query Query
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
query: QueryWith(
|
||||
Filesize(RangeWithin{24, 64}),
|
||||
Filename("kustomization.yaml"),
|
||||
Keyword("keyword1"),
|
||||
Keyword("keyword2"),
|
||||
Repo("user1/repo1"),
|
||||
User("user1"),
|
||||
),
|
||||
expected: "q=size:24..64+filename:kustomization.yaml+keyword1+keyword2+" +
|
||||
"repo:user1/repo1+user:user1",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
if queryStr := test.query.String(); queryStr != test.expected {
|
||||
t.Errorf("got (%#v = %s), expected %s", test.query, queryStr, test.expected)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestGithubSearchQuery(t *testing.T) {
|
||||
const (
|
||||
perPage = 100
|
||||
)
|
||||
|
||||
testCases := []struct {
|
||||
rc RequestConfig
|
||||
codeQuery Query
|
||||
fullRepoName string
|
||||
path string
|
||||
expectedCodeQuery string
|
||||
expectedContentsQuery string
|
||||
expectedCommitsQuery string
|
||||
}{
|
||||
{
|
||||
rc: RequestConfig{
|
||||
perPage: perPage,
|
||||
},
|
||||
codeQuery: Query{
|
||||
Filename("kustomization.yaml"),
|
||||
Filesize(RangeWithin{64, 128}),
|
||||
},
|
||||
fullRepoName: "kubernetes-sigs/kustomize",
|
||||
path: "examples/helloWorld/kustomization.yaml",
|
||||
|
||||
expectedCodeQuery: "https://api.github.com/search/code?" +
|
||||
"q=filename:kustomization.yaml+size:64..128&order=desc&per_page=100&sort=indexed",
|
||||
|
||||
expectedContentsQuery: "https://api.github.com/repos/kubernetes-sigs/kustomize/contents/" +
|
||||
"examples/helloWorld/kustomization.yaml?per_page=100",
|
||||
|
||||
expectedCommitsQuery: "https://api.github.com/repos/kubernetes-sigs/kustomize/commits?" +
|
||||
"q=path:examples/helloWorld/kustomization.yaml&per_page=100",
|
||||
},
|
||||
{
|
||||
rc: RequestConfig{
|
||||
perPage: perPage,
|
||||
},
|
||||
codeQuery: Query{
|
||||
Filename("kustomization.yaml"),
|
||||
Filesize(RangeWithin{64, 128}),
|
||||
},
|
||||
fullRepoName: "kubernetes-sigs/kustomize",
|
||||
path: "examples 1/helloWorld/kustomization.yaml",
|
||||
|
||||
expectedCodeQuery: "https://api.github.com/search/code?" +
|
||||
"q=filename:kustomization.yaml+size:64..128&order=desc&per_page=100&sort=indexed",
|
||||
|
||||
expectedContentsQuery: "https://api.github.com/repos/kubernetes-sigs/kustomize/contents/" +
|
||||
"examples%201/helloWorld/kustomization.yaml?per_page=100",
|
||||
|
||||
expectedCommitsQuery: "https://api.github.com/repos/kubernetes-sigs/kustomize/commits?" +
|
||||
"q=path:examples%201/helloWorld/kustomization.yaml&per_page=100",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
if result := test.rc.CodeSearchRequestWith(test.codeQuery).URL(); result != test.expectedCodeQuery {
|
||||
t.Errorf("Got code query: %s, expected %s", result, test.expectedCodeQuery)
|
||||
}
|
||||
|
||||
if result := test.rc.ContentsRequest(test.fullRepoName, test.path); result != test.expectedContentsQuery {
|
||||
t.Errorf("Got contents query: %s, expected %s", result, test.expectedContentsQuery)
|
||||
}
|
||||
if result := test.rc.CommitsRequest(test.fullRepoName, test.path); result != test.expectedCommitsQuery {
|
||||
t.Errorf("Got commits query: %s, expected %s", result, test.expectedCommitsQuery)
|
||||
}
|
||||
}
|
||||
}
|
||||
378
api/internal/crawl/crawler/github/split_search_ranges.go
Normal file
378
api/internal/crawl/crawler/github/split_search_ranges.go
Normal file
@@ -0,0 +1,378 @@
|
||||
package github
|
||||
|
||||
// GitHub only returns at most 1000 results per search query,
|
||||
// this is problematic if you want to retrieve all the results for a given
|
||||
// search query. However, GitHub allows you to specify as much as you want per
|
||||
// query to make things more specific. Specifically for files, GitHub allows
|
||||
// you to specify their sizes with range queries. This is very convenient
|
||||
// since it allows us to split the search into disjoint sets/shards of results
|
||||
// from the different file size ranges.
|
||||
//
|
||||
// Some important factors to consider:
|
||||
//
|
||||
// - These queries are rate limited by the API to roughly once query every two
|
||||
// seconds.
|
||||
//
|
||||
// - The search space for file sizes is in bytes, from 0B to < 512KiB (this is
|
||||
// a huge search space that cannot be probed linearly in a timely manner if
|
||||
// granularity is to be expected).
|
||||
//
|
||||
// - If you have K files there will likely be ~K/1000 sets that you have find
|
||||
// from this search space in order to get all of the results.
|
||||
//
|
||||
// - If you have O(K) sets it is unlikely that they are all of the same size,
|
||||
// since (most files are power law distributed). That means that the range
|
||||
// might be significantly smaller for 1000 small files, than it is for
|
||||
// 1000 large files.
|
||||
//
|
||||
// - This method is a best effort approach. There are some limitations to what
|
||||
// it can and can't do, so please note the following:
|
||||
//
|
||||
// + There may very well be a filesize that has more than 1000 results.
|
||||
// this method cannot help in this case. However, requerying over time
|
||||
// (days/weeks/months) while sorting by last indexed values may be
|
||||
// sufficient to eventually get all of the results.
|
||||
//
|
||||
// + It's possible that the github API returns inconsistent counts. This
|
||||
// is problematic in most cases, since it can cause many issues if the
|
||||
// case is not handled properly. For instance, if you requested the
|
||||
// number of files of an interval from size:0..64 and get that there
|
||||
// are 900 results, you may query at size:0..96 and get that there
|
||||
// are 800 results. To guarantee that this approach completes and does
|
||||
// not get into a query loop over the same intervals, it will retry a few
|
||||
// times and take the largest of the results or the largest previously
|
||||
// queried value from another range (in this case, the implementation
|
||||
// could decide that size:0..96 must have 900) results. This makes the
|
||||
// approach best effort even if there are no single file sizes of over
|
||||
// 1000 results.
|
||||
//
|
||||
//
|
||||
// The approach that was taken to solve this problem is the following:
|
||||
//
|
||||
// 1. Determine the total number of results by querying from the lower bound
|
||||
// to the upper bound (size:0..max). If there are less than 1000 files,
|
||||
// return a single range of values (size:0..max) since all results can be
|
||||
// retrieved.
|
||||
//
|
||||
// 2. Otherwise, set a target number of files to be 1000.
|
||||
//
|
||||
// 3. Binary search for the range from 0..r that provides a file count that is
|
||||
// less than or equal to the target. Once this value is found, store the
|
||||
// upper bound of range (r). If r is the same as the previous value, (or 0)
|
||||
// increase r by one (this guarantees progress, but will miss out on some
|
||||
// results).
|
||||
//
|
||||
// 4. Increase the target by 1000.
|
||||
//
|
||||
// 5. Repeat steps 3 and 4 until the target is at or exceeds the total number
|
||||
// of files.
|
||||
//
|
||||
//
|
||||
// In general there are other ways to get all of the files from GitHub. In
|
||||
// some cases it would be sufficient to just get the files that are being
|
||||
// updated/indexed by github periodically to update the corpus, so this
|
||||
// complicated approach does not have to be run every time. However, for
|
||||
// some searches, there may be too many results on a time interval to do
|
||||
// this simple update search limited to only 1000 results.
|
||||
//
|
||||
// There is also a more sophisticated approach that may yield better
|
||||
// performance:
|
||||
// - Perform this search once and create a prior distribution of file sizes.
|
||||
// Each time you want to retrieve the results of the query, scale the
|
||||
// prior of expected ranges to the current number of files. From each
|
||||
// expected range of 1000 files, perform a exponential search to find the
|
||||
// lower bound of the range. This would likely reduce the total number
|
||||
// of queries by a significant amount since it would only have to search
|
||||
// for a small set of values around each likely range boundary.
|
||||
//
|
||||
// However, actually retrieving the files will be the bottleneck operation
|
||||
// since the number of queries to find the ranges will be close to:
|
||||
// log2(maxFileSize) * totalResults / 1000 ~= totalResults / 50
|
||||
// whereas the number of queries to actually get all of the search results
|
||||
// are close to:
|
||||
// apiCallsPerResult * 10(pages) * 100(resultsPerPage) * totalResults / 1000
|
||||
// = apiCallsPerResult * totalResults.
|
||||
//
|
||||
// So it could very well take apiCallsPerResult * 50 times longer to acutally
|
||||
// fetch the results (assuming the quotas for the API calls are the same as the
|
||||
// search API), than it does to perform these range searches.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/bits"
|
||||
)
|
||||
|
||||
// Files cannot be more than 2^19 bytes, according to
|
||||
// https://help.github.com/en/articles/searching-code#considerations-for-code-search
|
||||
const (
|
||||
githubMaxFileSize = uint64(1 << 19)
|
||||
githubMaxResultsPerQuery = uint64(1000)
|
||||
)
|
||||
|
||||
// Interface instead of struct for testing purposes.
|
||||
// Not expecting to have multiple implementations.
|
||||
type cachedSearch interface {
|
||||
CountResults(uint64) (uint64, error)
|
||||
RequestString(filesize rangeFormatter) string
|
||||
}
|
||||
|
||||
// cachedSearch is a simple data structure that maps the upper bound (r) of a
|
||||
// range from 0 to r to the number of files that have between 0 and r files
|
||||
// (inclusive). It also guarantees that the counts are monotonically increasing
|
||||
// (not strict) as the value for r increases, by looking at the maximal
|
||||
// previous file count for the value that precedes r in the cache.
|
||||
//
|
||||
// It uses a bit trick to be more efficient in detecting
|
||||
// inconsistencies in the returned data from the Github API.
|
||||
// Therefore, the cache expects a search to always start at 0, and
|
||||
// it expects the max file size to be a power of 2. If this is to be changed
|
||||
// there are a few considerations to keep in mind:
|
||||
//
|
||||
// 1. The cache is only efficient if the queries can be reused, so if
|
||||
// the first chunk of files lives in the range 0..x, continuing the
|
||||
// search for the next chunk from x+1..max (while asymptotically sane)
|
||||
// may actually be less efficient since the cache is essentially reset
|
||||
// at every interval. This leads to a larger number of requests in
|
||||
// practice, and requests are what's expensive (rate limits).
|
||||
//
|
||||
// 2. The github API is not perfectly monotonic.. (this is somewhat
|
||||
// problematic). The current cache implementation looks at the
|
||||
// predecessor entry to find out if the current value is monotonic.
|
||||
// This is where the bit trick is used, since each step in the binary
|
||||
// search is adding or ommiting to add a decreasing power of 2 to the query
|
||||
// value, we can remove the least significant set bit to find the
|
||||
// predecessor in constant time. Ultimately since the search is rate
|
||||
// limited, we could also easily afford to compute this in linear time
|
||||
// by iterating over cached values. So this trick is not crucial to the
|
||||
// cache's performance.
|
||||
type githubCachedSearch struct {
|
||||
cache map[uint64]uint64
|
||||
gcl GhClient
|
||||
baseRequest request
|
||||
}
|
||||
|
||||
func newCache(client GhClient, query Query) githubCachedSearch {
|
||||
return githubCachedSearch{
|
||||
cache: map[uint64]uint64{
|
||||
0: 0,
|
||||
},
|
||||
gcl: client,
|
||||
baseRequest: client.CodeSearchRequestWith(query),
|
||||
}
|
||||
}
|
||||
|
||||
func (c githubCachedSearch) CountResults(upperBound uint64) (uint64, error) {
|
||||
count, cached := c.cache[upperBound]
|
||||
if cached {
|
||||
return count, nil
|
||||
}
|
||||
|
||||
sizeRange := RangeWithin{0, upperBound}
|
||||
rangeRequest := c.RequestString(sizeRange)
|
||||
|
||||
result := c.gcl.parseGithubResponse(rangeRequest)
|
||||
if result.Error != nil {
|
||||
return count, result.Error
|
||||
}
|
||||
|
||||
// As range search uses powers of 2 for binary search, the previously
|
||||
// cached value is easy to find by removing the least significant set
|
||||
// bit from the current upperBound, since each step of the search adds
|
||||
// least significant set bit.
|
||||
//
|
||||
// Finding the predecessor could also be implemented by iterating over
|
||||
// the map to find the largest key that is smaller than upperBound if
|
||||
// this approach deemed too complex.
|
||||
trail := bits.TrailingZeros64(upperBound)
|
||||
prev := uint64(0)
|
||||
if trail != 64 {
|
||||
prev = upperBound - (1 << uint64(trail))
|
||||
}
|
||||
|
||||
// Sometimes the github API is not monotonically increasing, or ouputs
|
||||
// an erroneous value of 0, or 1. This logic makes sure that it was not
|
||||
// erroneous, and that the sequence continues to be monotonic by setting
|
||||
// the current query count to match the previous value. which at least
|
||||
// guarantees that the range search terminates.
|
||||
//
|
||||
// On the other hand, if files are added, then we way loose out on some
|
||||
// files in a reviously completed range, but these files should be there
|
||||
// the next time the crawler runs, so this is not really problematic.
|
||||
retryMonotonicCount := 4
|
||||
for result.Parsed.TotalCount < c.cache[prev] {
|
||||
logger.Printf(
|
||||
"Retrying query... current lower bound: %d, got: %d\n",
|
||||
c.cache[prev], result.Parsed.TotalCount)
|
||||
|
||||
result = c.gcl.parseGithubResponse(rangeRequest)
|
||||
if result.Error != nil {
|
||||
return count, result.Error
|
||||
}
|
||||
|
||||
retryMonotonicCount--
|
||||
if retryMonotonicCount <= 0 {
|
||||
result.Parsed.TotalCount = c.cache[prev]
|
||||
logger.Println(
|
||||
"Retries for monotonic check exceeded,",
|
||||
" setting value to match predecessor")
|
||||
}
|
||||
}
|
||||
|
||||
count = result.Parsed.TotalCount
|
||||
logger.Printf("Caching new query %s, with count %d\n",
|
||||
sizeRange.RangeString(), count)
|
||||
c.cache[upperBound] = count
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (c githubCachedSearch) RequestString(filesize rangeFormatter) string {
|
||||
return c.baseRequest.CopyWith(Filesize(filesize)).URL()
|
||||
}
|
||||
|
||||
// Outputs a (possibly incomplete) list of ranges to query to find most search
|
||||
// results as permissible by the search github search API. Github search only
|
||||
// allows 1,000 results per query (paginated).
|
||||
// Source: https://developer.github.com/v3/search/
|
||||
//
|
||||
// This leaves the possibility of having file sizes with more than 1000 results,
|
||||
// This would mean that the search as it is could not find all files. If queries
|
||||
// are sorted by last indexed, and retrieved on regular intervals, it should be
|
||||
// sufficient to get most if not all documents.
|
||||
func FindRangesForRepoSearch(cache cachedSearch) ([]string, error) {
|
||||
totalFiles, err := cache.CountResults(githubMaxFileSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logger.Println("total kustomization files: ", totalFiles)
|
||||
|
||||
if githubMaxResultsPerQuery >= totalFiles {
|
||||
return []string{
|
||||
cache.RequestString(RangeWithin{0, githubMaxFileSize}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Find all the ranges of file sizes such that all files are queryable
|
||||
// using the Github API. This does not compute an optimal ranges, since
|
||||
// the number of queries needed to get the information required to
|
||||
// compute an optimal range is expected to be much larger than the
|
||||
// number of queries performed this way.
|
||||
//
|
||||
// The number of ranges is k = (number of files)/1000, and finding a
|
||||
// range is logarithmic in the max file size (n = filesize). This means
|
||||
// that preprocessing takes O(k * lg n) queries to find the ranges with
|
||||
// a binary search over file sizes.
|
||||
//
|
||||
// My intuition is that this approach is competitive to a perfectly
|
||||
// optimal solution, but I didn't actually take the time to do a
|
||||
// rigorous proof. Intuitively, since files sizes are typically power
|
||||
// law distibuted the binary search will be very skewed towards the
|
||||
// smaller file ranges. This means that in practice this approach will
|
||||
// make fewer than (#files/1000)*(log(n) = 19) queries for
|
||||
// preprocessing, since it reuses a lot of the queries in the denser
|
||||
// ranges. Furthermore, because of the distribution, it should be very
|
||||
// easy to find ranges that are very close to the upper bound, up to
|
||||
// the limiting factor of having no more than 1000 files accessible per
|
||||
// range.
|
||||
filesAccessible := uint64(0)
|
||||
sizes := make([]uint64, 0)
|
||||
for filesAccessible < totalFiles {
|
||||
target := filesAccessible + githubMaxResultsPerQuery
|
||||
if target >= totalFiles {
|
||||
break
|
||||
}
|
||||
|
||||
logger.Printf("%d accessible files, next target = %d\n",
|
||||
filesAccessible, target)
|
||||
|
||||
cur, err := lowerBoundFileCount(cache, target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If there are more than 1000 files in the next bucket, we must
|
||||
// advance anyway and lose out on some files :(.
|
||||
if l := len(sizes); l > 0 && sizes[l-1] == cur {
|
||||
cur++
|
||||
}
|
||||
|
||||
nextAccessible, err := cache.CountResults(cur)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"cache should be populated at %d already, got %v",
|
||||
cur, err)
|
||||
}
|
||||
if nextAccessible < filesAccessible {
|
||||
return nil, fmt.Errorf(
|
||||
"number of results dropped from %d to %d within range search",
|
||||
filesAccessible, nextAccessible)
|
||||
}
|
||||
|
||||
filesAccessible = nextAccessible
|
||||
if nextAccessible < totalFiles {
|
||||
sizes = append(sizes, cur)
|
||||
}
|
||||
}
|
||||
|
||||
return formatFilesizeRanges(cache, sizes), nil
|
||||
}
|
||||
|
||||
// lowerBoundFileCount finds the filesize range from [0, return value] that has
|
||||
// the largest file count that is smaller than or equal to
|
||||
// githubMaxResultsPerQuery. It is important to note that this returned value
|
||||
// could already be in a previous range if the next file size has more than 1000
|
||||
// results. It is left to the caller to handle this bit of logic and guarantee
|
||||
// forward progession in this case.
|
||||
func lowerBoundFileCount(
|
||||
cache cachedSearch, targetFileCount uint64) (uint64, error) {
|
||||
|
||||
// Binary search for file sizes that make up the next <=1000 element
|
||||
// chunk.
|
||||
cur := uint64(0)
|
||||
increase := githubMaxFileSize / 2
|
||||
|
||||
for increase > 0 {
|
||||
mid := cur + increase
|
||||
|
||||
count, err := cache.CountResults(mid)
|
||||
if err != nil {
|
||||
return count, err
|
||||
}
|
||||
|
||||
if count <= targetFileCount {
|
||||
cur = mid
|
||||
}
|
||||
|
||||
if count == targetFileCount {
|
||||
break
|
||||
}
|
||||
|
||||
increase /= 2
|
||||
}
|
||||
|
||||
return cur, nil
|
||||
}
|
||||
|
||||
func formatFilesizeRanges(cache cachedSearch, sizes []uint64) []string {
|
||||
ranges := make([]string, 0, len(sizes)+1)
|
||||
|
||||
if len(sizes) > 0 {
|
||||
ranges = append(ranges, cache.RequestString(
|
||||
RangeLessThan{sizes[0] + 1},
|
||||
))
|
||||
}
|
||||
|
||||
for i := 0; i < len(sizes)-1; i += 1 {
|
||||
ranges = append(ranges, cache.RequestString(
|
||||
RangeWithin{sizes[i] + 1, sizes[i+1]},
|
||||
))
|
||||
|
||||
if i != len(sizes)-2 {
|
||||
continue
|
||||
}
|
||||
ranges = append(ranges, cache.RequestString(
|
||||
RangeGreaterThan{sizes[i+1]},
|
||||
))
|
||||
}
|
||||
|
||||
return ranges
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type testCachedSearch struct {
|
||||
cache map[uint64]uint64
|
||||
}
|
||||
|
||||
func (c testCachedSearch) CountResults(upperBound uint64) (uint64, error) {
|
||||
fmt.Printf("CountResults(%05x)\n", upperBound)
|
||||
count, ok := c.cache[upperBound]
|
||||
if !ok {
|
||||
return count, fmt.Errorf("cache not set at %x", upperBound)
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (c testCachedSearch) RequestString(filesize rangeFormatter) string {
|
||||
return filesize.RangeString()
|
||||
}
|
||||
|
||||
// TODO(damienr74) make tests easier to write.. I'm thinking I can make the test
|
||||
// cache take in a list of (filesize, count) pairs and it can populate the cache
|
||||
// without relying on how the implementation will create queries. This was only
|
||||
// a quick and dirty test to make sure that modifications are not going to break
|
||||
// the functionality.
|
||||
func TestRangeSplitting(t *testing.T) {
|
||||
// Keys follow the binary search depending on whether or not the range
|
||||
// is too small/large to find close to optimal filesize ranges. This
|
||||
// test is heavily tied to the fact that the search is using powers of two
|
||||
// to make progress in the search (hence the use of hexadecimal values).
|
||||
cache := testCachedSearch{
|
||||
map[uint64]uint64{
|
||||
0x80000: 5000,
|
||||
0x40000: 5000,
|
||||
0x20000: 5000,
|
||||
0x10000: 5000,
|
||||
0x08000: 5000,
|
||||
0x04000: 5000,
|
||||
0x02000: 5000,
|
||||
0x01000: 5000,
|
||||
0x00fff: 3950,
|
||||
0x00ffe: 3950,
|
||||
0x00ffc: 3950,
|
||||
0x00ff8: 3950,
|
||||
0x00ff0: 3950,
|
||||
0x00fe0: 3950,
|
||||
0x00fc0: 3950,
|
||||
0x00f80: 3950,
|
||||
0x00f00: 3950,
|
||||
0x00e00: 3950,
|
||||
0x00c00: 3950,
|
||||
0x00800: 3950,
|
||||
0x00400: 3950,
|
||||
0x00200: 3688,
|
||||
0x00180: 3028,
|
||||
0x00100: 2999,
|
||||
0x000c0: 2448,
|
||||
0x00080: 1999,
|
||||
0x00070: 1600,
|
||||
0x0006c: 1003,
|
||||
0x0006b: 1001,
|
||||
0x0006a: 999,
|
||||
0x00068: 999,
|
||||
0x00060: 999,
|
||||
0x00040: 999,
|
||||
0x00000: 0,
|
||||
},
|
||||
}
|
||||
|
||||
requests, err := FindRangesForRepoSearch(cache)
|
||||
if err != nil {
|
||||
t.Errorf("Error while finding ranges: %v", err)
|
||||
}
|
||||
expected := []string{
|
||||
"<107", // cache.RequestString(RangeLessThan{0x6b}),
|
||||
"107..128", // cache.RequestString(RangeWithin{0x6b, 0x80}),
|
||||
"129..256", // cache.RequestString(RangeWithin{0x81, 0x100}),
|
||||
"257..4095", // cache.RequestString(RangeWithin{0x101, 0xfff}),
|
||||
">4095", // cache.RequestString(RangeGreaterThan{0xfff}),
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(requests, expected) {
|
||||
t.Errorf("Expected requests (%v) to equal (%v)", requests, expected)
|
||||
}
|
||||
}
|
||||
213
api/internal/crawl/doc/doc.go
Normal file
213
api/internal/crawl/doc/doc.go
Normal file
@@ -0,0 +1,213 @@
|
||||
package doc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/k8sdeps/kunstruct"
|
||||
"sigs.k8s.io/kustomize/api/konfig"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
var fileReader = kunstruct.NewKunstructuredFactoryImpl()
|
||||
|
||||
// This document is meant to be used at the elasticsearch document type.
|
||||
// Fields are serialized as-is to elasticsearch, where indices are built
|
||||
// to facilitate text search queries. Identifiers, Values, FilePath,
|
||||
// RepositoryURL and DocumentData are meant to be searched for text queries
|
||||
// directly, while the other fields can either be used as a filter, or as
|
||||
// additional metadata displayed in the UI.
|
||||
//
|
||||
// The fields of the document and their purpose are listed below:
|
||||
// - DocumentData contains the contents of the kustomization file.
|
||||
// - Kinds Represents the kubernetes Kinds that are in this file.
|
||||
// - Identifiers are a list of (partial and full) identifier paths that can be
|
||||
// found by users. Each part of a path is delimited by ":" e.g. spec:replicas.
|
||||
// - Values are a list of identifier paths and their values that can be found by
|
||||
// search queries. The path is delimited by ":" and the value follows the "="
|
||||
// symbol e.g. spec:replicas=4.
|
||||
// - FilePath is the path of the file.
|
||||
// - RepositoryURL is the URL of the source repository.
|
||||
// - CreationTime is the time at which the file was created.
|
||||
//
|
||||
// Representing each Identifier and Value as a flat string representation
|
||||
// facilitates the use of complex text search features from elasticsearch such
|
||||
// as fuzzy searching, regex, wildcards, etc.
|
||||
type KustomizationDocument struct {
|
||||
Document
|
||||
Kinds []string `json:"kinds,omitempty"`
|
||||
Identifiers []string `json:"identifiers,omitempty"`
|
||||
Values []string `json:"values,omitempty"`
|
||||
}
|
||||
|
||||
type set map[string]struct{}
|
||||
|
||||
func (doc *KustomizationDocument) String() string {
|
||||
return fmt.Sprintf("%s %s %s %v %v %v len(identifiers):%v len(values):%v",
|
||||
doc.RepositoryURL, doc.FilePath, doc.DefaultBranch, doc.CreationTime,
|
||||
doc.IsSame, doc.Kinds, len(doc.Identifiers), len(doc.Values))
|
||||
}
|
||||
|
||||
// Implements the CrawlerDocument interface.
|
||||
func (doc *KustomizationDocument) GetResources() ([]*Document, error) {
|
||||
isResource := true
|
||||
for _, suffix := range konfig.RecognizedKustomizationFileNames() {
|
||||
if strings.HasSuffix(doc.FilePath, "/"+suffix) {
|
||||
isResource = false
|
||||
}
|
||||
}
|
||||
if isResource {
|
||||
return []*Document{}, nil
|
||||
}
|
||||
|
||||
content := []byte(doc.DocumentData)
|
||||
content, err := FixKustomizationPreUnmarshallingNonFatal(content)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not fix kustomize file: %v", err)
|
||||
}
|
||||
|
||||
var k types.Kustomization
|
||||
err = yaml.Unmarshal(content, &k)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"could not parse kustomization: %v", err)
|
||||
}
|
||||
k.FixKustomizationPostUnmarshalling()
|
||||
|
||||
res := make([]*Document, 0, len(k.Resources))
|
||||
for _, r := range k.Resources {
|
||||
next, err := doc.Document.FromRelativePath(r)
|
||||
if err != nil {
|
||||
fmt.Printf("GetResources error: %v\n", err)
|
||||
continue
|
||||
}
|
||||
res = append(res, &next)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (doc *KustomizationDocument) readBytes() ([]map[string]interface{}, error) {
|
||||
data := []byte(doc.DocumentData)
|
||||
|
||||
for _, suffix := range konfig.RecognizedKustomizationFileNames() {
|
||||
if !strings.HasSuffix(doc.FilePath, "/"+suffix) {
|
||||
continue
|
||||
}
|
||||
var config map[string]interface{}
|
||||
err := yaml.Unmarshal(data, &config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"unable to parse kustomization: %v", err)
|
||||
}
|
||||
return []map[string]interface{}{config}, nil
|
||||
}
|
||||
|
||||
configs := make([]map[string]interface{}, 0)
|
||||
ks, err := fileReader.SliceFromBytes(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse resource: %v", err)
|
||||
}
|
||||
for _, k := range ks {
|
||||
configs = append(configs, k.Map())
|
||||
}
|
||||
|
||||
return configs, nil
|
||||
}
|
||||
|
||||
func (doc *KustomizationDocument) ParseYAML() error {
|
||||
doc.Identifiers = make([]string, 0)
|
||||
doc.Values = make([]string, 0)
|
||||
doc.Kinds = make([]string, 0, 1)
|
||||
|
||||
identifierSet := make(set)
|
||||
valueSet := make(set)
|
||||
kindSet := make(set)
|
||||
|
||||
getKind := func(m map[string]interface{}) string {
|
||||
const defaultStr = "Kustomization"
|
||||
kind, ok := m["kind"]
|
||||
if !ok {
|
||||
return defaultStr
|
||||
}
|
||||
if str, ok := kind.(string); ok && str != "" {
|
||||
return str
|
||||
}
|
||||
return defaultStr
|
||||
}
|
||||
|
||||
ks, err := doc.readBytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, contents := range ks {
|
||||
kindSet[getKind(contents)] = struct{}{}
|
||||
createFlatStructure(identifierSet, valueSet, contents)
|
||||
}
|
||||
|
||||
for val := range kindSet {
|
||||
doc.Kinds = append(doc.Kinds, val)
|
||||
}
|
||||
|
||||
for val := range valueSet {
|
||||
doc.Values = append(doc.Values, val)
|
||||
}
|
||||
|
||||
for key := range identifierSet {
|
||||
doc.Identifiers = append(doc.Identifiers, key)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createFlatStructure(identifierSet set, valueSet set, contents map[string]interface{}) {
|
||||
type Map struct {
|
||||
data map[string]interface{}
|
||||
prefix string
|
||||
}
|
||||
|
||||
toVisit := []Map{
|
||||
{
|
||||
data: contents,
|
||||
prefix: "",
|
||||
},
|
||||
}
|
||||
|
||||
for i := 0; i < len(toVisit); i++ {
|
||||
visiting := toVisit[i]
|
||||
for k, v := range visiting.data {
|
||||
identifier := fmt.Sprintf("%s:%s", visiting.prefix, k)
|
||||
// noop after the first iteration.
|
||||
identifier = strings.TrimLeft(identifier, ":")
|
||||
|
||||
// Recursive function traverses structure to find
|
||||
// identifiers and values. These later get formatted
|
||||
// into doc.Identifiers and doc.Values respectively.
|
||||
var traverseStructure func(interface{})
|
||||
traverseStructure = func(arg interface{}) {
|
||||
switch value := arg.(type) {
|
||||
case map[string]interface{}:
|
||||
toVisit = append(toVisit, Map{
|
||||
data: value,
|
||||
prefix: identifier,
|
||||
})
|
||||
case []interface{}:
|
||||
for _, val := range value {
|
||||
traverseStructure(val)
|
||||
}
|
||||
case interface{}:
|
||||
esc := fmt.Sprintf("%v", value)
|
||||
|
||||
valuePath := fmt.Sprintf("%s=%v",
|
||||
identifier, esc)
|
||||
valueSet[valuePath] = struct{}{}
|
||||
}
|
||||
}
|
||||
traverseStructure(v)
|
||||
|
||||
identifierSet[identifier] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
286
api/internal/crawl/doc/doc_test.go
Normal file
286
api/internal/crawl/doc/doc_test.go
Normal file
@@ -0,0 +1,286 @@
|
||||
package doc
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseYAML(t *testing.T) {
|
||||
testCases := []struct {
|
||||
identifiers []string
|
||||
values []string
|
||||
kinds []string
|
||||
filepath string
|
||||
yaml string
|
||||
}{
|
||||
{
|
||||
identifiers: []string{
|
||||
"namePrefix",
|
||||
"metadata",
|
||||
"metadata:name",
|
||||
"kind",
|
||||
},
|
||||
values: []string{
|
||||
"kind=",
|
||||
"namePrefix=dev-",
|
||||
"metadata:name=app",
|
||||
},
|
||||
kinds: []string{
|
||||
"Kustomization",
|
||||
},
|
||||
filepath: "some/path/to/kustomization.yaml",
|
||||
yaml: `
|
||||
namePrefix: dev-
|
||||
metadata:
|
||||
name: app
|
||||
kind: ""
|
||||
`,
|
||||
},
|
||||
{
|
||||
identifiers: []string{
|
||||
"namePrefix",
|
||||
"metadata",
|
||||
"metadata:name",
|
||||
"metadata:spec",
|
||||
"metadata:spec:replicas",
|
||||
"kind",
|
||||
"replicas",
|
||||
"replicas:name",
|
||||
"replicas:count",
|
||||
"resource",
|
||||
},
|
||||
values: []string{
|
||||
"namePrefix=dev-",
|
||||
"metadata:name=n1",
|
||||
"metadata:spec:replicas=3",
|
||||
"kind=Kustomization",
|
||||
"replicas:name=n1",
|
||||
"replicas:name=n2",
|
||||
"replicas:count=3",
|
||||
"resource=file1.yaml",
|
||||
"resource=file2.yaml",
|
||||
},
|
||||
kinds: []string{
|
||||
"Kustomization",
|
||||
},
|
||||
filepath: "./kustomization.yaml",
|
||||
yaml: `
|
||||
namePrefix: dev-
|
||||
# map of map
|
||||
metadata:
|
||||
name: n1
|
||||
spec:
|
||||
replicas: 3
|
||||
kind: Kustomization
|
||||
|
||||
#list of map
|
||||
replicas:
|
||||
- name: n1
|
||||
count: 3
|
||||
- name: n2
|
||||
count: 3
|
||||
|
||||
# list
|
||||
resource:
|
||||
- file1.yaml
|
||||
- file2.yaml
|
||||
`,
|
||||
},
|
||||
{
|
||||
identifiers: []string{
|
||||
"kind",
|
||||
"metadata",
|
||||
"metadata:name",
|
||||
},
|
||||
values: []string{
|
||||
"kind=Deployment",
|
||||
"kind=Service",
|
||||
"kind=Custom",
|
||||
"metadata:name=app",
|
||||
"metadata:name=app-service",
|
||||
"metadata:name=app-crd",
|
||||
},
|
||||
kinds: []string{
|
||||
"Deployment",
|
||||
"Service",
|
||||
"Custom",
|
||||
},
|
||||
filepath: "resources.yaml",
|
||||
yaml: `
|
||||
---
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: app
|
||||
---
|
||||
kind: Service
|
||||
metadata:
|
||||
name: app-service
|
||||
---
|
||||
kind: Custom
|
||||
metadata:
|
||||
name: app-crd
|
||||
`,
|
||||
},
|
||||
{
|
||||
identifiers: []string{
|
||||
"kind",
|
||||
"metadata",
|
||||
"metadata:name",
|
||||
},
|
||||
values: []string{
|
||||
"kind=Deployment",
|
||||
"kind=Service",
|
||||
"metadata:name=app1",
|
||||
"metadata:name=app2",
|
||||
},
|
||||
kinds: []string{
|
||||
"Deployment",
|
||||
"Service",
|
||||
},
|
||||
filepath: "resources.yaml",
|
||||
yaml: `
|
||||
---
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: app1
|
||||
---
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: app2
|
||||
---
|
||||
kind: Service
|
||||
metadata:
|
||||
name: app1
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
doc := KustomizationDocument{
|
||||
Document: Document{
|
||||
DocumentData: test.yaml,
|
||||
FilePath: test.filepath,
|
||||
},
|
||||
}
|
||||
|
||||
err := doc.ParseYAML()
|
||||
if err != nil {
|
||||
t.Errorf("Document error error: %s", err)
|
||||
}
|
||||
|
||||
cmpStrings := func(got, expected []string, label string) {
|
||||
sort.Strings(got)
|
||||
sort.Strings(expected)
|
||||
|
||||
if !reflect.DeepEqual(got, expected) {
|
||||
t.Errorf("Expected %s (%v) to be equal to (%v)\n",
|
||||
label,
|
||||
strings.Join(got, ","),
|
||||
strings.Join(expected, ","))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
cmpStrings(doc.Identifiers, test.identifiers, "identifiers")
|
||||
cmpStrings(doc.Values, test.values, "values")
|
||||
cmpStrings(doc.Kinds, test.kinds, "kinds")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetResources(t *testing.T) {
|
||||
tests := []struct {
|
||||
doc KustomizationDocument
|
||||
resources []*Document
|
||||
}{
|
||||
{
|
||||
doc: KustomizationDocument{
|
||||
Document: Document{
|
||||
RepositoryURL: "sigs.k8s.io/kustomize",
|
||||
FilePath: "some/path/to/kdir/kustomization.yaml",
|
||||
DocumentData: `
|
||||
bases:
|
||||
- ../base
|
||||
- ../otherbase
|
||||
|
||||
resources:
|
||||
- file.yaml
|
||||
- https://github.com/kubernetes-sigs/kustomize/examples/helloWorld?ref=v3.1.0
|
||||
`},
|
||||
},
|
||||
resources: []*Document{
|
||||
{
|
||||
RepositoryURL: "sigs.k8s.io/kustomize",
|
||||
FilePath: "some/path/to/base",
|
||||
},
|
||||
{
|
||||
RepositoryURL: "sigs.k8s.io/kustomize",
|
||||
FilePath: "some/path/to/otherbase",
|
||||
},
|
||||
{
|
||||
RepositoryURL: "sigs.k8s.io/kustomize",
|
||||
FilePath: "some/path/to/kdir/file.yaml",
|
||||
},
|
||||
{
|
||||
RepositoryURL: "https://github.com/kubernetes-sigs/kustomize",
|
||||
FilePath: "examples/helloWorld",
|
||||
DefaultBranch: "v3.1.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
doc: KustomizationDocument{
|
||||
Document: Document{
|
||||
RepositoryURL: "https://github.com/some/repo",
|
||||
FilePath: "some/resource.yaml",
|
||||
DocumentData: `
|
||||
bases:
|
||||
- ../base
|
||||
- ../overlay
|
||||
|
||||
resources:
|
||||
- https://github.com/kubernetes-sigs/kustomize/examples/helloWorld?ref=v3.1.0
|
||||
- some/file.yaml
|
||||
`,
|
||||
},
|
||||
},
|
||||
resources: []*Document{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
res, err := test.doc.GetResources()
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v\n", err)
|
||||
continue
|
||||
}
|
||||
if len(test.resources) != len(res) {
|
||||
t.Errorf("Number of resources does not match.")
|
||||
continue
|
||||
}
|
||||
cmp := func(docs []*Document) func(i, j int) bool {
|
||||
return func(i, j int) bool {
|
||||
if docs[i].RepositoryURL != docs[j].RepositoryURL {
|
||||
return docs[i].RepositoryURL <
|
||||
docs[j].RepositoryURL
|
||||
}
|
||||
|
||||
if docs[i].FilePath != docs[j].FilePath {
|
||||
return docs[i].FilePath <
|
||||
docs[j].FilePath
|
||||
}
|
||||
|
||||
return docs[i].DefaultBranch < docs[j].DefaultBranch
|
||||
}
|
||||
}
|
||||
sort.Slice(test.resources, cmp(test.resources))
|
||||
sort.Slice(res, cmp(res))
|
||||
for i, r := range test.resources {
|
||||
if !reflect.DeepEqual(res[i], r) {
|
||||
t.Errorf("Expected '%+v' to equal '%+v'\n",
|
||||
res[i], r)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
88
api/internal/crawl/doc/docname.go
Normal file
88
api/internal/crawl/doc/docname.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package doc
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/internal/git"
|
||||
)
|
||||
|
||||
type Document struct {
|
||||
RepositoryURL string `json:"repositoryUrl,omitempty"`
|
||||
FilePath string `json:"filePath,omitempty"`
|
||||
DefaultBranch string `json:"defaultBranch,omitempty"`
|
||||
DocumentData string `json:"document,omitempty"`
|
||||
CreationTime *time.Time `json:"creationTime,omitempty"`
|
||||
IsSame bool `json:"-"`
|
||||
}
|
||||
|
||||
// Implements the CrawlerDocument interface.
|
||||
func (doc *Document) GetDocument() *Document {
|
||||
return doc
|
||||
}
|
||||
|
||||
func (doc *Document) Copy() *Document {
|
||||
return &Document{
|
||||
RepositoryURL: doc.RepositoryURL,
|
||||
FilePath: doc.FilePath,
|
||||
DefaultBranch: doc.DefaultBranch,
|
||||
DocumentData: doc.DocumentData,
|
||||
CreationTime: doc.CreationTime,
|
||||
IsSame: doc.IsSame,
|
||||
}
|
||||
}
|
||||
|
||||
// Implements the CrawlerDocument interface.
|
||||
func (doc *Document) WasCached() bool {
|
||||
return doc.IsSame
|
||||
}
|
||||
|
||||
func (doc *Document) FromRelativePath(newFile string) (Document, error) {
|
||||
repoSpec, err := git.NewRepoSpecFromUrl(newFile)
|
||||
if err == nil {
|
||||
return Document{
|
||||
RepositoryURL: repoSpec.Host + path.Clean(repoSpec.OrgRepo),
|
||||
FilePath: path.Clean(repoSpec.Path),
|
||||
DefaultBranch: repoSpec.Ref,
|
||||
}, nil
|
||||
}
|
||||
// else document is probably relative path.
|
||||
|
||||
ret := Document{
|
||||
RepositoryURL: doc.RepositoryURL,
|
||||
DefaultBranch: doc.DefaultBranch,
|
||||
}
|
||||
ogDir, _ := path.Split(doc.FilePath)
|
||||
|
||||
cleaned := path.Clean(newFile)
|
||||
if !path.IsAbs(cleaned) {
|
||||
cleaned = path.Clean(ogDir + "/" + cleaned)
|
||||
}
|
||||
|
||||
ret.FilePath = cleaned
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (doc *Document) ID() string {
|
||||
sum := sha256.Sum256([]byte(strings.Join(
|
||||
[]string{
|
||||
doc.RepositoryURL,
|
||||
doc.DefaultBranch,
|
||||
doc.FilePath,
|
||||
},
|
||||
"---|---")))
|
||||
return fmt.Sprintf("%x", sum)
|
||||
}
|
||||
|
||||
func (doc *Document) RepositoryFullName() string {
|
||||
doc.RepositoryURL = strings.TrimRight(doc.RepositoryURL, "/")
|
||||
sections := strings.Split(doc.RepositoryURL, "/")
|
||||
l := len(sections)
|
||||
if l < 2 {
|
||||
return doc.RepositoryURL
|
||||
}
|
||||
return path.Join(sections[l-2], sections[l-1])
|
||||
}
|
||||
105
api/internal/crawl/doc/docname_test.go
Normal file
105
api/internal/crawl/doc/docname_test.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package doc
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFromRelativePath(t *testing.T) {
|
||||
type Case struct {
|
||||
RelativePath string
|
||||
Expected Document
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
BaseDoc Document
|
||||
Cases []Case
|
||||
}{
|
||||
{
|
||||
BaseDoc: Document{
|
||||
RepositoryURL: "example.com/repo",
|
||||
FilePath: "path/to/file/kustomization.yaml",
|
||||
DefaultBranch: "master",
|
||||
},
|
||||
Cases: []Case{
|
||||
{
|
||||
RelativePath: "../other/file/resource.yaml",
|
||||
Expected: Document{
|
||||
RepositoryURL: "example.com/repo",
|
||||
FilePath: "path/to/other/file/resource.yaml",
|
||||
DefaultBranch: "master",
|
||||
},
|
||||
},
|
||||
{
|
||||
RelativePath: "../file/../../something/../to/other/file/patch.yaml",
|
||||
Expected: Document{
|
||||
RepositoryURL: "example.com/repo",
|
||||
FilePath: "path/to/other/file/patch.yaml",
|
||||
DefaultBranch: "master",
|
||||
},
|
||||
},
|
||||
{
|
||||
RelativePath: "service.yaml",
|
||||
Expected: Document{
|
||||
RepositoryURL: "example.com/repo",
|
||||
FilePath: "path/to/file/service.yaml",
|
||||
DefaultBranch: "master",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
for _, c := range tc.Cases {
|
||||
rd, err := tc.BaseDoc.FromRelativePath(c.RelativePath)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(rd, c.Expected) {
|
||||
t.Errorf("document mismatch expected %v, got %v", c.Expected, rd)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDocument_RepositoryFullName(t *testing.T) {
|
||||
testCases := []struct {
|
||||
doc Document
|
||||
expectedRepositoryFullName string
|
||||
}{
|
||||
{
|
||||
doc: Document{
|
||||
RepositoryURL: "https://github.com/user/repo",
|
||||
},
|
||||
expectedRepositoryFullName: "user/repo",
|
||||
},
|
||||
{
|
||||
doc: Document{
|
||||
RepositoryURL: "https://github.com//user/repo////",
|
||||
},
|
||||
expectedRepositoryFullName: "user/repo",
|
||||
},
|
||||
{
|
||||
doc: Document{
|
||||
RepositoryURL: "repo/",
|
||||
},
|
||||
expectedRepositoryFullName: "repo",
|
||||
},
|
||||
{
|
||||
doc: Document{
|
||||
RepositoryURL: "",
|
||||
},
|
||||
expectedRepositoryFullName: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
returnedRepositoryFullName := tc.doc.RepositoryFullName()
|
||||
if returnedRepositoryFullName != tc.expectedRepositoryFullName {
|
||||
t.Errorf("RepositoryFullName expected %s, got %s",
|
||||
tc.expectedRepositoryFullName,
|
||||
returnedRepositoryFullName)
|
||||
}
|
||||
}
|
||||
}
|
||||
51
api/internal/crawl/doc/kustomize_util.go
Normal file
51
api/internal/crawl/doc/kustomize_util.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package doc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
func FixKustomizationPreUnmarshallingNonFatal(data []byte) ([]byte, error) {
|
||||
deprecateFieldsMap := map[string]string{
|
||||
"imageTags:": "images:",
|
||||
}
|
||||
for oldname, newname := range deprecateFieldsMap {
|
||||
pattern := regexp.MustCompile(oldname)
|
||||
data = pattern.ReplaceAll(data, []byte(newname))
|
||||
}
|
||||
|
||||
found, err := useLegacyPatch(data)
|
||||
if err == nil && found {
|
||||
pattern := regexp.MustCompile("patches:")
|
||||
data = pattern.ReplaceAll(data, []byte("patchesStrategicMerge:"))
|
||||
}
|
||||
|
||||
return data, err
|
||||
}
|
||||
|
||||
func useLegacyPatch(data []byte) (bool, error) {
|
||||
found := false
|
||||
|
||||
var object map[string]interface{}
|
||||
err := yaml.Unmarshal(data, &object)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("invalid content from %s",
|
||||
string(data))
|
||||
}
|
||||
if rawPatches, ok := object["patches"]; ok {
|
||||
patches, ok := rawPatches.([]interface{})
|
||||
if !ok {
|
||||
return false, fmt.Errorf("invalid patches from %v",
|
||||
rawPatches)
|
||||
}
|
||||
for _, p := range patches {
|
||||
_, ok := p.(string)
|
||||
if ok {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return found, nil
|
||||
}
|
||||
13
api/internal/crawl/go.mod
Normal file
13
api/internal/crawl/go.mod
Normal file
@@ -0,0 +1,13 @@
|
||||
module sigs.k8s.io/kustomize/api/internal/crawl
|
||||
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/elastic/go-elasticsearch/v6 v6.8.2
|
||||
github.com/gomodule/redigo v2.0.0+incompatible
|
||||
github.com/gorilla/mux v1.7.3
|
||||
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79
|
||||
github.com/rs/cors v1.7.0
|
||||
sigs.k8s.io/kustomize/api v0.3.0
|
||||
sigs.k8s.io/yaml v1.1.0
|
||||
)
|
||||
393
api/internal/crawl/go.sum
Normal file
393
api/internal/crawl/go.sum
Normal file
@@ -0,0 +1,393 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
|
||||
github.com/Azure/go-autorest/autorest v0.9.2/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc=
|
||||
github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
|
||||
github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM=
|
||||
github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
|
||||
github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM=
|
||||
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
|
||||
github.com/elastic/go-elasticsearch/v6 v6.8.2 h1:rp5DGrd63V5c6nHLjF6QEXUpZSvs0+QM3ld7m9VhV2g=
|
||||
github.com/elastic/go-elasticsearch/v6 v6.8.2/go.mod h1:UwaDJsD3rWLM5rKNFzv9hgox93HoX8utj1kxD9aFUcI=
|
||||
github.com/elazarl/goproxy v0.0.0-20191011121108-aa519ddbe484/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
|
||||
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
|
||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
github.com/emicklei/go-restful v2.9.6+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M=
|
||||
github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-critic/go-critic v0.3.5-0.20190904082202-d79a9f0c64db/go.mod h1:+sE8vrLDS2M0pZkBk0wy6+nLdKexVDrl/jBqQOTDThA=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
|
||||
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
|
||||
github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
|
||||
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
|
||||
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
|
||||
github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
|
||||
github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
|
||||
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
|
||||
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4=
|
||||
github.com/go-toolsmith/astcopy v1.0.0/go.mod h1:vrgyG+5Bxrnz4MZWPF+pI4R8h3qKRjjyvV/DSez4WVQ=
|
||||
github.com/go-toolsmith/astequal v0.0.0-20180903214952-dcb477bfacd6/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY=
|
||||
github.com/go-toolsmith/astequal v1.0.0/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY=
|
||||
github.com/go-toolsmith/astfmt v0.0.0-20180903215011-8f8ee99c3086/go.mod h1:mP93XdblcopXwlyN4X4uodxXQhldPGZbcEJIimQHrkg=
|
||||
github.com/go-toolsmith/astfmt v1.0.0/go.mod h1:cnWmsOAuq4jJY6Ct5YWlVLmcmLMn1JUPuQIHCY7CJDw=
|
||||
github.com/go-toolsmith/astinfo v0.0.0-20180906194353-9809ff7efb21/go.mod h1:dDStQCHtmZpYOmjRP/8gHHnCCch3Zz3oEgCdZVdtweU=
|
||||
github.com/go-toolsmith/astp v0.0.0-20180903215135-0af7e3c24f30/go.mod h1:SV2ur98SGypH1UjcPpCatrV5hPazG6+IfNHbkDXBRrk=
|
||||
github.com/go-toolsmith/astp v1.0.0/go.mod h1:RSyrtpVlfTFGDYRbrjyWP1pYu//tSFcvdYrA8meBmLI=
|
||||
github.com/go-toolsmith/pkgload v0.0.0-20181119091011-e9e65178eee8/go.mod h1:WoMrjiy4zvdS+Bg6z9jZH82QXwkcgCBX6nOfnmdaHks=
|
||||
github.com/go-toolsmith/pkgload v1.0.0/go.mod h1:5eFArkbO80v7Z0kdngIxsRXRMTaX4Ilcwuh3clNrQJc=
|
||||
github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8=
|
||||
github.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
|
||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4=
|
||||
github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk=
|
||||
github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6/go.mod h1:DbHgvLiFKX1Sh2T1w8Q/h4NAI8MHIpzCdnBUDTXU3I0=
|
||||
github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613/go.mod h1:SyvUF2NxV+sN8upjjeVYr5W7tyxaT1JVtvhKhOn2ii8=
|
||||
github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3/go.mod h1:JXrF4TWy4tXYn62/9x8Wm/K/dm06p8tCKwFRDPZG/1o=
|
||||
github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU=
|
||||
github.com/golangci/gofmt v0.0.0-20181222123516-0b8337e80d98/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU=
|
||||
github.com/golangci/golangci-lint v1.19.1/go.mod h1:2CEc4Fxx3vxDv7g8DyXkHCBF73AOzAymcJAprs2vCps=
|
||||
github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc/go.mod h1:e5tpTHCfVze+7EpLEozzMB3eafxo2KT5veNg1k6byQU=
|
||||
github.com/golangci/lint-1 v0.0.0-20190420132249-ee948d087217/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg=
|
||||
github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o=
|
||||
github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA=
|
||||
github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21/go.mod h1:tf5+bzsHdTM0bsB7+8mt0GUMvjCgwLpTapNZHU8AajI=
|
||||
github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4=
|
||||
github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ=
|
||||
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
|
||||
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
|
||||
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||
github.com/googleapis/gnostic v0.3.0 h1:CcQijm0XKekKjP/YCz28LXVSpgguuB+nCxaSjCe09y0=
|
||||
github.com/googleapis/gnostic v0.3.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||
github.com/gophercloud/gophercloud v0.6.0/go.mod h1:GICNByuaEBibcjmjvI7QvYJSZEbGkcYwAR7EZK2WMqM=
|
||||
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/mux v1.6.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
|
||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/securecookie v0.0.0-20160422134519-667fe4e3466a/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/gorilla/sessions v0.0.0-20160922145804-ca9ada445741/go.mod h1:+WVp8kdw6VhyKExm03PAMRn2ZxnPtm58pV0dBVPdhHE=
|
||||
github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
|
||||
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA=
|
||||
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok=
|
||||
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
|
||||
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/matoous/godox v0.0.0-20190910121045-032ad8106c86/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-ps v0.0.0-20190716172923-621e5597135b/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/monopole/mdrip v1.0.0/go.mod h1:N1/ppRG9CaPeUKAUHZ3dUlfOT81lTpKZLkyhCvTETwM=
|
||||
github.com/mozilla/tls-observatory v0.0.0-20190404164649-a3c1b6cfecfd/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk=
|
||||
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||
github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo=
|
||||
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME=
|
||||
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
|
||||
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/russross/blackfriday v2.0.0+incompatible/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/securego/gosec v0.0.0-20190912120752-140048b2a218/go.mod h1:q6oYAujd2qyeU4cJqIri4LBIgdHXGvxWHZ1E29HNFRE=
|
||||
github.com/shirou/gopsutil v0.0.0-20190901111213-e4ec7b275ada/go.mod h1:WWnYX4lzhCH5h/3YBfyVA3VbLYjlMZZAQcW9ojMexNc=
|
||||
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
|
||||
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
|
||||
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/timakin/bodyclose v0.0.0-20190721030226-87058b9bfcec/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/ultraware/funlen v0.0.2/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA=
|
||||
github.com/ultraware/whitespace v0.0.3/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.2.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s=
|
||||
github.com/valyala/quicktemplate v1.2.0/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4=
|
||||
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190909003024-a7b16738d86b h1:XfVGCX+0T4WOStkaOsJRllbsiImhB2jgVBGc9L0lPGc=
|
||||
golang.org/x/net v0.0.0-20190909003024-a7b16738d86b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190911201528-7ad0cfa0b7b5 h1:SW/0nsKCUaozCUtZTakri5laocGx/5bkDSSLrFUsa5s=
|
||||
golang.org/x/sys v0.0.0-20190911201528-7ad0cfa0b7b5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181117154741-2ddaf7f79a09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190311215038-5c2858a9cfe5/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190521203540-521d6ed310dd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190719005602-e377ae9d6386/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
|
||||
golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911230505-6bfd74cf029c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190912215617-3720d1ec3678/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
||||
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||
k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||
k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
|
||||
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
|
||||
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a h1:UcxjrRMyNx/i/y8G7kPvLyy7rfbeuf1PYyBf973pgyU=
|
||||
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
|
||||
k8s.io/utils v0.0.0-20191030222137-2b95a09bc58d/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
|
||||
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc=
|
||||
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4=
|
||||
mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZIVpwbkw+04kSxk3rAtzlimaUJw=
|
||||
sigs.k8s.io/kustomize/api v0.2.0 h1:e++6JpysnnlUbHmFrv6jvfF5rFlgQ103bS1DO7r5bWA=
|
||||
sigs.k8s.io/kustomize/api v0.2.0/go.mod h1:zVtMg179jW1gr74jo9fc2Ac9dLYLTZZThc3DDb9lDW4=
|
||||
sigs.k8s.io/kustomize/pseudo/k8s v0.1.0 h1:otg4dLFc03c3gzl+2CV8GPGcd1kk8wjXwD+UhhcCn5I=
|
||||
sigs.k8s.io/kustomize/pseudo/k8s v0.1.0/go.mod h1:bl/gVJgYYhJZCZdYU2BfnaKYAlqFkgbJEkpl302jEss=
|
||||
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
|
||||
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
|
||||
23
api/internal/crawl/httpclient/httpclient.go
Normal file
23
api/internal/crawl/httpclient/httpclient.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package httpclient
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gomodule/redigo/redis"
|
||||
"github.com/gregjones/httpcache"
|
||||
rediscache "github.com/gregjones/httpcache/redis"
|
||||
)
|
||||
|
||||
func FromCache(header http.Header) bool {
|
||||
return header.Get(httpcache.XFromCache) != ""
|
||||
}
|
||||
|
||||
func NewClient(conn redis.Conn) *http.Client {
|
||||
etagCache := rediscache.NewWithClient(conn)
|
||||
tr := httpcache.NewTransport(etagCache)
|
||||
return &http.Client{
|
||||
Transport: tr,
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
}
|
||||
298
api/internal/crawl/index/elasticsearch.go
Normal file
298
api/internal/crawl/index/elasticsearch.go
Normal file
@@ -0,0 +1,298 @@
|
||||
package index
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"time"
|
||||
|
||||
es "github.com/elastic/go-elasticsearch/v6"
|
||||
"github.com/elastic/go-elasticsearch/v6/esapi"
|
||||
)
|
||||
|
||||
const IndexConfig = `
|
||||
{
|
||||
"mappings": {
|
||||
"_doc": {
|
||||
"properties": {
|
||||
"repositoryUrl": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"filePath": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"defaultBranch": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"document": {
|
||||
"type": "text"
|
||||
},
|
||||
"creationTime": {
|
||||
"type": "date"
|
||||
},
|
||||
"kinds": {
|
||||
"type": "text"
|
||||
},
|
||||
"identifiers": {
|
||||
"type": "text"
|
||||
},
|
||||
"values": {
|
||||
"type": "text"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
// TODO(damienr74) Split index into reader and writer?
|
||||
type index struct {
|
||||
ctx context.Context
|
||||
client *es.Client
|
||||
name string
|
||||
}
|
||||
|
||||
func newIndex(ctx context.Context, name string) (*index, error) {
|
||||
client, err := es.NewDefaultClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &index{
|
||||
ctx: ctx,
|
||||
client: client,
|
||||
name: name,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type readerFunc func(io.Reader) error
|
||||
|
||||
func ignoreResponseBody(_ io.Reader) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// checks that elastic returned successfully. If it has not, it will read the
|
||||
// body and return it in an error message.
|
||||
//
|
||||
// Otherwise, it will use the readerFunc to read the body. This function is a
|
||||
// mechanism for getting relevant data from the response only if it was successful.
|
||||
func (idx *index) responseErrorOrNil(info string, res *esapi.Response,
|
||||
err error, reader readerFunc) error {
|
||||
|
||||
messageStart := fmt.Sprintf("index %s error: %s", idx.name, info)
|
||||
if err != nil || res == nil {
|
||||
return fmt.Errorf("%s: %v", messageStart, err)
|
||||
}
|
||||
|
||||
defer res.Body.Close()
|
||||
if res.IsError() {
|
||||
return fmt.Errorf("%s: %s", messageStart, res.String())
|
||||
}
|
||||
|
||||
if reader != nil {
|
||||
err = reader(res.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %v", messageStart, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func byteJoin(bts ...interface{}) []byte {
|
||||
ret := make([][]byte, len(bts))
|
||||
for i, v := range bts {
|
||||
switch bt := v.(type) {
|
||||
case []byte:
|
||||
ret[i] = bt
|
||||
case string:
|
||||
ret[i] = []byte(bt)
|
||||
default:
|
||||
ret[i] = []byte(fmt.Sprintf("%v", bt))
|
||||
}
|
||||
}
|
||||
|
||||
return bytes.Join(ret, []byte(` `))
|
||||
}
|
||||
|
||||
// Update the elasticsearch index mappings. (describes how to index/search for the documents).
|
||||
func (idx *index) UpdateMapping(mappings []byte) error {
|
||||
request := byteJoin(`{ "mappings":`, mappings, `}`)
|
||||
|
||||
op := idx.client.Indices.PutMapping
|
||||
res, err := op(
|
||||
bytes.NewReader(request),
|
||||
op.WithContext(idx.ctx),
|
||||
op.WithIndex(idx.name),
|
||||
op.WithIncludeTypeName(true),
|
||||
op.WithPretty(),
|
||||
)
|
||||
|
||||
return idx.responseErrorOrNil(
|
||||
fmt.Sprintf("could not update index mappings '%s'", request),
|
||||
res, err, ignoreResponseBody)
|
||||
}
|
||||
|
||||
// Update the elasticsearch index settings. (describes default parameters and
|
||||
// some analyzer definitions, etc.)
|
||||
func (idx *index) UpdateSetting(settings []byte) error {
|
||||
request := byteJoin(`{ "settings": `, settings, `}`)
|
||||
op := idx.client.Indices.PutSettings
|
||||
res, err := op(
|
||||
bytes.NewReader(request),
|
||||
op.WithContext(idx.ctx),
|
||||
op.WithIndex(idx.name),
|
||||
op.WithPretty(),
|
||||
)
|
||||
|
||||
return idx.responseErrorOrNil(
|
||||
fmt.Sprintf("could not update index settings '%s'", request),
|
||||
res, err, ignoreResponseBody)
|
||||
}
|
||||
|
||||
// Create an index providing the config for both the mappings and the settings.
|
||||
func (idx *index) CreateIndex(config []byte) error {
|
||||
op := idx.client.Indices.Create
|
||||
res, err := op(
|
||||
idx.name,
|
||||
op.WithBody(bytes.NewReader(config)),
|
||||
op.WithContext(idx.ctx),
|
||||
op.WithHuman(),
|
||||
op.WithPretty(),
|
||||
)
|
||||
|
||||
return idx.responseErrorOrNil(
|
||||
fmt.Sprintf("could not create index with config '%s'", config),
|
||||
res, err, ignoreResponseBody)
|
||||
}
|
||||
|
||||
// Delete an index.
|
||||
func (idx *index) DeleteIndex() error {
|
||||
res, err := idx.client.Indices.Delete(
|
||||
[]string{idx.name},
|
||||
)
|
||||
|
||||
return idx.responseErrorOrNil("could not delete index",
|
||||
res, err, ignoreResponseBody)
|
||||
}
|
||||
|
||||
// Insert or update the document by ID.
|
||||
func (idx *index) Put(uniqueID string, doc interface{}) (string, error) {
|
||||
body, err := json.Marshal(doc)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
req := esapi.IndexRequest{
|
||||
Index: idx.name,
|
||||
Body: bytes.NewReader(body),
|
||||
DocumentID: uniqueID,
|
||||
}
|
||||
res, err := req.Do(idx.ctx, idx.client)
|
||||
|
||||
var id string
|
||||
readId := func(reader io.Reader) error {
|
||||
type InsertResult struct {
|
||||
ID string `json:"_id,omitempty"`
|
||||
}
|
||||
var ir InsertResult
|
||||
data, err := ioutil.ReadAll(reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(data, &ir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
id = ir.ID
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// populates the id field.
|
||||
err = idx.responseErrorOrNil("could not insert document",
|
||||
res, err, readId)
|
||||
|
||||
return id, err
|
||||
}
|
||||
|
||||
type scrollUpdater func(string, readerFunc) error
|
||||
|
||||
// Update the scroll for iteration. If no scroll exists, create one.
|
||||
func (idx *index) scrollUpdater(query []byte, batchSize int,
|
||||
timeout time.Duration) scrollUpdater {
|
||||
|
||||
return func(scrollID string, reader readerFunc) error {
|
||||
var res *esapi.Response
|
||||
var err error
|
||||
|
||||
if scrollID == "" {
|
||||
search := idx.client.Search
|
||||
res, err = search(
|
||||
search.WithContext(idx.ctx),
|
||||
search.WithIndex(idx.name),
|
||||
search.WithBody(bytes.NewBuffer(query)),
|
||||
search.WithScroll(timeout),
|
||||
search.WithSize(batchSize),
|
||||
)
|
||||
} else {
|
||||
scroll := idx.client.Scroll
|
||||
res, err = scroll(
|
||||
scroll.WithContext(idx.ctx),
|
||||
scroll.WithScroll(timeout),
|
||||
scroll.WithScrollID(scrollID),
|
||||
)
|
||||
}
|
||||
|
||||
return idx.responseErrorOrNil(
|
||||
fmt.Sprintf("could not scroll for query %s", query),
|
||||
res, err, reader)
|
||||
}
|
||||
}
|
||||
|
||||
// Simple search options. Size is the number of elements to return, From is the
|
||||
// rank of the results according to the query. Used as a simple (stateless)
|
||||
// pagination technique.
|
||||
type SearchOptions struct {
|
||||
Size int
|
||||
From int
|
||||
}
|
||||
|
||||
// Search for a query (json query dsl) with some options, and use the reader func
|
||||
// to extract the response.
|
||||
func (idx *index) Search(query []byte, opts SearchOptions,
|
||||
responseReader readerFunc) error {
|
||||
|
||||
op := idx.client.Search
|
||||
res, err := op(
|
||||
op.WithContext(idx.ctx),
|
||||
op.WithIndex(idx.name),
|
||||
op.WithBody(bytes.NewBuffer(query)),
|
||||
op.WithTrackTotalHits(true),
|
||||
op.WithSize(opts.Size),
|
||||
op.WithFrom(opts.From),
|
||||
op.WithPretty(),
|
||||
)
|
||||
|
||||
return idx.responseErrorOrNil(
|
||||
fmt.Sprintf("could not complete search query %v", query),
|
||||
res, err, responseReader)
|
||||
}
|
||||
|
||||
// Delete an element from elasticsearch by Id.
|
||||
func (idx *index) Delete(id string) error {
|
||||
op := idx.client.Delete
|
||||
res, err := op(
|
||||
idx.name,
|
||||
id,
|
||||
op.WithContext(idx.ctx),
|
||||
op.WithPretty(),
|
||||
)
|
||||
|
||||
return idx.responseErrorOrNil(
|
||||
fmt.Sprintf("could not delete id(%s) from index(%s)", id, idx.name),
|
||||
res, err, ignoreResponseBody)
|
||||
}
|
||||
355
api/internal/crawl/index/kustomize.go
Normal file
355
api/internal/crawl/index/kustomize.go
Normal file
@@ -0,0 +1,355 @@
|
||||
package index
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/internal/crawl/doc"
|
||||
)
|
||||
|
||||
const (
|
||||
AggregationKeyword = "aggs"
|
||||
)
|
||||
|
||||
// Redefinition of Hits structure. Must match the json string of
|
||||
// KustomizeResult.Hits.Hits. Declared as a convenience for iteration.
|
||||
type KustomizeHits []struct {
|
||||
ID string `json:"id"`
|
||||
Document doc.KustomizationDocument `json:"result"`
|
||||
}
|
||||
|
||||
type KustomizeResult struct {
|
||||
ScrollID *string `json:"-"`
|
||||
|
||||
Hits *struct {
|
||||
Total int `json:"total"`
|
||||
Hits []struct {
|
||||
ID string `json:"id"`
|
||||
Document doc.KustomizationDocument `json:"result"`
|
||||
} `json:"hits"`
|
||||
} `json:"hits,omitempty"`
|
||||
|
||||
Aggregations *struct {
|
||||
Timeseries *struct {
|
||||
Buckets []struct {
|
||||
Key string `json:"key"`
|
||||
Count int `json:"count"`
|
||||
} `json:"buckets"`
|
||||
} `json:"timeseries,omitempty"`
|
||||
|
||||
Kinds *struct {
|
||||
OtherCount int `json:"otherResults"`
|
||||
Buckets []struct {
|
||||
Key string `json:"key"`
|
||||
Count int `json:"count"`
|
||||
} `json:"buckets"`
|
||||
} `json:"kinds,omitempty"`
|
||||
} `json:"aggregations,omitempty"`
|
||||
}
|
||||
|
||||
// Elasticsearch has some sometimes inconsistent labels, and some pretty ugly label choices.
|
||||
// However, the structure seems reasonable, so I wanted to use it if possible. This method
|
||||
// needs two copies of the types to make the json strings different. The Copies must be the
|
||||
// exact same type/structure, so the types must be declared inline. Go will check that these
|
||||
// are convertible at compile time, and converting at runtime is a noop.
|
||||
type ElasticKustomizeResult struct {
|
||||
ScrollID *string `json:"_scroll_id,omitempty"`
|
||||
|
||||
Hits *struct {
|
||||
Total int `json:"total"`
|
||||
Hits []struct {
|
||||
ID string `json:"_id"`
|
||||
Document doc.KustomizationDocument `json:"_source"`
|
||||
} `json:"hits"`
|
||||
} `json:"hits,omitempty"`
|
||||
|
||||
Aggregations *struct {
|
||||
Timeseries *struct {
|
||||
Buckets []struct {
|
||||
Key string `json:"key_as_string"`
|
||||
Count int `json:"doc_count"`
|
||||
}
|
||||
} `json:"timeseries,omitempty"`
|
||||
|
||||
Kinds *struct {
|
||||
OtherCount int `json:"sum_other_doc_count"`
|
||||
Buckets []struct {
|
||||
Key string `json:"key"`
|
||||
Count int `json:"doc_count"`
|
||||
}
|
||||
} `json:"kinds,omitempty"`
|
||||
} `json:"aggregations,omitempty"`
|
||||
}
|
||||
|
||||
type KustomizeIndex struct {
|
||||
*index
|
||||
}
|
||||
|
||||
// Create index reference to the index containing the kustomize documents.
|
||||
func NewKustomizeIndex(ctx context.Context) (*KustomizeIndex, error) {
|
||||
idx, err := newIndex(ctx, "kustomize")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
indicesExistsOp := idx.client.Indices.Exists
|
||||
resp, err := indicesExistsOp([]string{"kustomize"},
|
||||
indicesExistsOp.WithContext(idx.ctx),
|
||||
indicesExistsOp.WithPretty())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode == 200 {
|
||||
fmt.Printf("The kustomize index already exists\n")
|
||||
} else {
|
||||
fmt.Printf("Creating the kustomize index\n")
|
||||
if err := idx.CreateIndex([]byte(IndexConfig)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &KustomizeIndex{idx}, nil
|
||||
}
|
||||
|
||||
// Return a timeseries of kustomization file counts.
|
||||
func TimeseriesAggregation() (string, map[string]interface{}) {
|
||||
return "timeseries", map[string]interface{}{
|
||||
"date_histogram": map[string]interface{}{
|
||||
"field": "creationTime",
|
||||
"interval": "day",
|
||||
/// XXX Only return values with counts, otherwise
|
||||
// every day is added to the output...
|
||||
// This matters if ever a zero valued time would
|
||||
// be stored in the creationTime field... it would
|
||||
// return >600k entries (for every day since year 0).
|
||||
// IDK why this is default, but I would not want this
|
||||
// to happen...
|
||||
"min_doc_count": 1,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Return aggregation of results based off of their kinds.
|
||||
func KindAggregation(maxBuckets int) (string, map[string]interface{}) {
|
||||
if maxBuckets < 1 {
|
||||
maxBuckets = 1
|
||||
}
|
||||
return "kinds", map[string]interface{}{
|
||||
"terms": map[string]interface{}{
|
||||
"field": "kinds.keyword",
|
||||
"size": maxBuckets,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// The multi_match search type in elasticsearch will check each field according
|
||||
// to their respective analyzers for the identifier.
|
||||
func multiMatch(query string) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"multi_match": map[string]interface{}{
|
||||
"type": "cross_fields",
|
||||
"fields": []string{
|
||||
"values.keyword^3",
|
||||
"identifiers.keyword^3",
|
||||
"values.ngram",
|
||||
"identifiers.ngram",
|
||||
// TODO(damienr74) remove document with default
|
||||
// analyzer. It does not handle special (=,: etc)
|
||||
// characters properly, and matches with false
|
||||
// positives. document.whitespace does not exist
|
||||
// yet, but should use the whitespace analyzer.
|
||||
"document",
|
||||
"document.whitespace",
|
||||
},
|
||||
"query": query,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Build an elasticsearch query from a user query.
|
||||
func BuildQuery(query string) map[string]interface{} {
|
||||
queryTokens := strings.Fields(query)
|
||||
if len(queryTokens) == 0 {
|
||||
return map[string]interface{}{
|
||||
"size": 0,
|
||||
}
|
||||
}
|
||||
|
||||
mustMatch := make([]map[string]interface{}, len(queryTokens))
|
||||
|
||||
for i, tok := range queryTokens {
|
||||
if strings.HasPrefix(strings.ToLower(tok), "kind=") {
|
||||
mustMatch[i] = map[string]interface{}{
|
||||
"term": map[string]interface{}{
|
||||
"kinds.keyword": tok[5:],
|
||||
},
|
||||
}
|
||||
continue
|
||||
}
|
||||
mustMatch[i] = multiMatch(tok)
|
||||
}
|
||||
|
||||
structuredQuery := map[string]interface{}{
|
||||
"query": map[string]interface{}{
|
||||
"bool": map[string]interface{}{
|
||||
"must": mustMatch,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return structuredQuery
|
||||
}
|
||||
|
||||
// Iterator based off of the way bufio.Scanner works.
|
||||
//
|
||||
// Example:
|
||||
// for it.Next() {
|
||||
// for _, doc := range it.Value().Hits {
|
||||
// // Handle KustomizationDocument.
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if err := it.Err(); err != nil {
|
||||
// // Handle err.
|
||||
// }
|
||||
type KustomizeIterator struct {
|
||||
update scrollUpdater
|
||||
err error
|
||||
// Matches the return definition of elasticsearch search results. The
|
||||
// scroll ID is practically a database cursor.
|
||||
scrollImpl KustomizeResult
|
||||
}
|
||||
|
||||
// Get the next batch of results. Note that this returns multiple results that
|
||||
// can be iterated.
|
||||
func (it *KustomizeIterator) Next() bool {
|
||||
reader := func(reader io.Reader) error {
|
||||
data, err := ioutil.ReadAll(reader)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not read from body: %v", err)
|
||||
}
|
||||
var scrollInput ElasticKustomizeResult
|
||||
err = json.Unmarshal(data, &scrollInput)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cloud not marshal %s into %T: %v",
|
||||
data, scrollInput, err)
|
||||
}
|
||||
it.scrollImpl = KustomizeResult(scrollInput)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if it.err == nil {
|
||||
fmt.Printf("updating scroll: %s\n", *it.scrollImpl.ScrollID)
|
||||
it.err = it.update(*it.scrollImpl.ScrollID, reader)
|
||||
}
|
||||
|
||||
// if there is no error and the array is not empty, then Value is
|
||||
// obligated to return a valid result.
|
||||
return it.err == nil &&
|
||||
it.scrollImpl.Hits != nil &&
|
||||
len(it.scrollImpl.Hits.Hits) > 0
|
||||
}
|
||||
|
||||
// Get the value from this batch of iterations.
|
||||
func (it *KustomizeIterator) Value() KustomizeResult {
|
||||
return it.scrollImpl
|
||||
}
|
||||
|
||||
// Check if any errors have occured.
|
||||
func (it *KustomizeIterator) Err() error {
|
||||
return it.err
|
||||
}
|
||||
|
||||
// Create an iterator over query. Iterate in chunks of batchSize, each batch
|
||||
// should take no longer than timeout to read (otherwise, elasticsearch will
|
||||
// delete the context).
|
||||
//
|
||||
// XXX Important to set a reasonable amount of time to read the documents. If
|
||||
// a lot of processing must be done, consider loading everything in memory
|
||||
// before doing it so that, a short timeout period can be set. Scrolling creates
|
||||
// a consistent DB context, so this can be costly.
|
||||
//
|
||||
// Scrolling is also not meant to be used for real time purposes. If you need
|
||||
// results quickly, consider using the From: field in SearchOptions and a normal
|
||||
// search. This will not guarantee that the values will not change but is more
|
||||
// suitable for lower latencies/long execution timeouts.
|
||||
func (ki *KustomizeIndex) IterateQuery(query []byte, batchSize int,
|
||||
timeout time.Duration) *KustomizeIterator {
|
||||
|
||||
emptyScroll := ""
|
||||
return &KustomizeIterator{
|
||||
update: ki.scrollUpdater(query, batchSize, timeout),
|
||||
scrollImpl: KustomizeResult{
|
||||
ScrollID: &emptyScroll,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// type specific Put for inserting structured kustomization documents.
|
||||
func (ki *KustomizeIndex) Put(id string, doc *doc.KustomizationDocument) (string, error) {
|
||||
id, err := ki.index.Put(id, doc)
|
||||
if err != nil {
|
||||
return id, fmt.Errorf("could not insert in elastic: %v", err)
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// Kustomize search options: What metrics should be returned? Kind Aggregation,
|
||||
// TimeseriesAggregation, etc. Also embedds the SearchOptions field to specify
|
||||
// the position in the sorted list of results and the number of results to return.
|
||||
type KustomizeSearchOptions struct {
|
||||
SearchOptions
|
||||
KindAggregation bool
|
||||
TimeseriesAggregation bool
|
||||
}
|
||||
|
||||
// Search the index with the given query string. Returns a structured result and possible
|
||||
// aggregates.
|
||||
func (ki *KustomizeIndex) Search(query string,
|
||||
opts KustomizeSearchOptions) (*KustomizeResult, error) {
|
||||
|
||||
aggMap := make(map[string]interface{})
|
||||
if opts.KindAggregation {
|
||||
k, kAgg := KindAggregation(15)
|
||||
aggMap[k] = kAgg
|
||||
}
|
||||
if opts.TimeseriesAggregation {
|
||||
t, tAgg := TimeseriesAggregation()
|
||||
aggMap[t] = tAgg
|
||||
}
|
||||
|
||||
esQuery := BuildQuery(query)
|
||||
if len(aggMap) > 0 {
|
||||
esQuery[AggregationKeyword] = aggMap
|
||||
}
|
||||
|
||||
data, err := json.Marshal(&esQuery)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to format query %s", query)
|
||||
}
|
||||
fmt.Printf("formated query: %s\n", data)
|
||||
|
||||
var kr ElasticKustomizeResult
|
||||
err = ki.index.Search(data, opts.SearchOptions, func(results io.Reader) error {
|
||||
data, err = ioutil.ReadAll(results)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not read results from search: %v", err)
|
||||
}
|
||||
|
||||
if err = json.Unmarshal(data, &kr); err != nil {
|
||||
return fmt.Errorf("could not parse results from search: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
res := KustomizeResult(kr)
|
||||
|
||||
return &res, err
|
||||
}
|
||||
72
api/internal/crawl/index/kustomize_test.go
Normal file
72
api/internal/crawl/index/kustomize_test.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package index
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBuildQuery(t *testing.T) {
|
||||
testCases := []struct {
|
||||
query string
|
||||
result map[string]interface{}
|
||||
}{
|
||||
{
|
||||
query: " \t\n\r",
|
||||
result: map[string]interface{}{"size": 0},
|
||||
},
|
||||
{
|
||||
query: "\tidentifier1 identifier2\nidentifier3\r",
|
||||
result: map[string]interface{}{
|
||||
"query": map[string]interface{}{
|
||||
"bool": map[string]interface{}{
|
||||
"must": []map[string]interface{}{
|
||||
multiMatch("identifier1"),
|
||||
multiMatch("identifier2"),
|
||||
multiMatch("identifier3"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
query: "kind=Kustomization",
|
||||
result: map[string]interface{}{
|
||||
"query": map[string]interface{}{
|
||||
"bool": map[string]interface{}{
|
||||
"must": []map[string]interface{}{
|
||||
{
|
||||
"term": map[string]interface{}{
|
||||
"kinds.keyword": "Kustomization",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
query: "kind=Kustomization identifier2",
|
||||
result: map[string]interface{}{
|
||||
"query": map[string]interface{}{
|
||||
"bool": map[string]interface{}{
|
||||
"must": []map[string]interface{}{
|
||||
{
|
||||
"term": map[string]interface{}{
|
||||
"kinds.keyword": "Kustomization",
|
||||
},
|
||||
},
|
||||
multiMatch("identifier2"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
result := BuildQuery(tc.query)
|
||||
if !reflect.DeepEqual(tc.result, result) {
|
||||
t.Errorf("Expected %#v to match %#v", result, tc.result)
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
api/internal/crawl/pictures/github_token.png
Normal file
BIN
api/internal/crawl/pictures/github_token.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 53 KiB |
BIN
api/internal/crawl/pictures/sys_arch.png
Normal file
BIN
api/internal/crawl/pictures/sys_arch.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user