mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-14 18:40:55 +00:00
Compare commits
888 Commits
issue3377
...
release-ap
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
326a57a9cc | ||
|
|
9dfdebc6c7 | ||
|
|
b896e04c20 | ||
|
|
6ecae1ad50 | ||
|
|
9abb72e4d6 | ||
|
|
6365b3d0cf | ||
|
|
33c2ea01c4 | ||
|
|
863ff0ef1b | ||
|
|
a5117083ec | ||
|
|
a143688a1d | ||
|
|
0676d0bd11 | ||
|
|
b6cb6c8ae9 | ||
|
|
b16e4ec566 | ||
|
|
cb1cbbe044 | ||
|
|
86fb408b2c | ||
|
|
ca5d691199 | ||
|
|
394567079d | ||
|
|
e0c8ebc41f | ||
|
|
8668691ade | ||
|
|
374d790a21 | ||
|
|
d8f406d06f | ||
|
|
46b3cd2109 | ||
|
|
20c608989a | ||
|
|
ba4d83f75f | ||
|
|
067559127d | ||
|
|
37ab5579f0 | ||
|
|
ef5f1d347d | ||
|
|
2c4b195516 | ||
|
|
04396ab4e6 | ||
|
|
4fd77b3a6e | ||
|
|
3ea8b79925 | ||
|
|
71b978da1a | ||
|
|
7110298c52 | ||
|
|
b4a69f08c0 | ||
|
|
572d5841c6 | ||
|
|
984a2dab3d | ||
|
|
c3c02887ec | ||
|
|
ba051c863b | ||
|
|
4d59146e48 | ||
|
|
5765ab4dbc | ||
|
|
4769751943 | ||
|
|
e506ce021e | ||
|
|
ed763991de | ||
|
|
55ac9ca88d | ||
|
|
548f5ffca9 | ||
|
|
dd3377b1a0 | ||
|
|
605239a1e5 | ||
|
|
67c58ad4f4 | ||
|
|
11e19a3d0f | ||
|
|
6fffcb9203 | ||
|
|
68790e00a9 | ||
|
|
6cf06fac12 | ||
|
|
0d8c107362 | ||
|
|
f30e45c549 | ||
|
|
250ea13767 | ||
|
|
608128738d | ||
|
|
7153f33466 | ||
|
|
274b12318d | ||
|
|
94c82f61a3 | ||
|
|
d260f50573 | ||
|
|
40c014a991 | ||
|
|
75c8aec29d | ||
|
|
fcb9c0065e | ||
|
|
63ec6bdb3d | ||
|
|
25bfe6f306 | ||
|
|
635c4fd43b | ||
|
|
c455215f55 | ||
|
|
8f56f51307 | ||
|
|
70a8ed6ed3 | ||
|
|
febfaf16dc | ||
|
|
8268b17700 | ||
|
|
0889995a61 | ||
|
|
4e476ae574 | ||
|
|
7efd7d23fe | ||
|
|
6fb944815b | ||
|
|
5e3432fbbe | ||
|
|
f0c6bd7773 | ||
|
|
dd579c905d | ||
|
|
22b735885a | ||
|
|
b7c5058e37 | ||
|
|
baff5f4359 | ||
|
|
dce4ea5846 | ||
|
|
c47fc48607 | ||
|
|
1d9b6cbe57 | ||
|
|
1cb93123fc | ||
|
|
c6cb42ec27 | ||
|
|
67a5f6d68f | ||
|
|
e997cc5486 | ||
|
|
53577a5190 | ||
|
|
c1ae234a64 | ||
|
|
02cb395ec2 | ||
|
|
65e7529ca0 | ||
|
|
f70743b267 | ||
|
|
f4382738ab | ||
|
|
a100dca303 | ||
|
|
50414208d1 | ||
|
|
e17a007719 | ||
|
|
dd3c5f5c0a | ||
|
|
fb3f560e0c | ||
|
|
12c177a365 | ||
|
|
402f6ca72b | ||
|
|
2b8a39373e | ||
|
|
17f18604e4 | ||
|
|
99e404cb61 | ||
|
|
d4e3b4f832 | ||
|
|
6552b90657 | ||
|
|
bf57d698b1 | ||
|
|
4d002af735 | ||
|
|
2bfc7cc1b0 | ||
|
|
0244f0919e | ||
|
|
f122fb12f3 | ||
|
|
f7cd553044 | ||
|
|
7d0b7e2113 | ||
|
|
c3a67cfdca | ||
|
|
4315e982be | ||
|
|
634464353f | ||
|
|
9c36004493 | ||
|
|
1b1034442c | ||
|
|
a89863c84c | ||
|
|
f7340e0615 | ||
|
|
bf6b207cc9 | ||
|
|
f93b4877f7 | ||
|
|
cd17338759 | ||
|
|
c46867c3a7 | ||
|
|
3c321ef79c | ||
|
|
7938fdb596 | ||
|
|
a2111869e6 | ||
|
|
f8288e2f02 | ||
|
|
f2f90d1185 | ||
|
|
459e800ecf | ||
|
|
8f00d3fd53 | ||
|
|
cd94cb13c6 | ||
|
|
e100be620e | ||
|
|
1e1b9b484a | ||
|
|
3e7246690f | ||
|
|
360585dfaf | ||
|
|
f604619dd5 | ||
|
|
0fa056327a | ||
|
|
6db2bf69f3 | ||
|
|
20fb9578c0 | ||
|
|
4eb8232495 | ||
|
|
6c4e8019f8 | ||
|
|
28707bf5df | ||
|
|
023a580f00 | ||
|
|
a2eaae5555 | ||
|
|
75df1a5422 | ||
|
|
f0b4cc4581 | ||
|
|
7a41e479c9 | ||
|
|
3350c7213c | ||
|
|
7b5e43d343 | ||
|
|
06661ea310 | ||
|
|
38b2b33503 | ||
|
|
f735d6fb3a | ||
|
|
5cb5e07ac0 | ||
|
|
54778504ed | ||
|
|
1bfe0d08dc | ||
|
|
56da9a58fc | ||
|
|
54383bca25 | ||
|
|
88461b4fed | ||
|
|
aabbea3e78 | ||
|
|
adedca09f2 | ||
|
|
9a27a9f19f | ||
|
|
3ebdb3fcef | ||
|
|
97e7cb1512 | ||
|
|
262a2d9288 | ||
|
|
91b862b556 | ||
|
|
b8ffc725c7 | ||
|
|
76f1411922 | ||
|
|
d1003d6f8f | ||
|
|
91f74e8d16 | ||
|
|
94c5096a95 | ||
|
|
f35aeb6a8e | ||
|
|
d6ce846047 | ||
|
|
ec069e4f19 | ||
|
|
c5adafd9ce | ||
|
|
16dcc98cff | ||
|
|
59c410a70a | ||
|
|
9b586162d0 | ||
|
|
803885049b | ||
|
|
be4fe7540e | ||
|
|
927568eea2 | ||
|
|
436d5e717c | ||
|
|
d37fa66ebc | ||
|
|
e8a4bf6edc | ||
|
|
35d1c3f9b4 | ||
|
|
e17785af21 | ||
|
|
0537b59f27 | ||
|
|
339e33d2f3 | ||
|
|
f082ac02cf | ||
|
|
9538ae1258 | ||
|
|
34981b664f | ||
|
|
477d8930e0 | ||
|
|
b5091a566a | ||
|
|
9981c45554 | ||
|
|
0f736ec7fd | ||
|
|
7826ad1e06 | ||
|
|
f4e6816338 | ||
|
|
4a13725678 | ||
|
|
ab9b010856 | ||
|
|
29be7fabe4 | ||
|
|
74e867833a | ||
|
|
91dc6d2a0f | ||
|
|
3c1fd0e9cf | ||
|
|
4deeb7d59b | ||
|
|
89b12cfc62 | ||
|
|
c07ffa5c1e | ||
|
|
259fcfcef8 | ||
|
|
f81201b74d | ||
|
|
6dbc74b32e | ||
|
|
ed38b5fe2b | ||
|
|
a84badb834 | ||
|
|
e1804cbc76 | ||
|
|
d13eef7951 | ||
|
|
0b4c6baf44 | ||
|
|
b3af54340c | ||
|
|
8c14b9d1af | ||
|
|
d818ccae92 | ||
|
|
4cea8b9785 | ||
|
|
84a36801e0 | ||
|
|
6eb7b3508d | ||
|
|
2a5f4ac7d7 | ||
|
|
518a16d3ac | ||
|
|
d53a2ad45d | ||
|
|
bb02a7645b | ||
|
|
5a9d90c872 | ||
|
|
4fd7269643 | ||
|
|
1eb3c1a075 | ||
|
|
a1746f2f8c | ||
|
|
b727febd08 | ||
|
|
c819d69ae4 | ||
|
|
bb6f83fb96 | ||
|
|
02d14d724a | ||
|
|
aa92d83d8c | ||
|
|
5427ab7cc3 | ||
|
|
e583f199b8 | ||
|
|
b465c20f65 | ||
|
|
5c2c617ff0 | ||
|
|
3ab0665c19 | ||
|
|
4b66043735 | ||
|
|
979f03e76c | ||
|
|
c8b049f57f | ||
|
|
f3d8883046 | ||
|
|
e308f321d3 | ||
|
|
beea785ead | ||
|
|
95c5b686be | ||
|
|
0ddf68cc8a | ||
|
|
4cadad5cfe | ||
|
|
9e4a6397d6 | ||
|
|
2e8a3b7c45 | ||
|
|
276d0430bf | ||
|
|
1aa7a1e709 | ||
|
|
78737f5a38 | ||
|
|
dac84d867e | ||
|
|
217e5c7268 | ||
|
|
936ac37a2e | ||
|
|
cb4f5c3983 | ||
|
|
1801d33287 | ||
|
|
b01da61d83 | ||
|
|
23e28bb18a | ||
|
|
a1f1c2d32f | ||
|
|
10331d9560 | ||
|
|
60038d44f9 | ||
|
|
24d06f83ca | ||
|
|
e76638f98d | ||
|
|
ef1b9d4854 | ||
|
|
3c0f805674 | ||
|
|
324581594c | ||
|
|
7fae7d1bd6 | ||
|
|
0af3a75708 | ||
|
|
d39d7db9ed | ||
|
|
a04a6de0ef | ||
|
|
69a6708f9b | ||
|
|
c19a972739 | ||
|
|
2e674337b3 | ||
|
|
727e24f365 | ||
|
|
7e8ba62e9f | ||
|
|
fe60d0c403 | ||
|
|
2e0556b544 | ||
|
|
479acac581 | ||
|
|
3b37fed24b | ||
|
|
8fdb3f1703 | ||
|
|
95e242353b | ||
|
|
199802a176 | ||
|
|
065432e074 | ||
|
|
62fd36facb | ||
|
|
f121e74744 | ||
|
|
5aa2f534be | ||
|
|
86dd74fd62 | ||
|
|
218da9858f | ||
|
|
cebda58437 | ||
|
|
6a82437bc9 | ||
|
|
6b9e8eb891 | ||
|
|
615984bf2d | ||
|
|
bc6ac8a68a | ||
|
|
39f24ef8d2 | ||
|
|
24294d3bd0 | ||
|
|
234fcbfc02 | ||
|
|
b54093ebca | ||
|
|
db307a7084 | ||
|
|
a0c7997b66 | ||
|
|
7458a53a73 | ||
|
|
cf6e6ca4db | ||
|
|
e847ec7474 | ||
|
|
440026b9b3 | ||
|
|
64331ad845 | ||
|
|
294070b3ab | ||
|
|
cabbea0d97 | ||
|
|
732a8522df | ||
|
|
8f82c4c748 | ||
|
|
d0bc25f339 | ||
|
|
ed3200e4f5 | ||
|
|
a3ed120efb | ||
|
|
f1b191c02f | ||
|
|
1493b24b46 | ||
|
|
5993eae1aa | ||
|
|
3e506eae02 | ||
|
|
0305860078 | ||
|
|
0205090e0d | ||
|
|
6adefe4562 | ||
|
|
da1bd901b4 | ||
|
|
636b9c7aeb | ||
|
|
942f112ef5 | ||
|
|
03bbb076bf | ||
|
|
e468d6b4d2 | ||
|
|
57206a628d | ||
|
|
f061bb887b | ||
|
|
75fd9a43a3 | ||
|
|
58165dfc89 | ||
|
|
0e8257c387 | ||
|
|
62e78f8349 | ||
|
|
84724a3ebf | ||
|
|
23544e0431 | ||
|
|
b1fda3d62e | ||
|
|
b8ae69b748 | ||
|
|
4014440d06 | ||
|
|
74b0b3adc6 | ||
|
|
382f09a126 | ||
|
|
f9afdc5c95 | ||
|
|
5e4fb4796e | ||
|
|
76f8988865 | ||
|
|
fa3e829eb6 | ||
|
|
d9435bd1b1 | ||
|
|
af96bb4bda | ||
|
|
8607e0adec | ||
|
|
5a2a7709a4 | ||
|
|
437e8f90f6 | ||
|
|
06ac670951 | ||
|
|
3ee1579688 | ||
|
|
5954314b98 | ||
|
|
c0324456a7 | ||
|
|
172adc404f | ||
|
|
501748192b | ||
|
|
f6e6ac0320 | ||
|
|
a10ce1d787 | ||
|
|
839cc2467c | ||
|
|
dbc11ed29f | ||
|
|
0f614e92f7 | ||
|
|
afaf7c62bc | ||
|
|
78d22069d7 | ||
|
|
22720a8b7a | ||
|
|
38c66d213a | ||
|
|
871de80544 | ||
|
|
c24daec480 | ||
|
|
0849d12572 | ||
|
|
23bd8ff749 | ||
|
|
b5759305af | ||
|
|
da518668b5 | ||
|
|
51605beb3b | ||
|
|
2646861a4c | ||
|
|
f8c910bd3b | ||
|
|
cb82dced8a | ||
|
|
f618f9ce96 | ||
|
|
020dc9c216 | ||
|
|
a6b9445702 | ||
|
|
36408a120c | ||
|
|
c1bca9cd62 | ||
|
|
701973b73e | ||
|
|
24a64bdee3 | ||
|
|
3f3d3b17a4 | ||
|
|
53c87a32e9 | ||
|
|
0fdf0f825f | ||
|
|
73da51d0ac | ||
|
|
df10d5a17d | ||
|
|
9557888b32 | ||
|
|
3e14a31312 | ||
|
|
dca13a4770 | ||
|
|
017a094438 | ||
|
|
b8e7cf04b6 | ||
|
|
a8dacdaffc | ||
|
|
5c4e363f11 | ||
|
|
1e3ce57077 | ||
|
|
01ddeb476d | ||
|
|
714af0cd66 | ||
|
|
82abd7e9ea | ||
|
|
823ff2d048 | ||
|
|
fcfdf6be51 | ||
|
|
627118c438 | ||
|
|
5bb7364967 | ||
|
|
a46926c1eb | ||
|
|
4f760a0850 | ||
|
|
e905411207 | ||
|
|
1eb77a6cab | ||
|
|
8b1704bcf3 | ||
|
|
a0edb2d966 | ||
|
|
02dff45d7d | ||
|
|
3cf18adae9 | ||
|
|
2bec25b46e | ||
|
|
8ee308d5d6 | ||
|
|
3c3c97f9b5 | ||
|
|
c3e8f6008e | ||
|
|
660847225d | ||
|
|
48d16f877b | ||
|
|
a4f4945455 | ||
|
|
bd7229ea17 | ||
|
|
72441ce3ef | ||
|
|
fe5b7a3b41 | ||
|
|
a4db686b6c | ||
|
|
10026758d3 | ||
|
|
c8dddac5b9 | ||
|
|
5a8a4d47a5 | ||
|
|
6c35c06f3e | ||
|
|
1235047742 | ||
|
|
6c041c3a79 | ||
|
|
1e7260b69a | ||
|
|
1de5fe608f | ||
|
|
6c9bf58e7f | ||
|
|
accd71a105 | ||
|
|
e3fd691459 | ||
|
|
3a508da641 | ||
|
|
a64a1022e6 | ||
|
|
b6fba0ad5c | ||
|
|
45fc67062e | ||
|
|
32cef014bb | ||
|
|
677ec868e0 | ||
|
|
7716b1bd3d | ||
|
|
8a529ca399 | ||
|
|
914a82bfa4 | ||
|
|
197c95f6ef | ||
|
|
323672bc38 | ||
|
|
66ce6f8801 | ||
|
|
6bc9d7358c | ||
|
|
bcbfa069ae | ||
|
|
7e622e1382 | ||
|
|
a2871181fe | ||
|
|
86c3863bc9 | ||
|
|
14d2f4bb15 | ||
|
|
c6f575ce37 | ||
|
|
e86fd7f009 | ||
|
|
d03a5ab95f | ||
|
|
8ef9f75de7 | ||
|
|
cf3a125940 | ||
|
|
8049f7b1af | ||
|
|
985fe4821d | ||
|
|
c828b1e49a | ||
|
|
d5d44ce3fe | ||
|
|
5d3dac04fa | ||
|
|
d2f5fe13aa | ||
|
|
705c6ad5ce | ||
|
|
94f8d4ec63 | ||
|
|
225bae8491 | ||
|
|
53f78260a9 | ||
|
|
06add3ab35 | ||
|
|
a59ec8fe23 | ||
|
|
f93cee9440 | ||
|
|
21e65990c1 | ||
|
|
38e9c34f08 | ||
|
|
038bc7713b | ||
|
|
a5914abad8 | ||
|
|
3f2b98ff01 | ||
|
|
aa1dd9ddc2 | ||
|
|
5ba45f1ef8 | ||
|
|
f3752dc75c | ||
|
|
d5f4da1261 | ||
|
|
7680392d96 | ||
|
|
d8015d3c93 | ||
|
|
7487e2f9cb | ||
|
|
4b8bc7d6ba | ||
|
|
59af49522e | ||
|
|
72d3eb15e0 | ||
|
|
14e31de6b1 | ||
|
|
162b8f3d37 | ||
|
|
003cf61a48 | ||
|
|
74f0df8b9d | ||
|
|
0df531e7c6 | ||
|
|
e3ce61647f | ||
|
|
768132f65f | ||
|
|
6a708bcc23 | ||
|
|
d8182f8d81 | ||
|
|
99b6a5920e | ||
|
|
8877c81468 | ||
|
|
88911bbb61 | ||
|
|
82ff64c374 | ||
|
|
01c477570a | ||
|
|
f8dad80a79 | ||
|
|
240cda089a | ||
|
|
c94c193b5b | ||
|
|
9989b5fc84 | ||
|
|
aeba50488b | ||
|
|
9c43518a15 | ||
|
|
9d50890174 | ||
|
|
3af1ae4159 | ||
|
|
5100568b0c | ||
|
|
aa5b4814d6 | ||
|
|
264b3ff338 | ||
|
|
a40c74e545 | ||
|
|
f61b075d3b | ||
|
|
629d822604 | ||
|
|
bf64f109b9 | ||
|
|
5f93fc53f4 | ||
|
|
0fe3f303e8 | ||
|
|
7825050b18 | ||
|
|
ed688a87e4 | ||
|
|
629fdee26a | ||
|
|
ca58ce775a | ||
|
|
0990d96c52 | ||
|
|
94c45e0f9f | ||
|
|
ca527a8e4c | ||
|
|
fa0b237178 | ||
|
|
a9bcf7187a | ||
|
|
a49d429909 | ||
|
|
c63288024d | ||
|
|
f374a12f24 | ||
|
|
c7156d0586 | ||
|
|
d3b7d3ab70 | ||
|
|
197bb9d9e3 | ||
|
|
fa96878cfc | ||
|
|
558995536d | ||
|
|
3255c73c71 | ||
|
|
fd486c1f23 | ||
|
|
b0a40e2752 | ||
|
|
ccb95ab269 | ||
|
|
e6b52e7295 | ||
|
|
4a6ec9063d | ||
|
|
c3beadacd9 | ||
|
|
5f3bd4b4c2 | ||
|
|
e77c284924 | ||
|
|
5ed2067be9 | ||
|
|
7b38ce4ef2 | ||
|
|
700a112b28 | ||
|
|
e05ce0f05b | ||
|
|
b8cfa3ca9b | ||
|
|
26a8455717 | ||
|
|
710db98dbf | ||
|
|
0d152c4784 | ||
|
|
1729c95135 | ||
|
|
6f6d41f17f | ||
|
|
3ff5263ff6 | ||
|
|
5bb668533f | ||
|
|
eb48b1b718 | ||
|
|
1f837fdfec | ||
|
|
1301384670 | ||
|
|
a9c20a2eb7 | ||
|
|
297bdc3825 | ||
|
|
831f99c95b | ||
|
|
5247aa5750 | ||
|
|
74d5646526 | ||
|
|
2f6a611e62 | ||
|
|
d0dbc3e87b | ||
|
|
235101a614 | ||
|
|
123a5d6e56 | ||
|
|
e4bbd04a43 | ||
|
|
75120b2a92 | ||
|
|
cb423ad300 | ||
|
|
c81b5bd3c2 | ||
|
|
b3cec39c25 | ||
|
|
5fc6cab49f | ||
|
|
c636ee616b | ||
|
|
f96ac2d61e | ||
|
|
a513c56d88 | ||
|
|
ed3ab9f532 | ||
|
|
bab8c34c1f | ||
|
|
839fd2b971 | ||
|
|
26e9b8b3b8 | ||
|
|
ddfb4ff02d | ||
|
|
a5e6295923 | ||
|
|
e2e495027d | ||
|
|
397744f436 | ||
|
|
9e8e7a7fe9 | ||
|
|
4d66f9a093 | ||
|
|
81cac9b633 | ||
|
|
43edc6dd7f | ||
|
|
f313cca52b | ||
|
|
243e7cca1f | ||
|
|
b9c36caa1c | ||
|
|
711b4ff4bb | ||
|
|
8d72528eb5 | ||
|
|
6590cce5c1 | ||
|
|
12c0360ba3 | ||
|
|
8e8fa5409d | ||
|
|
5af35f4f1a | ||
|
|
412e73cf76 | ||
|
|
ec27642e2f | ||
|
|
7165b1ec40 | ||
|
|
6dd50de7a4 | ||
|
|
a8b851f84a | ||
|
|
9c4966ccc8 | ||
|
|
d0bb1cd0fa | ||
|
|
102cf87f36 | ||
|
|
584a6c2a86 | ||
|
|
03c6f8fff4 | ||
|
|
90de9b78df | ||
|
|
34f1f2967e | ||
|
|
9a9df7436e | ||
|
|
c036830c70 | ||
|
|
ebbd0c7b5a | ||
|
|
7264a3a65d | ||
|
|
f3a958bbf7 | ||
|
|
14bf6f8a27 | ||
|
|
60c8a0498b | ||
|
|
774d768e7b | ||
|
|
efef397acf | ||
|
|
5793653630 | ||
|
|
4ee3d05bd8 | ||
|
|
a1df3e030f | ||
|
|
4e0332551a | ||
|
|
216ab488a6 | ||
|
|
722b0131f0 | ||
|
|
93dd571df9 | ||
|
|
a7000dd9c6 | ||
|
|
5c4b5b1bf0 | ||
|
|
8e57ee9111 | ||
|
|
60bd8d15d9 | ||
|
|
1d524b6fbe | ||
|
|
e9c97a4c4e | ||
|
|
48c89cb698 | ||
|
|
af1e692a5e | ||
|
|
57e7db0423 | ||
|
|
7fb6fa0f35 | ||
|
|
50c3875354 | ||
|
|
efc03bf329 | ||
|
|
9785bda7be | ||
|
|
29bfdfc1ef | ||
|
|
4f72cb8d00 | ||
|
|
a45e90b1e4 | ||
|
|
6b6bc45f2c | ||
|
|
e4a34f2a48 | ||
|
|
4a2ed901b3 | ||
|
|
ba67bc0f18 | ||
|
|
be8d60fb9f | ||
|
|
d9d5bb83f0 | ||
|
|
cfa7645d3b | ||
|
|
2e6ef91a7c | ||
|
|
508f294e0c | ||
|
|
a81ebe9842 | ||
|
|
c92fb809c6 | ||
|
|
043e8c36e5 | ||
|
|
7965195c29 | ||
|
|
4263d18c1a | ||
|
|
827fb1e1da | ||
|
|
03c77cee9b | ||
|
|
2db34e7127 | ||
|
|
821b14bfd1 | ||
|
|
33b4735f98 | ||
|
|
bbebd1e56a | ||
|
|
c9d9348944 | ||
|
|
555c4cb279 | ||
|
|
3da90dbde7 | ||
|
|
4ac0f59b8a | ||
|
|
2b9c69f964 | ||
|
|
437c960d86 | ||
|
|
131aba8f14 | ||
|
|
ada02703cf | ||
|
|
f96dfb5772 | ||
|
|
57b3e70cef | ||
|
|
f4fbcc6fb4 | ||
|
|
867da9631a | ||
|
|
cd2b0fce7e | ||
|
|
66504c263c | ||
|
|
bce4f75fc5 | ||
|
|
8b082aff5a | ||
|
|
48e4cad72e | ||
|
|
30e53a992b | ||
|
|
2df9f85a20 | ||
|
|
01733d970a | ||
|
|
ac178c539c | ||
|
|
61dcb3f548 | ||
|
|
6f15b1e56d | ||
|
|
9a94c5ecd3 | ||
|
|
2ba148d9b5 | ||
|
|
5a0e193002 | ||
|
|
3265f64cd5 | ||
|
|
c2b1ab8303 | ||
|
|
7dd0ade0f9 | ||
|
|
316e4314ed | ||
|
|
324353eaf6 | ||
|
|
6361c3b1b7 | ||
|
|
f7d13ade35 | ||
|
|
99e82890e1 | ||
|
|
4a35bfa84c | ||
|
|
27f28d5fe0 | ||
|
|
c04cf01b45 | ||
|
|
5614852b33 | ||
|
|
4f23ae5e1a | ||
|
|
3bd088a77c | ||
|
|
fe30a9321a | ||
|
|
c715b82ad7 | ||
|
|
44d308cbba | ||
|
|
c9e7f627fe | ||
|
|
00fa7e636c | ||
|
|
c7a504c9cf | ||
|
|
516ff1fa56 | ||
|
|
81562a7a37 | ||
|
|
ba0baa828c | ||
|
|
420f03d429 | ||
|
|
6cf48442df | ||
|
|
8cf7bc67bb | ||
|
|
48d6af6e38 | ||
|
|
0309a0fb07 | ||
|
|
d7b29455ab | ||
|
|
a414f75f1b | ||
|
|
1c3832f897 | ||
|
|
3ec62c6e26 | ||
|
|
c7ee4c281e | ||
|
|
471ff0c4bb | ||
|
|
cd0d416a11 | ||
|
|
4a1a2bfdd8 | ||
|
|
8d75824bb6 | ||
|
|
232e615afe | ||
|
|
50bfa0564c | ||
|
|
d0b101dc90 | ||
|
|
526ae9ff57 | ||
|
|
b553997447 | ||
|
|
66b7c5968b | ||
|
|
003b4946a2 | ||
|
|
2d7b6a57ca | ||
|
|
ecda4f423e | ||
|
|
9dc4004fbe | ||
|
|
a815774e5e | ||
|
|
f170af70fd | ||
|
|
6518393f5d | ||
|
|
dd72ea1e6a | ||
|
|
dbbe340b4f | ||
|
|
38b30b0edc | ||
|
|
10f9a5afda | ||
|
|
8a61bff299 | ||
|
|
52fbe73d49 | ||
|
|
17ecec2f0a | ||
|
|
aeb6024e72 | ||
|
|
e33addc16b | ||
|
|
2db4121c6a | ||
|
|
d203c2328a | ||
|
|
ec0e42709a | ||
|
|
abae65d8f1 | ||
|
|
054f18701a | ||
|
|
c764bc1618 | ||
|
|
2d2fbe9f14 | ||
|
|
7073371c1a | ||
|
|
dc25a6a1ce | ||
|
|
602ad8aa98 | ||
|
|
1fa3b224b1 | ||
|
|
5a328ababf | ||
|
|
3ae1aa7cd7 | ||
|
|
703fee93af | ||
|
|
3e4a59c1cd | ||
|
|
f98edf1b55 | ||
|
|
e41d94ddef | ||
|
|
0508c89b4b | ||
|
|
e653cffab6 | ||
|
|
e036f85b71 | ||
|
|
cee1324d18 | ||
|
|
b062ce0f66 | ||
|
|
fbcb15b15f | ||
|
|
8eb062637f | ||
|
|
2fe85be932 | ||
|
|
44edfa87fe | ||
|
|
bbccee0219 | ||
|
|
7cdc6cbe2f | ||
|
|
264bfa8998 | ||
|
|
041181afe4 | ||
|
|
5db79285a7 | ||
|
|
556692c9f5 | ||
|
|
71f4cecb4c | ||
|
|
d4d5fca2a5 | ||
|
|
811e1dca05 | ||
|
|
09bc6e76b1 | ||
|
|
065b14c5c5 | ||
|
|
adb2f2237a | ||
|
|
024bbd0777 | ||
|
|
ca1929abfb | ||
|
|
a13ef4da65 | ||
|
|
81ec59fa62 | ||
|
|
075a2d6c0f | ||
|
|
1df430255a | ||
|
|
94d06e1e18 | ||
|
|
c3b240639d | ||
|
|
4de6db3d59 | ||
|
|
58216d1d33 | ||
|
|
4c456d60a4 | ||
|
|
582bc4de01 | ||
|
|
a167084ccf | ||
|
|
a3a11bf3f4 | ||
|
|
4e96502ec6 | ||
|
|
1ae8303bdc | ||
|
|
41df2bed1f | ||
|
|
1f1304194d | ||
|
|
4157933c8d | ||
|
|
a79253e02f | ||
|
|
3b35b121b3 | ||
|
|
3c94d20599 | ||
|
|
1faeb91cc4 | ||
|
|
9f1ef993a1 | ||
|
|
55d8cb3d3a | ||
|
|
a684592639 | ||
|
|
386d10834b | ||
|
|
af32126e80 | ||
|
|
b0cfa15b9c | ||
|
|
62e7df6812 | ||
|
|
c077ed4b58 | ||
|
|
156beb300c | ||
|
|
25b02d2d6c | ||
|
|
781098843e | ||
|
|
af3ffa7059 | ||
|
|
9d7b8952a0 | ||
|
|
cb400c895e | ||
|
|
9a8dcf6a8e | ||
|
|
384b71b5f5 | ||
|
|
4345cd2ade | ||
|
|
1460d13d50 | ||
|
|
97a2b15be6 | ||
|
|
f927cf0b8e | ||
|
|
cbb121e651 | ||
|
|
447b315a61 | ||
|
|
b3cb61b80f | ||
|
|
b9f05dd357 | ||
|
|
1ee16d9f52 | ||
|
|
43157f5d35 | ||
|
|
cd918483f9 | ||
|
|
f71854a0c8 | ||
|
|
6246262965 | ||
|
|
d3ea87220b | ||
|
|
61daea0202 | ||
|
|
a54cd12b39 | ||
|
|
11ce6363b4 | ||
|
|
4de26ccf9d | ||
|
|
7801830152 | ||
|
|
aae2be1a79 | ||
|
|
79e15c05d5 | ||
|
|
507244e6f8 | ||
|
|
3892e3c910 | ||
|
|
dcb26d0901 | ||
|
|
f5f1a15226 | ||
|
|
64644643d4 | ||
|
|
4d1eebbb82 | ||
|
|
b3a9314e27 | ||
|
|
86168cebbc | ||
|
|
57a53797d3 | ||
|
|
f5beffe394 | ||
|
|
4287e28ff4 | ||
|
|
a5cdd98414 | ||
|
|
16a49c50c4 | ||
|
|
a6f29f2bf7 | ||
|
|
22fcf3b3fa | ||
|
|
a31b846fa5 | ||
|
|
22fb23071b | ||
|
|
382cf5c2e0 | ||
|
|
8d4508a041 | ||
|
|
4d5657f037 | ||
|
|
5958edda14 | ||
|
|
48676fe34b | ||
|
|
a8278b6da9 | ||
|
|
31d6e24fa4 | ||
|
|
25e11e9020 | ||
|
|
d8e2a76ef3 | ||
|
|
cff7bd4eb2 | ||
|
|
58db58202c | ||
|
|
fc29d7e108 | ||
|
|
ae060cc225 | ||
|
|
9b4fdcf35a | ||
|
|
bb2d63ab58 | ||
|
|
b5012385c8 | ||
|
|
659a7de8f9 | ||
|
|
866dbf2017 | ||
|
|
ef89df6123 | ||
|
|
97f23966af | ||
|
|
5059033b13 | ||
|
|
d91e8af702 | ||
|
|
1bef8c4cdd | ||
|
|
9b87f78511 | ||
|
|
eda827c317 | ||
|
|
be57e1f6c2 | ||
|
|
021c3ce3fc | ||
|
|
4f184e8ce3 | ||
|
|
2e0d6d42bf | ||
|
|
6cf6eb9f76 | ||
|
|
ffed8f1430 | ||
|
|
7ac37867dc | ||
|
|
d8f2d2256d | ||
|
|
463b776486 |
23
.github/workflows/go.yml
vendored
23
.github/workflows/go.yml
vendored
@@ -16,14 +16,14 @@ jobs:
|
||||
- name: Set up Go 1.x
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ^1.13
|
||||
go-version: ^1.16
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Lint
|
||||
run: ./scripts/kyaml-pre-commit.sh
|
||||
run: ./hack/kyaml-pre-commit.sh
|
||||
env:
|
||||
KUSTOMIZE_DOCKER_E2E: false # don't need to do e2e tests for linting
|
||||
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
- name: Set up Go 1.x
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ^1.13
|
||||
go-version: ^1.16
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
@@ -45,6 +45,10 @@ jobs:
|
||||
run: go test -cover ./...
|
||||
working-directory: ./kyaml
|
||||
|
||||
- name: Test api
|
||||
run: go test -cover ./... -ldflags "-X sigs.k8s.io/kustomize/api/provenance.version=v444.333.222"
|
||||
working-directory: ./api
|
||||
|
||||
- name: Test cmd/config
|
||||
run: go test -cover ./...
|
||||
working-directory: ./cmd/config
|
||||
@@ -59,7 +63,7 @@ jobs:
|
||||
- name: Set up Go 1.x
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ^1.13
|
||||
go-version: ^1.16
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
@@ -69,6 +73,10 @@ jobs:
|
||||
run: go test -cover ./...
|
||||
working-directory: ./kyaml
|
||||
|
||||
- name: Test api
|
||||
run: go test -cover ./... -ldflags "-X sigs.k8s.io/kustomize/api/provenance.version=v444.333.222"
|
||||
working-directory: ./api
|
||||
|
||||
- name: Test cmd/config
|
||||
run: go test -cover ./...
|
||||
working-directory: ./cmd/config
|
||||
@@ -83,7 +91,7 @@ jobs:
|
||||
- name: Set up Go 1.x
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ^1.13
|
||||
go-version: ^1.16
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
@@ -93,6 +101,11 @@ jobs:
|
||||
run: go test -cover ./...
|
||||
working-directory: ./kyaml
|
||||
|
||||
# TODO: uncomment once Windows tests are passing.
|
||||
# - name: Test api
|
||||
# run: go test -cover ./... -ldflags "-X sigs.k8s.io/kustomize/api/provenance.version=v444.333.222"
|
||||
# working-directory: ./api
|
||||
|
||||
- name: Test cmd/config
|
||||
run: go test -cover ./...
|
||||
working-directory: ./cmd/config
|
||||
|
||||
@@ -1,6 +1,25 @@
|
||||
[SIG-CLI]: https://github.com/kubernetes/community/tree/master/sig-cli
|
||||
[Slack channel]: https://kubernetes.slack.com/messages/kustomize
|
||||
[Mailing list]: https://groups.google.com/forum/#!forum/kubernetes-sig-cli
|
||||
|
||||
[OWNERS file spec]: https://github.com/kubernetes/community/blob/master/contributors/guide/owners.md
|
||||
[Kustomize OWNERS_ALIASES]: https://github.com/kubernetes-sigs/kustomize/blob/8049f7b1af52e8a7ec26faf6cf714f560d0043c5/OWNERS_ALIASES
|
||||
[SIG-CLI Teams]: https://github.com/kubernetes/org/blob/main/config/kubernetes-sigs/sig-cli/teams.yaml
|
||||
[Github permissions]: https://docs.github.com/en/organizations/managing-access-to-your-organizations-repositories/repository-permission-levels-for-an-organization#repository-access-for-each-permission-level
|
||||
|
||||
[Contributor License Agreement]: https://git.k8s.io/community/CLA.md
|
||||
[Kubernetes Contributor Guide]: http://git.k8s.io/community/contributors/guide
|
||||
[Contributor Cheat Sheet]: https://git.k8s.io/community/contributors/guide/contributor-cheatsheet/README.md
|
||||
[CNCF Code of Conduct]: https://github.com/cncf/foundation/blob/master/code-of-conduct.md
|
||||
[Kubernetes Community Membership]: https://github.com/kubernetes/community/blob/master/community-membership.md
|
||||
|
||||
[Contribution Guide]: https://kubectl.docs.kubernetes.io/contributing/kustomize/
|
||||
[MacOS Dev Guide]: https://kubectl.docs.kubernetes.io/contributing/kustomize/mac/
|
||||
[Windows Dev Guide]: https://kubectl.docs.kubernetes.io/contributing/kustomize/windows/
|
||||
|
||||
# Contributing Guidelines
|
||||
|
||||
Welcome to Kubernetes. We are excited about the prospect of you joining our [community](https://github.com/kubernetes/community)! The Kubernetes community abides by the CNCF [code of conduct](code-of-conduct.md). Here is an excerpt:
|
||||
Welcome to Kubernetes. We are excited about the prospect of you joining our [community](https://github.com/kubernetes/community)! The Kubernetes community abides by the [CNCF Code of Conduct]. Here is an excerpt:
|
||||
|
||||
_As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities._
|
||||
|
||||
@@ -8,19 +27,45 @@ _As contributors and maintainers of this project, and in the interest of fosteri
|
||||
|
||||
Dev guides:
|
||||
|
||||
- [Mac](docs/macDevGuide.md)
|
||||
- [Contribution Guide]
|
||||
- [MacOS Dev Guide]
|
||||
- [Windows Dev Guide]
|
||||
|
||||
We have full documentation on how to get started contributing here:
|
||||
General resources for contributors:
|
||||
|
||||
- [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/README.md) - Common resources for existing developers
|
||||
- [Contributor License Agreement] - Kubernetes projects require that you sign a Contributor License Agreement (CLA) before we can accept your pull requests.
|
||||
- [Kubernetes Contributor Guide] - Main contributor documentation.
|
||||
- [Contributor Cheat Sheet] - Common resources for existing developers.
|
||||
|
||||
Here are some additional ideas to help you get started with Kustomize:
|
||||
- Attend a Kustomize Bug Scrub. Check the [SIG-CLI] meetings list to find the next one.
|
||||
- Help triage issues by confirming validity and applying the appropriate `kind` label (e.g. comment `/kind bug`).
|
||||
- Pick up an issue to fix. Issues with the `help-wanted` label are a good place to start, but you can also look for any issue with the `triage/accepted` label and no assignee. Remember to `/assign` yourself to let others know you're working on it.
|
||||
- Help confirm new issues labelled `kind/bug` by reproducing them with the latest release.
|
||||
- Support Kustomize users by responding to questions on issues labelled `kind/support` or in the [Slack channel].
|
||||
|
||||
## Mentorship
|
||||
|
||||
- [Mentoring Initiatives](https://git.k8s.io/community/mentoring) - We have a diverse set of mentorship programs available that are always looking for volunteers!
|
||||
|
||||
## Contributor Ladder
|
||||
|
||||
Kustomize follows the [Kubernetes Community Membership] contributor ladder. Roles are as follows:
|
||||
|
||||
1. Contributor: Anyone who actively contributes code, issues or reviews to the project. All contributors must sign the [Contributor License Agreement].
|
||||
1. Reviewer: Contributors with a history of review and authorship on Kustomize. Has LGTM rights on the Kustomize repo (as do all kubernetes-sigs org members). Active contributors are encouraged to join the reviewers list to be automatically pinged on PRs.
|
||||
1. Approver: Highly experienced active reviewer and contributor to Kustomize. Has both LTGM and approval rights on the Kustomize repo, as well as "maintain" [Github permissions].
|
||||
1. Owner: Approver who sets technical direction and makes or approves design decisions for the project. Has LGTM and approval rights on the Kustomize repo as well as "admin" [Github permissions].
|
||||
|
||||
The kyaml module within the Kustomize repo has additional owners following the same ladder.
|
||||
|
||||
Administrative notes:
|
||||
|
||||
- The [OWNERS file spec] is a useful resources in making changes.
|
||||
- Maintainers and admins must be added to the appropriate lists in both [Kustomize OWNERS_ALIASES] and [SIG-CLI Teams]. If this isn't done, the individual in question will lack either PR approval rights (Kustomize list) or the appropriate Github repository permissions (community list).
|
||||
|
||||
|
||||
## Contact Information
|
||||
|
||||
- [Slack channel](https://kubernetes.slack.com/messages/sig-cli)
|
||||
- [Mailing list](https://groups.google.com/forum/#!forum/kubernetes-sig-cli)
|
||||
- [Slack channel]
|
||||
- [Mailing list]
|
||||
|
||||
112
Makefile
112
Makefile
@@ -3,10 +3,16 @@
|
||||
#
|
||||
# Makefile for kustomize CLI and API.
|
||||
|
||||
MYGOBIN := $(shell go env GOPATH)/bin
|
||||
SHELL := /bin/bash
|
||||
SHELL := /usr/bin/env bash
|
||||
GOOS = $(shell go env GOOS)
|
||||
GOARCH = $(shell go env GOARCH)
|
||||
MYGOBIN = $(shell go env GOBIN)
|
||||
ifeq ($(MYGOBIN),)
|
||||
MYGOBIN = $(shell go env GOPATH)/bin
|
||||
endif
|
||||
export PATH := $(MYGOBIN):$(PATH)
|
||||
MODULES := '"cmd/config" "api/" "kustomize/" "kyaml/"'
|
||||
LATEST_V4_RELEASE=v4.4.0
|
||||
|
||||
# Provide defaults for REPO_OWNER and REPO_NAME if not present.
|
||||
# Typically these values would be provided by Prow.
|
||||
@@ -19,66 +25,59 @@ REPO_NAME := "kustomize"
|
||||
endif
|
||||
|
||||
.PHONY: all
|
||||
all: verify-kustomize
|
||||
all: install-tools verify-kustomize
|
||||
|
||||
.PHONY: verify-kustomize
|
||||
verify-kustomize: \
|
||||
lint-kustomize \
|
||||
test-unit-kustomize-all \
|
||||
test-examples-kustomize-against-HEAD \
|
||||
test-examples-kustomize-against-3.9.2 \
|
||||
test-examples-kustomize-against-3.8.9
|
||||
test-examples-kustomize-against-v4-release
|
||||
|
||||
# The following target referenced by a file in
|
||||
# https://github.com/kubernetes/test-infra/tree/master/config/jobs/kubernetes-sigs/kustomize
|
||||
.PHONY: prow-presubmit-check
|
||||
prow-presubmit-check: \
|
||||
install-tools \
|
||||
lint-kustomize \
|
||||
test-multi-module \
|
||||
test-unit-kustomize-all \
|
||||
test-unit-cmd-all \
|
||||
test-go-mod \
|
||||
test-examples-kustomize-against-HEAD \
|
||||
test-examples-kustomize-against-3.9.2 \
|
||||
test-examples-kustomize-against-3.8.9
|
||||
test-examples-kustomize-against-v4-release
|
||||
|
||||
.PHONY: verify-kustomize-e2e
|
||||
verify-kustomize-e2e: test-examples-e2e-kustomize
|
||||
|
||||
# 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 hack/go.mod
|
||||
# cannot assume that golanci-lint is at the version they want.
|
||||
# This installs what kustomize wants to use.
|
||||
$(MYGOBIN)/golangci-lint-kustomize:
|
||||
( \
|
||||
set -e; \
|
||||
cd hack; \
|
||||
GO111MODULE=on go build -tags=tools -o $(MYGOBIN)/golangci-lint-kustomize github.com/golangci/golangci-lint/cmd/golangci-lint; \
|
||||
)
|
||||
rm -f $(CURDIR)/hack/golangci-lint
|
||||
GOBIN=$(CURDIR)/hack go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.23.8
|
||||
mv $(CURDIR)/hack/golangci-lint $(MYGOBIN)/golangci-lint-kustomize
|
||||
|
||||
# Install from version specified in api/go.mod.
|
||||
$(MYGOBIN)/mdrip:
|
||||
cd api; \
|
||||
go install github.com/monopole/mdrip
|
||||
go install github.com/monopole/mdrip@v1.0.2
|
||||
|
||||
# Install from version specified in api/go.mod.
|
||||
$(MYGOBIN)/stringer:
|
||||
cd api; \
|
||||
go install golang.org/x/tools/cmd/stringer
|
||||
go get golang.org/x/tools/cmd/stringer
|
||||
|
||||
# Install from version specified in api/go.mod.
|
||||
$(MYGOBIN)/goimports:
|
||||
cd api; \
|
||||
go install golang.org/x/tools/cmd/goimports
|
||||
go get golang.org/x/tools/cmd/goimports
|
||||
|
||||
# Build from local source.
|
||||
$(MYGOBIN)/gorepomod:
|
||||
cd cmd/gorepomod; \
|
||||
go install .
|
||||
|
||||
# Build from local source.
|
||||
$(MYGOBIN)/k8scopy:
|
||||
cd cmd/k8scopy; \
|
||||
go install .
|
||||
|
||||
# Build from local source.
|
||||
$(MYGOBIN)/pluginator:
|
||||
cd cmd/pluginator; \
|
||||
@@ -90,7 +89,7 @@ $(MYGOBIN)/prchecker:
|
||||
go install .
|
||||
|
||||
# Build from local source.
|
||||
$(MYGOBIN)/kustomize:
|
||||
$(MYGOBIN)/kustomize: build-kustomize-api
|
||||
cd kustomize; \
|
||||
go install .
|
||||
|
||||
@@ -98,13 +97,13 @@ $(MYGOBIN)/kustomize:
|
||||
install-tools: \
|
||||
$(MYGOBIN)/goimports \
|
||||
$(MYGOBIN)/golangci-lint-kustomize \
|
||||
$(MYGOBIN)/gh \
|
||||
$(MYGOBIN)/gorepomod \
|
||||
$(MYGOBIN)/helmV3 \
|
||||
$(MYGOBIN)/k8scopy \
|
||||
$(MYGOBIN)/mdrip \
|
||||
$(MYGOBIN)/pluginator \
|
||||
$(MYGOBIN)/prchecker \
|
||||
$(MYGOBIN)/stringer \
|
||||
$(MYGOBIN)/helm
|
||||
$(MYGOBIN)/stringer
|
||||
|
||||
### Begin kustomize plugin rules.
|
||||
#
|
||||
@@ -135,6 +134,7 @@ pSrc=plugin/builtin
|
||||
_builtinplugins = \
|
||||
AnnotationsTransformer.go \
|
||||
ConfigMapGenerator.go \
|
||||
IAMPolicyGenerator.go \
|
||||
HashTransformer.go \
|
||||
ImageTagTransformer.go \
|
||||
LabelTransformer.go \
|
||||
@@ -144,6 +144,7 @@ _builtinplugins = \
|
||||
PatchStrategicMergeTransformer.go \
|
||||
PatchTransformer.go \
|
||||
PrefixSuffixTransformer.go \
|
||||
ReplacementTransformer.go \
|
||||
ReplicaCountTransformer.go \
|
||||
SecretGenerator.go \
|
||||
ValueAddTransformer.go \
|
||||
@@ -161,6 +162,7 @@ builtinplugins = $(patsubst %,$(pGen)/%,$(_builtinplugins))
|
||||
# that file, will be recreated.
|
||||
$(pGen)/AnnotationsTransformer.go: $(pSrc)/annotationstransformer/AnnotationsTransformer.go
|
||||
$(pGen)/ConfigMapGenerator.go: $(pSrc)/configmapgenerator/ConfigMapGenerator.go
|
||||
$(pGen)/GkeSaGenerator.go: $(pSrc)/gkesagenerator/GkeSaGenerator.go
|
||||
$(pGen)/HashTransformer.go: $(pSrc)/hashtransformer/HashTransformer.go
|
||||
$(pGen)/ImageTagTransformer.go: $(pSrc)/imagetagtransformer/ImageTagTransformer.go
|
||||
$(pGen)/LabelTransformer.go: $(pSrc)/labeltransformer/LabelTransformer.go
|
||||
@@ -170,6 +172,7 @@ $(pGen)/PatchJson6902Transformer.go: $(pSrc)/patchjson6902transformer/PatchJson6
|
||||
$(pGen)/PatchStrategicMergeTransformer.go: $(pSrc)/patchstrategicmergetransformer/PatchStrategicMergeTransformer.go
|
||||
$(pGen)/PatchTransformer.go: $(pSrc)/patchtransformer/PatchTransformer.go
|
||||
$(pGen)/PrefixSuffixTransformer.go: $(pSrc)/prefixsuffixtransformer/PrefixSuffixTransformer.go
|
||||
$(pGen)/ReplacementTransformer.go: $(pSrc)/replacementtransformer/ReplacementTransformer.go
|
||||
$(pGen)/ReplicaCountTransformer.go: $(pSrc)/replicacounttransformer/ReplicaCountTransformer.go
|
||||
$(pGen)/SecretGenerator.go: $(pSrc)/secretgenerator/SecretGenerator.go
|
||||
$(pGen)/ValueAddTransformer.go: $(pSrc)/valueaddtransformer/ValueAddTransformer.go
|
||||
@@ -203,7 +206,7 @@ clean-kustomize-external-go-plugin:
|
||||
### End kustomize plugin rules.
|
||||
|
||||
.PHONY: lint-kustomize
|
||||
lint-kustomize: install-tools $(builtinplugins)
|
||||
lint-kustomize: $(MYGOBIN)/golangci-lint-kustomize $(builtinplugins)
|
||||
cd api; $(MYGOBIN)/golangci-lint-kustomize \
|
||||
-c ../.golangci-kustomize.yml \
|
||||
run ./...
|
||||
@@ -221,12 +224,13 @@ build-kustomize-api: $(builtinplugins)
|
||||
cd api; go build ./...
|
||||
|
||||
.PHONY: generate-kustomize-api
|
||||
generate-kustomize-api:
|
||||
generate-kustomize-api: $(MYGOBIN)/k8scopy
|
||||
cd api; go generate ./...
|
||||
|
||||
.PHONY: test-unit-kustomize-api
|
||||
test-unit-kustomize-api: build-kustomize-api
|
||||
cd api; go test ./... -ldflags "-X sigs.k8s.io/kustomize/api/provenance.version=v444.333.222"
|
||||
cd api; go test ./... -ldflags "-X sigs.k8s.io/kustomize/api/provenance.version=v444.333.222"; \
|
||||
cd krusty; OPENAPI_TEST=true go test -run TestCustomOpenAPIFieldFromComponentWithOverlays
|
||||
|
||||
.PHONY: test-unit-kustomize-plugins
|
||||
test-unit-kustomize-plugins:
|
||||
@@ -243,10 +247,10 @@ test-unit-kustomize-all: \
|
||||
test-unit-kustomize-plugins
|
||||
|
||||
test-unit-cmd-all:
|
||||
./scripts/kyaml-pre-commit.sh
|
||||
./hack/kyaml-pre-commit.sh
|
||||
|
||||
test-go-mod:
|
||||
./scripts/check-go-mod.sh
|
||||
./hack/check-go-mod.sh
|
||||
|
||||
# Environment variables are defined at
|
||||
# https://github.com/kubernetes/test-infra/blob/master/prow/jobs.md#job-environment-variables
|
||||
@@ -258,7 +262,7 @@ test-multi-module: $(MYGOBIN)/prchecker
|
||||
export REPO_NAME=$(REPO_NAME); \
|
||||
export PULL_NUMBER=$(PULL_NUMBER); \
|
||||
export MODULES=$(MODULES); \
|
||||
./scripts/check-multi-module.sh; \
|
||||
./hack/check-multi-module.sh; \
|
||||
)
|
||||
|
||||
.PHONY:
|
||||
@@ -276,12 +280,8 @@ test-examples-kustomize-against-HEAD: $(MYGOBIN)/kustomize $(MYGOBIN)/mdrip
|
||||
./hack/testExamplesAgainstKustomize.sh HEAD
|
||||
|
||||
.PHONY:
|
||||
test-examples-kustomize-against-3.9.2: $(MYGOBIN)/mdrip
|
||||
./hack/testExamplesAgainstKustomize.sh v3.9.2
|
||||
|
||||
.PHONY:
|
||||
test-examples-kustomize-against-3.8.9: $(MYGOBIN)/mdrip
|
||||
./hack/testExamplesAgainstKustomize.sh v3.8.9
|
||||
test-examples-kustomize-against-v4-release: $(MYGOBIN)/mdrip
|
||||
./hack/testExamplesAgainstKustomize.sh v4@$(LATEST_V4_RELEASE)
|
||||
|
||||
# linux only.
|
||||
# This is for testing an example plugin that
|
||||
@@ -293,8 +293,8 @@ $(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; \
|
||||
wget https://github.com/instrumenta/kubeval/releases/latest/download/kubeval-$(GOOS)-$(GOARCH).tar.gz; \
|
||||
tar xf kubeval-$(GOOS)-$(GOARCH).tar.gz; \
|
||||
mv kubeval $(MYGOBIN); \
|
||||
rm -rf $$d; \
|
||||
)
|
||||
@@ -308,10 +308,10 @@ $(MYGOBIN)/helmV2:
|
||||
( \
|
||||
set -e; \
|
||||
d=$(shell mktemp -d); cd $$d; \
|
||||
tgzFile=helm-v2.13.1-linux-amd64.tar.gz; \
|
||||
tgzFile=helm-v2.13.1-$(GOOS)-$(GOARCH).tar.gz; \
|
||||
wget https://storage.googleapis.com/kubernetes-helm/$$tgzFile; \
|
||||
tar -xvzf $$tgzFile; \
|
||||
mv linux-amd64/helm $(MYGOBIN)/helmV2; \
|
||||
mv $(GOOS)-$(GOARCH)/helm $(MYGOBIN)/helmV2; \
|
||||
rm -rf $$d \
|
||||
)
|
||||
|
||||
@@ -321,22 +321,18 @@ $(MYGOBIN)/helmV3:
|
||||
( \
|
||||
set -e; \
|
||||
d=$(shell mktemp -d); cd $$d; \
|
||||
tgzFile=helm-v3.4.0-linux-amd64.tar.gz; \
|
||||
tgzFile=helm-v3.6.3-$(GOOS)-$(GOARCH).tar.gz; \
|
||||
wget https://get.helm.sh/$$tgzFile; \
|
||||
tar -xvzf $$tgzFile; \
|
||||
mv linux-amd64/helm $(MYGOBIN)/helmV3; \
|
||||
mv $(GOOS)-$(GOARCH)/helm $(MYGOBIN)/helmV3; \
|
||||
rm -rf $$d \
|
||||
)
|
||||
|
||||
# Default version of helm is v3.
|
||||
$(MYGOBIN)/helm: $(MYGOBIN)/helmV3
|
||||
ln -s $(MYGOBIN)/helmV3 $(MYGOBIN)/helm
|
||||
|
||||
$(MYGOBIN)/kind:
|
||||
( \
|
||||
set -e; \
|
||||
d=$(shell mktemp -d); cd $$d; \
|
||||
wget -O ./kind https://github.com/kubernetes-sigs/kind/releases/download/v0.7.0/kind-$(shell uname)-amd64; \
|
||||
wget -O ./kind https://github.com/kubernetes-sigs/kind/releases/download/v0.7.0/kind-$(GOOS)-$(GOARCH); \
|
||||
chmod +x ./kind; \
|
||||
mv ./kind $(MYGOBIN); \
|
||||
rm -rf $$d; \
|
||||
@@ -347,10 +343,10 @@ $(MYGOBIN)/gh:
|
||||
( \
|
||||
set -e; \
|
||||
d=$(shell mktemp -d); cd $$d; \
|
||||
tgzFile=gh_1.0.0_linux_amd64.tar.gz; \
|
||||
tgzFile=gh_1.0.0_$(GOOS)_$(GOARCH).tar.gz; \
|
||||
wget https://github.com/cli/cli/releases/download/v1.0.0/$$tgzFile; \
|
||||
tar -xvzf $$tgzFile; \
|
||||
mv gh_1.0.0_linux_amd64/bin/gh $(MYGOBIN)/gh; \
|
||||
mv gh_1.0.0_$(GOOS)_$(GOARCH)/bin/gh $(MYGOBIN)/gh; \
|
||||
rm -rf $$d \
|
||||
)
|
||||
|
||||
@@ -358,8 +354,12 @@ $(MYGOBIN)/gh:
|
||||
clean: clean-kustomize-external-go-plugin
|
||||
go clean --cache
|
||||
rm -f $(builtinplugins)
|
||||
rm -f $(MYGOBIN)/kustomize
|
||||
rm -f $(MYGOBIN)/goimports
|
||||
rm -f $(MYGOBIN)/golangci-lint-kustomize
|
||||
rm -f $(MYGOBIN)/kustomize
|
||||
rm -f $(MYGOBIN)/mdrip
|
||||
rm -f $(MYGOBIN)/prchecker
|
||||
rm -f $(MYGOBIN)/stringer
|
||||
|
||||
# Handle pluginator manually.
|
||||
# rm -f $(MYGOBIN)/pluginator
|
||||
|
||||
6
OWNERS
6
OWNERS
@@ -1,4 +1,6 @@
|
||||
# See https://github.com/kubernetes/community/blob/master/community-membership.md
|
||||
approvers:
|
||||
- kustomize-admins
|
||||
- kustomize-maintainers
|
||||
- kustomize-approvers
|
||||
|
||||
reviewers:
|
||||
- kustomize-reviewers
|
||||
|
||||
@@ -1,14 +1,30 @@
|
||||
# Keep *-owners and *-approvers lists in sync with *-admins and *-maintainers in
|
||||
# https://github.com/kubernetes/org/blob/main/config/kubernetes-sigs/sig-cli/teams.yaml
|
||||
aliases:
|
||||
kustomize-admins:
|
||||
kustomize-owners:
|
||||
- knverey
|
||||
- monopole
|
||||
- pwittrock
|
||||
kustomize-maintainers:
|
||||
- droot
|
||||
kustomize-approvers:
|
||||
- justinsb
|
||||
- liujingfang1
|
||||
- mengqiy
|
||||
- knverey
|
||||
- monopole
|
||||
- natasha41575
|
||||
- pwittrock
|
||||
kustomize-reviewers:
|
||||
- knverey
|
||||
- monopole
|
||||
- natasha41575
|
||||
|
||||
kyaml-approvers:
|
||||
- mengqiy
|
||||
- mortent
|
||||
- phanimarupaka
|
||||
kyaml-reviewers:
|
||||
- mengqiy
|
||||
- mortent
|
||||
- phanimarupaka
|
||||
|
||||
emeritus-approvers:
|
||||
- liujingfang1
|
||||
- Shell32-Natsu
|
||||
|
||||
19
README.md
19
README.md
@@ -22,15 +22,22 @@ This tool is sponsored by [sig-cli] ([KEP]).
|
||||
|
||||
The kustomize build flow at [v2.0.3] was added
|
||||
to [kubectl v1.14][kubectl announcement]. The kustomize
|
||||
flow in kubectl has remained frozen at v2.0.3 while work
|
||||
to extract kubectl from the k/k repo, and work to remove
|
||||
kustomize's dependence on core k/k code ([#2506]) has proceeded.
|
||||
The reintegration effort is tracked in [#1500] (and its blocking
|
||||
issues).
|
||||
flow in kubectl remained frozen at v2.0.3 until kubectl v1.21,
|
||||
which [updated it to v4.0.5][kust-in-kubectl update]. It will
|
||||
be updated on a regular basis going forward, and such updates
|
||||
will be reflected in the Kubernetes release notes.
|
||||
|
||||
| Kubectl version | Kustomize version |
|
||||
| --- | --- |
|
||||
| < v1.14 | n/a |
|
||||
| v1.14-v1.20 | v2.0.3 |
|
||||
| v1.21 | v4.0.5 |
|
||||
| v1.22 | v4.2.0 |
|
||||
|
||||
[v2.0.3]: /../../tree/v2.0.3
|
||||
[#2506]: https://github.com/kubernetes-sigs/kustomize/issues/2506
|
||||
[#1500]: https://github.com/kubernetes-sigs/kustomize/issues/1500
|
||||
[kust-in-kubectl update]: https://github.com/kubernetes/kubernetes/blob/4d75a6238a6e330337526e0513e67d02b1940b63/CHANGELOG/CHANGELOG-1.21.md#kustomize-updates-in-kubectl
|
||||
|
||||
For examples and guides for using the kubectl integration please
|
||||
see the [kubectl book] or the [kubernetes documentation].
|
||||
@@ -145,7 +152,7 @@ is governed by the [Kubernetes Code of Conduct].
|
||||
[`make`]: https://www.gnu.org/software/make
|
||||
[`sed`]: https://www.gnu.org/software/sed
|
||||
[DAM]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#declarative-application-management
|
||||
[KEP]: https://github.com/kubernetes/enhancements/blob/master/keps/sig-cli/0008-kustomize.md
|
||||
[KEP]: https://github.com/kubernetes/enhancements/blob/master/keps/sig-cli/2377-Kustomize/README.md
|
||||
[Kubernetes Code of Conduct]: code-of-conduct.md
|
||||
[applied]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#apply
|
||||
[base]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#base
|
||||
|
||||
@@ -27,16 +27,10 @@ func (p *AnnotationsTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
if len(p.Annotations) == 0 {
|
||||
return nil
|
||||
}
|
||||
for _, r := range m.Resources() {
|
||||
err := r.ApplyFilter(annotations.Filter{
|
||||
Annotations: p.Annotations,
|
||||
FsSlice: p.FieldSpecs,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return m.ApplyFilter(annotations.Filter{
|
||||
Annotations: p.Annotations,
|
||||
FsSlice: p.FieldSpecs,
|
||||
})
|
||||
}
|
||||
|
||||
func NewAnnotationsTransformerPlugin() resmap.TransformerPlugin {
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
type HashTransformerPlugin struct {
|
||||
hasher ifc.KunstructuredHasher
|
||||
hasher ifc.KustHasher
|
||||
}
|
||||
|
||||
func (p *HashTransformerPlugin) Config(
|
||||
@@ -24,11 +24,11 @@ func (p *HashTransformerPlugin) Config(
|
||||
func (p *HashTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
for _, res := range m.Resources() {
|
||||
if res.NeedHashSuffix() {
|
||||
h, err := p.hasher.Hash(res)
|
||||
h, err := res.Hash(p.hasher)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res.SetOriginalName(res.GetName(), false)
|
||||
res.StorePreviousId()
|
||||
res.SetName(fmt.Sprintf("%s-%s", res.GetName(), h))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,17 +6,15 @@ package builtins
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/imdario/mergo"
|
||||
"github.com/pkg/errors"
|
||||
"sigs.k8s.io/kustomize/api/filesys"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/yaml"
|
||||
@@ -25,207 +23,291 @@ import (
|
||||
// HelmChartInflationGeneratorPlugin is a plugin to generate resources
|
||||
// from a remote or local helm chart.
|
||||
type HelmChartInflationGeneratorPlugin struct {
|
||||
h *resmap.PluginHelpers
|
||||
types.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
||||
runHelmCommand func([]string) ([]byte, error)
|
||||
types.HelmChartArgs
|
||||
h *resmap.PluginHelpers
|
||||
types.HelmGlobals
|
||||
types.HelmChart
|
||||
tmpDir string
|
||||
}
|
||||
|
||||
var KustomizePlugin HelmChartInflationGeneratorPlugin
|
||||
|
||||
const (
|
||||
valuesMergeOptionMerge = "merge"
|
||||
valuesMergeOptionOverride = "override"
|
||||
valuesMergeOptionReplace = "replace"
|
||||
)
|
||||
|
||||
var legalMergeOptions = []string{
|
||||
valuesMergeOptionMerge,
|
||||
valuesMergeOptionOverride,
|
||||
valuesMergeOptionReplace,
|
||||
}
|
||||
|
||||
// Config uses the input plugin configurations `config` to setup the generator
|
||||
// options
|
||||
func (p *HelmChartInflationGeneratorPlugin) Config(h *resmap.PluginHelpers, config []byte) error {
|
||||
func (p *HelmChartInflationGeneratorPlugin) Config(
|
||||
h *resmap.PluginHelpers, config []byte) (err error) {
|
||||
if h.GeneralConfig() == nil {
|
||||
return fmt.Errorf("unable to access general config")
|
||||
}
|
||||
if !h.GeneralConfig().HelmConfig.Enabled {
|
||||
return fmt.Errorf("must specify --enable-helm")
|
||||
}
|
||||
if h.GeneralConfig().HelmConfig.Command == "" {
|
||||
return fmt.Errorf("must specify --helm-command")
|
||||
}
|
||||
p.h = h
|
||||
err := yaml.Unmarshal(config, p)
|
||||
if err != nil {
|
||||
return err
|
||||
if err = yaml.Unmarshal(config, p); err != nil {
|
||||
return
|
||||
}
|
||||
tmpDir, err := filesys.NewTmpConfirmedDir()
|
||||
if err != nil {
|
||||
return err
|
||||
return p.validateArgs()
|
||||
}
|
||||
|
||||
// This uses the real file system since tmpDir may be used
|
||||
// by the helm subprocess. Cannot use a chroot jail or fake
|
||||
// filesystem since we allow the user to use previously
|
||||
// downloaded charts. This is safe since this plugin is
|
||||
// owned by kustomize.
|
||||
func (p *HelmChartInflationGeneratorPlugin) establishTmpDir() (err error) {
|
||||
if p.tmpDir != "" {
|
||||
// already done.
|
||||
return nil
|
||||
}
|
||||
p.tmpDir = string(tmpDir)
|
||||
if p.ChartName == "" {
|
||||
return fmt.Errorf("chartName cannot be empty")
|
||||
p.tmpDir, err = ioutil.TempDir("", "kustomize-helm-")
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *HelmChartInflationGeneratorPlugin) validateArgs() (err error) {
|
||||
if p.Name == "" {
|
||||
return fmt.Errorf("chart name cannot be empty")
|
||||
}
|
||||
|
||||
// ChartHome might be consulted by the plugin (to read
|
||||
// values files below it), so it must be located under
|
||||
// the loader root (unless root restrictions are
|
||||
// disabled, in which case this can be an absolute path).
|
||||
if p.ChartHome == "" {
|
||||
p.ChartHome = path.Join(p.tmpDir, "chart")
|
||||
p.ChartHome = "charts"
|
||||
}
|
||||
if p.ChartRepoName == "" {
|
||||
p.ChartRepoName = "stable"
|
||||
|
||||
// The ValuesFile may be consulted by the plugin, so it must
|
||||
// be under the loader root (unless root restrictions are
|
||||
// disabled).
|
||||
if p.ValuesFile == "" {
|
||||
p.ValuesFile = filepath.Join(p.ChartHome, p.Name, "values.yaml")
|
||||
}
|
||||
if p.HelmBin == "" {
|
||||
p.HelmBin = "helm"
|
||||
|
||||
if err = p.errIfIllegalValuesMerge(); err != nil {
|
||||
return err
|
||||
}
|
||||
if p.HelmHome == "" {
|
||||
p.HelmHome = path.Join(p.tmpDir, ".helm")
|
||||
}
|
||||
if p.Values == "" {
|
||||
p.Values = path.Join(p.ChartHome, p.ChartName, "values.yaml")
|
||||
|
||||
// ConfigHome is not loaded by the plugin, and can be located anywhere.
|
||||
if p.ConfigHome == "" {
|
||||
if err = p.establishTmpDir(); err != nil {
|
||||
return errors.Wrap(
|
||||
err, "unable to create tmp dir for HELM_CONFIG_HOME")
|
||||
}
|
||||
p.ConfigHome = filepath.Join(p.tmpDir, "helm")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *HelmChartInflationGeneratorPlugin) errIfIllegalValuesMerge() error {
|
||||
if p.ValuesMerge == "" {
|
||||
p.ValuesMerge = "override"
|
||||
// Use the default.
|
||||
p.ValuesMerge = valuesMergeOptionOverride
|
||||
return nil
|
||||
}
|
||||
// runHelmCommand will run `helm` command with args provided. Return stdout
|
||||
// and error if there is any.
|
||||
p.runHelmCommand = func(args []string) ([]byte, error) {
|
||||
stdout := new(bytes.Buffer)
|
||||
stderr := new(bytes.Buffer)
|
||||
cmd := exec.Command(p.HelmBin, args...)
|
||||
cmd.Stdout = stdout
|
||||
cmd.Stderr = stderr
|
||||
cmd.Env = append(cmd.Env,
|
||||
fmt.Sprintf("HELM_CONFIG_HOME=%s", p.HelmHome),
|
||||
fmt.Sprintf("HELM_CACHE_HOME=%s/.cache", p.HelmHome),
|
||||
fmt.Sprintf("HELM_DATA_HOME=%s/.data", p.HelmHome),
|
||||
for _, opt := range legalMergeOptions {
|
||||
if p.ValuesMerge == opt {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("valuesMerge must be one of %v", legalMergeOptions)
|
||||
}
|
||||
|
||||
func (p *HelmChartInflationGeneratorPlugin) absChartHome() string {
|
||||
if filepath.IsAbs(p.ChartHome) {
|
||||
return p.ChartHome
|
||||
}
|
||||
return filepath.Join(p.h.Loader().Root(), p.ChartHome)
|
||||
}
|
||||
|
||||
func (p *HelmChartInflationGeneratorPlugin) runHelmCommand(
|
||||
args []string) ([]byte, error) {
|
||||
stdout := new(bytes.Buffer)
|
||||
stderr := new(bytes.Buffer)
|
||||
cmd := exec.Command(p.h.GeneralConfig().HelmConfig.Command, args...)
|
||||
cmd.Stdout = stdout
|
||||
cmd.Stderr = stderr
|
||||
env := []string{
|
||||
fmt.Sprintf("HELM_CONFIG_HOME=%s", p.ConfigHome),
|
||||
fmt.Sprintf("HELM_CACHE_HOME=%s/.cache", p.ConfigHome),
|
||||
fmt.Sprintf("HELM_DATA_HOME=%s/.data", p.ConfigHome)}
|
||||
cmd.Env = append(os.Environ(), env...)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
helm := p.h.GeneralConfig().HelmConfig.Command
|
||||
err = errors.Wrap(
|
||||
fmt.Errorf(
|
||||
"unable to run: '%s %s' with env=%s (is '%s' installed?)",
|
||||
helm, strings.Join(args, " "), env, helm),
|
||||
stderr.String(),
|
||||
)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return stdout.Bytes(),
|
||||
errors.Wrap(
|
||||
fmt.Errorf("failed to run command %s %s", p.HelmBin, strings.Join(args, " ")),
|
||||
stderr.String(),
|
||||
)
|
||||
}
|
||||
return stdout.Bytes(), nil
|
||||
}
|
||||
return nil
|
||||
return stdout.Bytes(), err
|
||||
}
|
||||
|
||||
// EncodeValues for writing
|
||||
func (p *HelmChartInflationGeneratorPlugin) EncodeValues(w io.Writer) error {
|
||||
d, err := yaml.Marshal(p.ValuesLocal)
|
||||
if err != nil {
|
||||
return err
|
||||
// createNewMergedValuesFile replaces/merges original values file with ValuesInline.
|
||||
func (p *HelmChartInflationGeneratorPlugin) createNewMergedValuesFile() (
|
||||
path string, err error) {
|
||||
if p.ValuesMerge == valuesMergeOptionMerge ||
|
||||
p.ValuesMerge == valuesMergeOptionOverride {
|
||||
if err = p.replaceValuesInline(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
_, err = w.Write(d)
|
||||
var b []byte
|
||||
b, err = yaml.Marshal(p.ValuesInline)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
return nil
|
||||
return p.writeValuesBytes(b)
|
||||
}
|
||||
|
||||
// useValuesLocal process (merge) inflator config provided values with chart default values.yaml
|
||||
func (p *HelmChartInflationGeneratorPlugin) useValuesLocal() error {
|
||||
fn := path.Join(p.ChartHome, p.ChartName, "kustomize-values.yaml")
|
||||
vf, err := os.Create(fn)
|
||||
defer vf.Close()
|
||||
func (p *HelmChartInflationGeneratorPlugin) replaceValuesInline() error {
|
||||
pValues, err := p.h.Loader().Load(p.ValuesFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// override, merge, none
|
||||
if p.ValuesMerge == "none" || p.ValuesMerge == "no" || p.ValuesMerge == "false" {
|
||||
p.Values = fn
|
||||
} else {
|
||||
pValues, err := ioutil.ReadFile(p.Values)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chValues := make(map[string]interface{})
|
||||
err = yaml.Unmarshal(pValues, &chValues)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if p.ValuesMerge == "override" {
|
||||
err = mergo.Merge(&chValues, p.ValuesLocal, mergo.WithOverride)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if p.ValuesMerge == "merge" {
|
||||
err = mergo.Merge(&chValues, p.ValuesLocal)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
p.ValuesLocal = chValues
|
||||
p.Values = fn
|
||||
}
|
||||
err = p.EncodeValues(vf)
|
||||
if err != nil {
|
||||
chValues := make(map[string]interface{})
|
||||
if err = yaml.Unmarshal(pValues, &chValues); err != nil {
|
||||
return err
|
||||
}
|
||||
vf.Sync()
|
||||
return nil
|
||||
switch p.ValuesMerge {
|
||||
case valuesMergeOptionOverride:
|
||||
err = mergo.Merge(
|
||||
&chValues, p.ValuesInline, mergo.WithOverride)
|
||||
case valuesMergeOptionMerge:
|
||||
err = mergo.Merge(&chValues, p.ValuesInline)
|
||||
}
|
||||
p.ValuesInline = chValues
|
||||
return err
|
||||
}
|
||||
|
||||
// copyValuesFile to avoid branching. TODO: get rid of this.
|
||||
func (p *HelmChartInflationGeneratorPlugin) copyValuesFile() (string, error) {
|
||||
b, err := p.h.Loader().Load(p.ValuesFile)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return p.writeValuesBytes(b)
|
||||
}
|
||||
|
||||
// Write a absolute path file in the tmp file system.
|
||||
func (p *HelmChartInflationGeneratorPlugin) writeValuesBytes(
|
||||
b []byte) (string, error) {
|
||||
if err := p.establishTmpDir(); err != nil {
|
||||
return "", fmt.Errorf("cannot create tmp dir to write helm values")
|
||||
}
|
||||
path := filepath.Join(p.tmpDir, p.Name+"-kustomize-values.yaml")
|
||||
return path, ioutil.WriteFile(path, b, 0644)
|
||||
}
|
||||
|
||||
func (p *HelmChartInflationGeneratorPlugin) cleanup() {
|
||||
if p.tmpDir != "" {
|
||||
os.RemoveAll(p.tmpDir)
|
||||
}
|
||||
}
|
||||
|
||||
// Generate implements generator
|
||||
func (p *HelmChartInflationGeneratorPlugin) Generate() (resmap.ResMap, error) {
|
||||
// cleanup
|
||||
defer os.RemoveAll(p.tmpDir)
|
||||
// check helm version. we only support V3
|
||||
err := p.checkHelmVersion()
|
||||
func (p *HelmChartInflationGeneratorPlugin) Generate() (rm resmap.ResMap, err error) {
|
||||
defer p.cleanup()
|
||||
if err = p.checkHelmVersion(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if path, exists := p.chartExistsLocally(); !exists {
|
||||
if p.Repo == "" {
|
||||
return nil, fmt.Errorf(
|
||||
"no repo specified for pull, no chart found at '%s'", path)
|
||||
}
|
||||
if _, err := p.runHelmCommand(p.pullCommand()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if len(p.ValuesInline) > 0 {
|
||||
p.ValuesFile, err = p.createNewMergedValuesFile()
|
||||
} else {
|
||||
p.ValuesFile, err = p.copyValuesFile()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// pull the chart
|
||||
if !p.checkLocalChart() {
|
||||
_, err := p.runHelmCommand(p.getPullCommandArgs())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// inflator config valuesLocal
|
||||
if len(p.ValuesLocal) > 0 {
|
||||
err := p.useValuesLocal()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// render the charts
|
||||
stdout, err := p.runHelmCommand(p.getTemplateCommandArgs())
|
||||
var stdout []byte
|
||||
stdout, err = p.runHelmCommand(p.templateCommand())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return p.h.ResmapFactory().NewResMapFromBytes(stdout)
|
||||
rm, err = p.h.ResmapFactory().NewResMapFromBytes(stdout)
|
||||
if err == nil {
|
||||
return rm, nil
|
||||
}
|
||||
// try to remove the contents before first "---" because
|
||||
// helm may produce messages to stdout before it
|
||||
stdoutStr := string(stdout)
|
||||
if idx := strings.Index(stdoutStr, "---"); idx != -1 {
|
||||
return p.h.ResmapFactory().NewResMapFromBytes([]byte(stdoutStr[idx:]))
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (p *HelmChartInflationGeneratorPlugin) getTemplateCommandArgs() []string {
|
||||
func (p *HelmChartInflationGeneratorPlugin) templateCommand() []string {
|
||||
args := []string{"template"}
|
||||
if p.ReleaseName != "" {
|
||||
args = append(args, p.ReleaseName)
|
||||
}
|
||||
args = append(args, path.Join(p.ChartHome, p.ChartName))
|
||||
if p.ReleaseNamespace != "" {
|
||||
args = append(args, "--namespace", p.ReleaseNamespace)
|
||||
if p.Namespace != "" {
|
||||
args = append(args, "--namespace", p.Namespace)
|
||||
}
|
||||
if p.Values != "" {
|
||||
args = append(args, "--values", p.Values)
|
||||
args = append(args, filepath.Join(p.absChartHome(), p.Name))
|
||||
if p.ValuesFile != "" {
|
||||
args = append(args, "--values", p.ValuesFile)
|
||||
}
|
||||
if p.ReleaseName == "" {
|
||||
// AFAICT, this doesn't work as intended due to a bug in helm.
|
||||
// See https://github.com/helm/helm/issues/6019
|
||||
// I've tried placing the flag before and after the name argument.
|
||||
args = append(args, "--generate-name")
|
||||
}
|
||||
if p.IncludeCRDs {
|
||||
args = append(args, "--include-crds")
|
||||
}
|
||||
args = append(args, p.ExtraArgs...)
|
||||
return args
|
||||
}
|
||||
|
||||
func (p *HelmChartInflationGeneratorPlugin) getPullCommandArgs() []string {
|
||||
args := []string{"pull", "--untar", "--untardir", p.ChartHome}
|
||||
chartName := fmt.Sprintf("%s/%s", p.ChartRepoName, p.ChartName)
|
||||
if p.ChartVersion != "" {
|
||||
args = append(args, "--version", p.ChartVersion)
|
||||
func (p *HelmChartInflationGeneratorPlugin) pullCommand() []string {
|
||||
args := []string{
|
||||
"pull",
|
||||
"--untar",
|
||||
"--untardir", p.absChartHome(),
|
||||
"--repo", p.Repo,
|
||||
p.Name}
|
||||
if p.Version != "" {
|
||||
args = append(args, "--version", p.Version)
|
||||
}
|
||||
if p.ChartRepoURL != "" {
|
||||
args = append(args, "--repo", p.ChartRepoURL)
|
||||
chartName = p.ChartName
|
||||
}
|
||||
|
||||
args = append(args, chartName)
|
||||
|
||||
return args
|
||||
}
|
||||
|
||||
// checkLocalChart will return true if the chart does exist in
|
||||
// chartExistsLocally will return true if the chart does exist in
|
||||
// local chart home.
|
||||
func (p *HelmChartInflationGeneratorPlugin) checkLocalChart() bool {
|
||||
path := path.Join(p.ChartHome, p.ChartName)
|
||||
func (p *HelmChartInflationGeneratorPlugin) chartExistsLocally() (string, bool) {
|
||||
path := filepath.Join(p.absChartHome(), p.Name)
|
||||
s, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return false
|
||||
return "", false
|
||||
}
|
||||
return s.IsDir()
|
||||
return path, s.IsDir()
|
||||
}
|
||||
|
||||
// checkHelmVersion will return an error if the helm version is not V3
|
||||
@@ -234,11 +316,17 @@ func (p *HelmChartInflationGeneratorPlugin) checkHelmVersion() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r, err := regexp.Compile(`v\d+(\.\d+)+`)
|
||||
r, err := regexp.Compile(`v?\d+(\.\d+)+`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v := string(r.Find(stdout))[1:]
|
||||
v := r.FindString(string(stdout))
|
||||
if v == "" {
|
||||
return fmt.Errorf("cannot find version string in %s", string(stdout))
|
||||
}
|
||||
if v[0] == 'v' {
|
||||
v = v[1:]
|
||||
}
|
||||
majorVersion := strings.Split(v, ".")[0]
|
||||
if majorVersion != "3" {
|
||||
return fmt.Errorf("this plugin requires helm V3 but got v%s", v)
|
||||
|
||||
33
api/builtins/IAMPolicyGenerator.go
Normal file
33
api/builtins/IAMPolicyGenerator.go
Normal file
@@ -0,0 +1,33 @@
|
||||
// Code generated by pluginator on IAMPolicyGenerator; DO NOT EDIT.
|
||||
// pluginator {unknown 1970-01-01T00:00:00Z }
|
||||
|
||||
package builtins
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/api/filters/iampolicygenerator"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
type IAMPolicyGeneratorPlugin struct {
|
||||
types.IAMPolicyGeneratorArgs
|
||||
}
|
||||
|
||||
func (p *IAMPolicyGeneratorPlugin) Config(h *resmap.PluginHelpers, config []byte) (err error) {
|
||||
p.IAMPolicyGeneratorArgs = types.IAMPolicyGeneratorArgs{}
|
||||
err = yaml.Unmarshal(config, p)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *IAMPolicyGeneratorPlugin) Generate() (resmap.ResMap, error) {
|
||||
r := resmap.New()
|
||||
err := r.ApplyFilter(iampolicygenerator.Filter{
|
||||
IAMPolicyGenerator: p.IAMPolicyGeneratorArgs,
|
||||
})
|
||||
return r, err
|
||||
}
|
||||
|
||||
func NewIAMPolicyGeneratorPlugin() resmap.GeneratorPlugin {
|
||||
return &IAMPolicyGeneratorPlugin{}
|
||||
}
|
||||
@@ -4,10 +4,6 @@
|
||||
package builtins
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/filters/imagetag"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
@@ -29,157 +25,15 @@ func (p *ImageTagTransformerPlugin) Config(
|
||||
}
|
||||
|
||||
func (p *ImageTagTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
for _, r := range m.Resources() {
|
||||
// traverse all fields at first
|
||||
err := r.ApplyFilter(imagetag.LegacyFilter{
|
||||
ImageTag: p.ImageTag,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// then use user specified field specs
|
||||
err = r.ApplyFilter(imagetag.Filter{
|
||||
ImageTag: p.ImageTag,
|
||||
FsSlice: p.FieldSpecs,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := m.ApplyFilter(imagetag.LegacyFilter{
|
||||
ImageTag: p.ImageTag,
|
||||
}); err != nil {
|
||||
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_.{}-].
|
||||
// Some tools like Bazel rules_k8s allow tag patterns with {} characters.
|
||||
// More info: https://github.com/bazelbuild/rules_k8s/pull/423
|
||||
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
|
||||
return m.ApplyFilter(imagetag.Filter{
|
||||
ImageTag: p.ImageTag,
|
||||
FsSlice: p.FieldSpecs,
|
||||
})
|
||||
}
|
||||
|
||||
func NewImageTagTransformerPlugin() resmap.TransformerPlugin {
|
||||
|
||||
@@ -27,16 +27,10 @@ func (p *LabelTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
if len(p.Labels) == 0 {
|
||||
return nil
|
||||
}
|
||||
for _, r := range m.Resources() {
|
||||
err := r.ApplyFilter(labels.Filter{
|
||||
Labels: p.Labels,
|
||||
FsSlice: p.FieldSpecs,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return m.ApplyFilter(labels.Filter{
|
||||
Labels: p.Labels,
|
||||
FsSlice: p.FieldSpecs,
|
||||
})
|
||||
}
|
||||
|
||||
func NewLabelTransformerPlugin() resmap.TransformerPlugin {
|
||||
|
||||
@@ -30,16 +30,15 @@ func (p *NamespaceTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
return nil
|
||||
}
|
||||
for _, r := range m.Resources() {
|
||||
if r.IsEmpty() {
|
||||
if r.IsNilOrEmpty() {
|
||||
// Don't mutate empty objects?
|
||||
continue
|
||||
}
|
||||
r.SetOriginalNs(r.GetNamespace(), false)
|
||||
err := r.ApplyFilter(namespace.Filter{
|
||||
r.StorePreviousId()
|
||||
if err := r.ApplyFilter(namespace.Filter{
|
||||
Namespace: p.Namespace,
|
||||
FsSlice: p.FieldSpecs,
|
||||
})
|
||||
if err != nil {
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
matches := m.GetMatchingResourcesByCurrentId(r.CurId().Equals)
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"sigs.k8s.io/kustomize/api/ifc"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
@@ -78,12 +79,23 @@ func (p *PatchJson6902TransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
return err
|
||||
}
|
||||
for _, res := range resources {
|
||||
internalAnnotations := kioutil.GetInternalAnnotations(&res.RNode)
|
||||
|
||||
err = res.ApplyFilter(patchjson6902.Filter{
|
||||
Patch: p.JsonOp,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
annotations := res.GetAnnotations()
|
||||
for key, value := range internalAnnotations {
|
||||
annotations[key] = value
|
||||
}
|
||||
err = res.SetAnnotations(annotations)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -28,46 +28,48 @@ func (p *PatchStrategicMergeTransformerPlugin) Config(
|
||||
return fmt.Errorf("empty file path and empty patch content")
|
||||
}
|
||||
if len(p.Paths) != 0 {
|
||||
for _, onePath := range p.Paths {
|
||||
// The following oddly attempts to interpret a path string as an
|
||||
// actual patch (instead of as a path to a file containing a patch).
|
||||
// All tests pass if this code is commented out. This code should
|
||||
// be deleted; the user should use the Patches field which
|
||||
// exists for this purpose (inline patch declaration).
|
||||
res, err := h.ResmapFactory().RF().SliceFromBytes([]byte(onePath))
|
||||
if err == nil {
|
||||
p.loadedPatches = append(p.loadedPatches, res...)
|
||||
continue
|
||||
}
|
||||
res, err = h.ResmapFactory().RF().SliceFromPatches(
|
||||
h.Loader(), []types.PatchStrategicMerge{onePath})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.loadedPatches = append(p.loadedPatches, res...)
|
||||
}
|
||||
}
|
||||
if p.Patches != "" {
|
||||
res, err := h.ResmapFactory().RF().SliceFromBytes([]byte(p.Patches))
|
||||
patches, err := loadFromPaths(h, p.Paths)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.loadedPatches = append(p.loadedPatches, res...)
|
||||
p.loadedPatches = append(p.loadedPatches, patches...)
|
||||
}
|
||||
if p.Patches != "" {
|
||||
patches, err := h.ResmapFactory().RF().SliceFromBytes([]byte(p.Patches))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.loadedPatches = append(p.loadedPatches, patches...)
|
||||
}
|
||||
|
||||
if len(p.loadedPatches) == 0 {
|
||||
return fmt.Errorf(
|
||||
"patch appears to be empty; files=%v, Patch=%s", p.Paths, p.Patches)
|
||||
}
|
||||
// Merge the patches, looking for conflicts.
|
||||
m, err := h.ResmapFactory().ConflatePatches(p.loadedPatches)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.loadedPatches = m.Resources()
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadFromPaths(
|
||||
h *resmap.PluginHelpers,
|
||||
paths []types.PatchStrategicMerge) (
|
||||
result []*resource.Resource, err error) {
|
||||
var patches []*resource.Resource
|
||||
for _, path := range paths {
|
||||
// For legacy reasons, attempt to treat the path string as
|
||||
// actual patch content.
|
||||
patches, err = h.ResmapFactory().RF().SliceFromBytes([]byte(path))
|
||||
if err != nil {
|
||||
// Failing that, treat it as a file path.
|
||||
patches, err = h.ResmapFactory().RF().SliceFromPatches(
|
||||
h.Loader(), []types.PatchStrategicMerge{path})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
result = append(result, patches...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p *PatchStrategicMergeTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
for _, patch := range p.loadedPatches {
|
||||
target, err := m.GetById(patch.OrgId())
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/resource"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
@@ -21,6 +22,7 @@ type PatchTransformerPlugin struct {
|
||||
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"`
|
||||
Options map[string]bool `json:"options,omitempty" yaml:"options,omitempty"`
|
||||
}
|
||||
|
||||
func (p *PatchTransformerPlugin) Config(
|
||||
@@ -60,6 +62,12 @@ func (p *PatchTransformerPlugin) Config(
|
||||
}
|
||||
if errSM == nil {
|
||||
p.loadedPatch = patchSM
|
||||
if p.Options["allowNameChange"] {
|
||||
p.loadedPatch.AllowNameChange()
|
||||
}
|
||||
if p.Options["allowKindChange"] {
|
||||
p.loadedPatch.AllowKindChange()
|
||||
}
|
||||
} else {
|
||||
p.decodedPatch = patchJson
|
||||
}
|
||||
@@ -69,10 +77,9 @@ func (p *PatchTransformerPlugin) Config(
|
||||
func (p *PatchTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
if p.loadedPatch == nil {
|
||||
return p.transformJson6902(m, p.decodedPatch)
|
||||
} else {
|
||||
// The patch was a strategic merge patch
|
||||
return p.transformStrategicMerge(m, p.loadedPatch)
|
||||
}
|
||||
// The patch was a strategic merge patch
|
||||
return p.transformStrategicMerge(m, p.loadedPatch)
|
||||
}
|
||||
|
||||
// transformStrategicMerge applies the provided strategic merge patch
|
||||
@@ -104,13 +111,20 @@ func (p *PatchTransformerPlugin) transformJson6902(m resmap.ResMap, patch jsonpa
|
||||
return err
|
||||
}
|
||||
for _, res := range resources {
|
||||
res.SetOriginalName(res.GetName(), false)
|
||||
res.StorePreviousId()
|
||||
internalAnnotations := kioutil.GetInternalAnnotations(&res.RNode)
|
||||
err = res.ApplyFilter(patchjson6902.Filter{
|
||||
Patch: p.Patch,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
annotations := res.GetAnnotations()
|
||||
for key, value := range internalAnnotations {
|
||||
annotations[key] = value
|
||||
}
|
||||
err = res.SetAnnotations(annotations)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
"errors"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/filters/prefixsuffix"
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/resid"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
@@ -70,15 +70,14 @@ func (p *PrefixSuffixTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
r.AddNamePrefix(p.Prefix)
|
||||
r.AddNameSuffix(p.Suffix)
|
||||
if p.Prefix != "" || p.Suffix != "" {
|
||||
r.SetOriginalName(r.GetName(), false)
|
||||
r.StorePreviousId()
|
||||
}
|
||||
}
|
||||
err := r.ApplyFilter(prefixsuffix.Filter{
|
||||
if err := r.ApplyFilter(prefixsuffix.Filter{
|
||||
Prefix: p.Prefix,
|
||||
Suffix: p.Suffix,
|
||||
FieldSpec: fs,
|
||||
})
|
||||
if err != nil {
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
59
api/builtins/ReplacementTransformer.go
Normal file
59
api/builtins/ReplacementTransformer.go
Normal file
@@ -0,0 +1,59 @@
|
||||
// Code generated by pluginator on ReplacementTransformer; DO NOT EDIT.
|
||||
// pluginator {unknown 1970-01-01T00:00:00Z }
|
||||
|
||||
package builtins
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/filters/replacement"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
// Replace values in targets with values from a source
|
||||
type ReplacementTransformerPlugin struct {
|
||||
ReplacementList []types.ReplacementField `json:"replacements,omitempty" yaml:"replacements,omitempty"`
|
||||
Replacements []types.Replacement `json:"omitempty" yaml:"omitempty"`
|
||||
}
|
||||
|
||||
func (p *ReplacementTransformerPlugin) Config(
|
||||
h *resmap.PluginHelpers, c []byte) (err error) {
|
||||
p.ReplacementList = []types.ReplacementField{}
|
||||
if err := yaml.Unmarshal(c, p); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, r := range p.ReplacementList {
|
||||
if r.Path != "" && (r.Source != nil || len(r.Targets) != 0) {
|
||||
return fmt.Errorf("cannot specify both path and inline replacement")
|
||||
}
|
||||
if r.Path != "" {
|
||||
// load the replacement from the path
|
||||
content, err := h.Loader().Load(r.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
repl := types.Replacement{}
|
||||
if err := yaml.Unmarshal(content, &repl); err != nil {
|
||||
return err
|
||||
}
|
||||
p.Replacements = append(p.Replacements, repl)
|
||||
} else {
|
||||
// replacement information is already loaded
|
||||
p.Replacements = append(p.Replacements, r.Replacement)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *ReplacementTransformerPlugin) Transform(m resmap.ResMap) (err error) {
|
||||
return m.ApplyFilter(replacement.Filter{
|
||||
Replacements: p.Replacements,
|
||||
})
|
||||
}
|
||||
|
||||
func NewReplacementTransformerPlugin() resmap.TransformerPlugin {
|
||||
return &ReplacementTransformerPlugin{}
|
||||
}
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
"fmt"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/filters/replicacount"
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/resid"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
@@ -31,9 +31,7 @@ func (p *ReplicaCountTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
found := false
|
||||
for _, fs := range p.FieldSpecs {
|
||||
matcher := p.createMatcher(fs)
|
||||
matchOriginal := m.GetMatchingResourcesByOriginalId(matcher)
|
||||
resList := append(
|
||||
matchOriginal, m.GetMatchingResourcesByCurrentId(matcher)...)
|
||||
resList := m.GetMatchingResourcesByAnyId(matcher)
|
||||
if len(resList) > 0 {
|
||||
found = true
|
||||
for _, r := range resList {
|
||||
|
||||
61
api/filesys/filesys.go
Normal file
61
api/filesys/filesys.go
Normal file
@@ -0,0 +1,61 @@
|
||||
// Copyright 2021 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package filesys provides a file system abstraction,
|
||||
// a subset of that provided by golang.org/pkg/os,
|
||||
// with an on-disk and in-memory representation.
|
||||
//
|
||||
// Deprecated: use sigs.k8s.io/kustomize/kyaml/filesys instead.
|
||||
package filesys
|
||||
|
||||
import "sigs.k8s.io/kustomize/kyaml/filesys"
|
||||
|
||||
const (
|
||||
// Separator is deprecated, use sigs.k8s.io/kustomize/kyaml/filesys.Separator.
|
||||
Separator = filesys.Separator
|
||||
// SelfDir is deprecated, use sigs.k8s.io/kustomize/kyaml/filesys.SelfDir.
|
||||
SelfDir = filesys.SelfDir
|
||||
// ParentDir is deprecated, use sigs.k8s.io/kustomize/kyaml/filesys.ParentDir.
|
||||
ParentDir = filesys.ParentDir
|
||||
)
|
||||
|
||||
type (
|
||||
// FileSystem is deprecated, use sigs.k8s.io/kustomize/kyaml/filesys.FileSystem.
|
||||
FileSystem = filesys.FileSystem
|
||||
// FileSystemOrOnDisk is deprecated, use sigs.k8s.io/kustomize/kyaml/filesys.FileSystemOrOnDisk.
|
||||
FileSystemOrOnDisk = filesys.FileSystemOrOnDisk
|
||||
// ConfirmedDir is deprecated, use sigs.k8s.io/kustomize/kyaml/filesys.ConfirmedDir.
|
||||
ConfirmedDir = filesys.ConfirmedDir
|
||||
)
|
||||
|
||||
// MakeEmptyDirInMemory is deprecated, use sigs.k8s.io/kustomize/kyaml/filesys.MakeEmptyDirInMemory.
|
||||
func MakeEmptyDirInMemory() FileSystem { return filesys.MakeEmptyDirInMemory() }
|
||||
|
||||
// MakeFsInMemory is deprecated, use sigs.k8s.io/kustomize/kyaml/filesys.MakeFsInMemory.
|
||||
func MakeFsInMemory() FileSystem { return filesys.MakeFsInMemory() }
|
||||
|
||||
// MakeFsOnDisk is deprecated, use sigs.k8s.io/kustomize/kyaml/filesys.MakeFsOnDisk.
|
||||
func MakeFsOnDisk() FileSystem { return filesys.MakeFsOnDisk() }
|
||||
|
||||
// NewTmpConfirmedDir is deprecated, use sigs.k8s.io/kustomize/kyaml/filesys.NewTmpConfirmedDir.
|
||||
func NewTmpConfirmedDir() (filesys.ConfirmedDir, error) { return filesys.NewTmpConfirmedDir() }
|
||||
|
||||
// RootedPath is deprecated, use sigs.k8s.io/kustomize/kyaml/filesys.RootedPath.
|
||||
func RootedPath(elem ...string) string { return filesys.RootedPath(elem...) }
|
||||
|
||||
// StripTrailingSeps is deprecated, use sigs.k8s.io/kustomize/kyaml/filesys.StripTrailingSeps.
|
||||
func StripTrailingSeps(s string) string { return filesys.StripTrailingSeps(s) }
|
||||
|
||||
// StripLeadingSeps is deprecated, use sigs.k8s.io/kustomize/kyaml/filesys.StripLeadingSeps.
|
||||
func StripLeadingSeps(s string) string { return filesys.StripLeadingSeps(s) }
|
||||
|
||||
// PathSplit is deprecated, use sigs.k8s.io/kustomize/kyaml/filesys.PathSplit.
|
||||
func PathSplit(incoming string) []string { return filesys.PathSplit(incoming) }
|
||||
|
||||
// PathJoin is deprecated, use sigs.k8s.io/kustomize/kyaml/filesys.PathJoin.
|
||||
func PathJoin(incoming []string) string { return filesys.PathJoin(incoming) }
|
||||
|
||||
// InsertPathPart is deprecated, use sigs.k8s.io/kustomize/kyaml/filesys.InsertPathPart.
|
||||
func InsertPathPart(path string, pos int, part string) string {
|
||||
return filesys.InsertPathPart(path, pos, part)
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
// 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
|
||||
}
|
||||
5
api/filters/doc.go
Normal file
5
api/filters/doc.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package filters
|
||||
|
||||
// Package filters collects various implementations
|
||||
// sigs.k8s.io/kustomize/kyaml/kio.Filter used by kustomize
|
||||
// transformers to modify kubernetes objects.
|
||||
@@ -4,17 +4,29 @@
|
||||
package fieldspec
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/filters/filtersutil"
|
||||
"sigs.k8s.io/kustomize/api/internal/utils"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/errors"
|
||||
"sigs.k8s.io/kustomize/kyaml/resid"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
var _ yaml.Filter = Filter{}
|
||||
|
||||
// Filter applies a single fieldSpec to a single object
|
||||
// Filter possibly mutates its object argument using a FieldSpec.
|
||||
// If the object matches the FieldSpec, and the node found
|
||||
// by following the fieldSpec's path is non-null, this filter calls
|
||||
// the setValue function on the node at the end of the path.
|
||||
// If any part of the path doesn't exist, the filter returns
|
||||
// without doing anything and without error, unless it was set
|
||||
// to create the path. If set to create, it creates a tree of maps
|
||||
// along the path, and the leaf node gets the setValue called on it.
|
||||
// Error on GVK mismatch, empty or poorly formed path.
|
||||
// Filter expect kustomize style paths, not JSON paths.
|
||||
// Filter stores internal state and should not be reused
|
||||
type Filter struct {
|
||||
// FieldSpec contains the path to the value to set.
|
||||
@@ -34,47 +46,53 @@ type Filter struct {
|
||||
|
||||
func (fltr Filter) Filter(obj *yaml.RNode) (*yaml.RNode, error) {
|
||||
// check if the FieldSpec applies to the object
|
||||
if match, err := isMatchGVK(fltr.FieldSpec, obj); !match || err != nil {
|
||||
return obj, errors.Wrap(err)
|
||||
if match := isMatchGVK(fltr.FieldSpec, obj); !match {
|
||||
return obj, nil
|
||||
}
|
||||
fltr.path = splitPath(fltr.FieldSpec.Path)
|
||||
fltr.path = utils.PathSplitter(fltr.FieldSpec.Path, "/")
|
||||
if err := fltr.filter(obj); err != nil {
|
||||
s, _ := obj.String()
|
||||
return nil, errors.WrapPrefixf(err,
|
||||
"obj '%s' at path '%v'", s, fltr.FieldSpec.Path)
|
||||
"considering field '%s' of object\n%v", fltr.FieldSpec.Path, s)
|
||||
}
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
// Recursively called.
|
||||
func (fltr Filter) filter(obj *yaml.RNode) error {
|
||||
if len(fltr.path) == 0 {
|
||||
// found the field -- set its value
|
||||
return fltr.SetValue(obj)
|
||||
}
|
||||
if obj.IsTaggedNull() {
|
||||
if obj.IsTaggedNull() || obj.IsNil() {
|
||||
return nil
|
||||
}
|
||||
switch obj.YNode().Kind {
|
||||
case yaml.SequenceNode:
|
||||
return fltr.seq(obj)
|
||||
return fltr.handleSequence(obj)
|
||||
case yaml.MappingNode:
|
||||
return fltr.field(obj)
|
||||
return fltr.handleMap(obj)
|
||||
case yaml.AliasNode:
|
||||
return fltr.filter(yaml.NewRNode(obj.YNode().Alias))
|
||||
default:
|
||||
return errors.Errorf("expected sequence or mapping node")
|
||||
}
|
||||
}
|
||||
|
||||
// field calls filter on the field matching the next path element
|
||||
func (fltr Filter) field(obj *yaml.RNode) error {
|
||||
// handleMap calls filter on the map field matching the next path element
|
||||
func (fltr Filter) handleMap(obj *yaml.RNode) error {
|
||||
fieldName, isSeq := isSequenceField(fltr.path[0])
|
||||
if fieldName == "" {
|
||||
return fmt.Errorf("cannot set or create an empty field name")
|
||||
}
|
||||
// lookup the field matching the next path element
|
||||
var lookupField yaml.Filter
|
||||
var operation yaml.Filter
|
||||
var kind yaml.Kind
|
||||
tag := yaml.NodeTagEmpty
|
||||
switch {
|
||||
case !fltr.FieldSpec.CreateIfNotPresent || fltr.CreateKind == 0 || isSeq:
|
||||
// dont' create the field if we don't find it
|
||||
lookupField = yaml.Lookup(fieldName)
|
||||
// don't create the field if we don't find it
|
||||
operation = yaml.Lookup(fieldName)
|
||||
if isSeq {
|
||||
// The query path thinks this field should be a sequence;
|
||||
// accept this hint for use later if the tag is NodeTagNull.
|
||||
@@ -82,21 +100,25 @@ func (fltr Filter) field(obj *yaml.RNode) error {
|
||||
}
|
||||
case len(fltr.path) <= 1:
|
||||
// create the field if it is missing: use the provided node kind
|
||||
lookupField = yaml.LookupCreate(fltr.CreateKind, fieldName)
|
||||
operation = yaml.LookupCreate(fltr.CreateKind, fieldName)
|
||||
kind = fltr.CreateKind
|
||||
tag = fltr.CreateTag
|
||||
default:
|
||||
// create the field if it is missing: must be a mapping node
|
||||
lookupField = yaml.LookupCreate(yaml.MappingNode, fieldName)
|
||||
operation = yaml.LookupCreate(yaml.MappingNode, fieldName)
|
||||
kind = yaml.MappingNode
|
||||
tag = yaml.NodeTagMap
|
||||
}
|
||||
|
||||
// locate (or maybe create) the field
|
||||
field, err := obj.Pipe(lookupField)
|
||||
if err != nil || field == nil {
|
||||
field, err := obj.Pipe(operation)
|
||||
if err != nil {
|
||||
return errors.WrapPrefixf(err, "fieldName: %s", fieldName)
|
||||
}
|
||||
if field == nil {
|
||||
// No error if field not found.
|
||||
return nil
|
||||
}
|
||||
|
||||
// if the value exists, but is null and kind is set,
|
||||
// then change it to the creation type
|
||||
@@ -114,7 +136,7 @@ func (fltr Filter) field(obj *yaml.RNode) error {
|
||||
}
|
||||
|
||||
// seq calls filter on all sequence elements
|
||||
func (fltr Filter) seq(obj *yaml.RNode) error {
|
||||
func (fltr Filter) handleSequence(obj *yaml.RNode) error {
|
||||
if err := obj.VisitElements(func(node *yaml.RNode) error {
|
||||
// recurse on each element -- re-allocating a Filter is
|
||||
// not strictly required, but is more consistent with field
|
||||
@@ -125,56 +147,35 @@ func (fltr Filter) seq(obj *yaml.RNode) error {
|
||||
return errors.WrapPrefixf(err,
|
||||
"visit traversal on path: %v", fltr.path)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// isSequenceField returns true if the path element is for a sequence field.
|
||||
// isSequence also returns the path element with the '[]' suffix trimmed
|
||||
func isSequenceField(name string) (string, bool) {
|
||||
isSeq := strings.HasSuffix(name, "[]")
|
||||
name = strings.TrimSuffix(name, "[]")
|
||||
return name, isSeq
|
||||
shorter := strings.TrimSuffix(name, "[]")
|
||||
return shorter, shorter != name
|
||||
}
|
||||
|
||||
// isMatchGVK returns true if the fs.GVK matches the obj GVK.
|
||||
func isMatchGVK(fs types.FieldSpec, obj *yaml.RNode) (bool, error) {
|
||||
meta, err := obj.GetMeta()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if fs.Kind != "" && fs.Kind != meta.Kind {
|
||||
func isMatchGVK(fs types.FieldSpec, obj *yaml.RNode) bool {
|
||||
if kind := obj.GetKind(); fs.Kind != "" && fs.Kind != kind {
|
||||
// kind doesn't match
|
||||
return false, err
|
||||
return false
|
||||
}
|
||||
|
||||
// parse the group and version from the apiVersion field
|
||||
group, version := parseGV(meta.APIVersion)
|
||||
group, version := resid.ParseGroupVersion(obj.GetApiVersion())
|
||||
|
||||
if fs.Group != "" && fs.Group != group {
|
||||
// group doesn't match
|
||||
return false, nil
|
||||
return false
|
||||
}
|
||||
|
||||
if fs.Version != "" && fs.Version != version {
|
||||
// version doesn't match
|
||||
return false, nil
|
||||
return false
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func splitPath(path string) []string {
|
||||
ps := strings.Split(path, "/")
|
||||
var res []string
|
||||
res = append(res, ps[0])
|
||||
for i := 1; i < len(ps); i++ {
|
||||
lastIndex := len(res) - 1
|
||||
if strings.HasSuffix(res[lastIndex], "\\") {
|
||||
res[lastIndex] = strings.TrimSuffix(res[lastIndex], "\\") + "/" + ps[i]
|
||||
} else {
|
||||
res = append(res, ps[i])
|
||||
}
|
||||
}
|
||||
return res
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -15,206 +15,247 @@ import (
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
type TestCase struct {
|
||||
name string
|
||||
input string
|
||||
expected string
|
||||
filter fieldspec.Filter
|
||||
fieldSpec string
|
||||
error string
|
||||
}
|
||||
|
||||
var tests = []TestCase{
|
||||
{
|
||||
name: "update",
|
||||
fieldSpec: `
|
||||
func TestFilter_Filter(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
input string
|
||||
expected string
|
||||
filter fieldspec.Filter
|
||||
fieldSpec string
|
||||
error string
|
||||
}{
|
||||
"path not found": {
|
||||
fieldSpec: `
|
||||
path: a/b
|
||||
group: foo
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
input: `
|
||||
apiVersion: foo
|
||||
kind: Bar
|
||||
xxx:
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: foo
|
||||
kind: Bar
|
||||
xxx:
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
},
|
||||
},
|
||||
"empty path": {
|
||||
fieldSpec: `
|
||||
group: foo
|
||||
version: v1
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
apiVersion: foo/v1
|
||||
kind: Bar
|
||||
xxx:
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: foo
|
||||
kind: Bar
|
||||
xxx:
|
||||
`,
|
||||
error: `considering field '' of object
|
||||
apiVersion: foo/v1
|
||||
kind: Bar
|
||||
xxx:
|
||||
: cannot set or create an empty field name`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
},
|
||||
},
|
||||
|
||||
"update": {
|
||||
fieldSpec: `
|
||||
path: a/b
|
||||
group: foo
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
apiVersion: foo/v1beta1
|
||||
kind: Bar
|
||||
a:
|
||||
b: c
|
||||
`,
|
||||
expected: `
|
||||
expected: `
|
||||
apiVersion: foo/v1beta1
|
||||
kind: Bar
|
||||
a:
|
||||
b: e
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "update-kind-not-match",
|
||||
fieldSpec: `
|
||||
"update-kind-not-match": {
|
||||
fieldSpec: `
|
||||
path: a/b
|
||||
group: foo
|
||||
kind: Bar1
|
||||
`,
|
||||
input: `
|
||||
input: `
|
||||
apiVersion: foo/v1beta1
|
||||
kind: Bar2
|
||||
a:
|
||||
b: c
|
||||
`,
|
||||
expected: `
|
||||
expected: `
|
||||
apiVersion: foo/v1beta1
|
||||
kind: Bar2
|
||||
a:
|
||||
b: c
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "update-group-not-match",
|
||||
fieldSpec: `
|
||||
"update-group-not-match": {
|
||||
fieldSpec: `
|
||||
path: a/b
|
||||
group: foo1
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
input: `
|
||||
apiVersion: foo2/v1beta1
|
||||
kind: Bar
|
||||
a:
|
||||
b: c
|
||||
`,
|
||||
expected: `
|
||||
expected: `
|
||||
apiVersion: foo2/v1beta1
|
||||
kind: Bar
|
||||
a:
|
||||
b: c
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "update-version-not-match",
|
||||
fieldSpec: `
|
||||
"update-version-not-match": {
|
||||
fieldSpec: `
|
||||
path: a/b
|
||||
group: foo
|
||||
version: v1beta1
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
input: `
|
||||
apiVersion: foo/v1beta2
|
||||
kind: Bar
|
||||
a:
|
||||
b: c
|
||||
`,
|
||||
expected: `
|
||||
expected: `
|
||||
apiVersion: foo/v1beta2
|
||||
kind: Bar
|
||||
a:
|
||||
b: c
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "bad-version",
|
||||
fieldSpec: `
|
||||
"bad-version": {
|
||||
fieldSpec: `
|
||||
path: a/b
|
||||
group: foo
|
||||
version: v1beta1
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
input: `
|
||||
apiVersion: foo/v1beta2/something
|
||||
kind: Bar
|
||||
a:
|
||||
b: c
|
||||
`,
|
||||
expected: `
|
||||
expected: `
|
||||
apiVersion: foo/v1beta2/something
|
||||
kind: Bar
|
||||
a:
|
||||
b: c
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "bad-meta",
|
||||
fieldSpec: `
|
||||
"bad-meta": {
|
||||
fieldSpec: `
|
||||
path: a/b
|
||||
group: foo
|
||||
version: v1beta1
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
input: `
|
||||
a:
|
||||
b: c
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
expected: `
|
||||
a:
|
||||
b: c
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
},
|
||||
},
|
||||
error: "missing Resource metadata",
|
||||
},
|
||||
|
||||
{
|
||||
name: "miss-match-type",
|
||||
fieldSpec: `
|
||||
"miss-match-type": {
|
||||
fieldSpec: `
|
||||
path: a/b/c
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
input: `
|
||||
kind: Bar
|
||||
a:
|
||||
b: a
|
||||
`,
|
||||
error: "obj 'kind: Bar\na:\n b: a\n' at path 'a/b/c': " +
|
||||
"expected sequence or mapping node",
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
error: `considering field 'a/b/c' of object
|
||||
kind: Bar
|
||||
a:
|
||||
b: a
|
||||
: expected sequence or mapping node`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "add",
|
||||
fieldSpec: `
|
||||
"add": {
|
||||
fieldSpec: `
|
||||
path: a/b/c/d
|
||||
group: foo
|
||||
create: true
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
input: `
|
||||
apiVersion: foo/v1beta1
|
||||
kind: Bar
|
||||
a: {}
|
||||
`,
|
||||
expected: `
|
||||
expected: `
|
||||
apiVersion: foo/v1beta1
|
||||
kind: Bar
|
||||
a: {b: {c: {d: e}}}
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "update-in-sequence",
|
||||
fieldSpec: `
|
||||
"update-in-sequence": {
|
||||
fieldSpec: `
|
||||
path: a/b[]/c/d
|
||||
group: foo
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
input: `
|
||||
apiVersion: foo/v1beta1
|
||||
kind: Bar
|
||||
a:
|
||||
@@ -222,7 +263,7 @@ a:
|
||||
- c:
|
||||
d: a
|
||||
`,
|
||||
expected: `
|
||||
expected: `
|
||||
apiVersion: foo/v1beta1
|
||||
kind: Bar
|
||||
a:
|
||||
@@ -230,244 +271,237 @@ a:
|
||||
- c:
|
||||
d: e
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Don't create a sequence
|
||||
{
|
||||
name: "empty-sequence-no-create",
|
||||
fieldSpec: `
|
||||
// Don't create a sequence
|
||||
"empty-sequence-no-create": {
|
||||
fieldSpec: `
|
||||
path: a/b[]/c/d
|
||||
group: foo
|
||||
create: true
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
input: `
|
||||
apiVersion: foo/v1beta1
|
||||
kind: Bar
|
||||
a: {}
|
||||
`,
|
||||
expected: `
|
||||
expected: `
|
||||
apiVersion: foo/v1beta1
|
||||
kind: Bar
|
||||
a: {}
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Create a new field for an element in a sequence
|
||||
{
|
||||
name: "empty-sequence-create",
|
||||
fieldSpec: `
|
||||
// Create a new field for an element in a sequence
|
||||
"empty-sequence-create": {
|
||||
fieldSpec: `
|
||||
path: a/b[]/c/d
|
||||
group: foo
|
||||
create: true
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
input: `
|
||||
apiVersion: foo/v1beta1
|
||||
kind: Bar
|
||||
a:
|
||||
b:
|
||||
- c: {}
|
||||
`,
|
||||
expected: `
|
||||
expected: `
|
||||
apiVersion: foo/v1beta1
|
||||
kind: Bar
|
||||
a:
|
||||
b:
|
||||
- c: {d: e}
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "group v1",
|
||||
fieldSpec: `
|
||||
"group v1": {
|
||||
fieldSpec: `
|
||||
path: a/b
|
||||
group: v1
|
||||
create: true
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
input: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
`,
|
||||
expected: `
|
||||
expected: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "version v1",
|
||||
fieldSpec: `
|
||||
"version v1": {
|
||||
fieldSpec: `
|
||||
path: a/b
|
||||
version: v1
|
||||
create: true
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
input: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
`,
|
||||
expected: `
|
||||
expected: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
a:
|
||||
b: e
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "successfully set field on array entry no sequence hint",
|
||||
fieldSpec: `
|
||||
|
||||
"successfully set field on array entry no sequence hint": {
|
||||
fieldSpec: `
|
||||
path: spec/containers/image
|
||||
version: v1
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
input: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
spec:
|
||||
containers:
|
||||
- image: foo
|
||||
`,
|
||||
expected: `
|
||||
expected: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
spec:
|
||||
containers:
|
||||
- image: bar
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("bar"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("bar"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "successfully set field on array entry with sequence hint",
|
||||
fieldSpec: `
|
||||
|
||||
"successfully set field on array entry with sequence hint": {
|
||||
fieldSpec: `
|
||||
path: spec/containers[]/image
|
||||
version: v1
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
input: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
spec:
|
||||
containers:
|
||||
- image: foo
|
||||
`,
|
||||
expected: `
|
||||
expected: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
spec:
|
||||
containers:
|
||||
- image: bar
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("bar"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("bar"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "failure to set field on array entry with sequence hint in path",
|
||||
fieldSpec: `
|
||||
"failure to set field on array entry with sequence hint in path": {
|
||||
fieldSpec: `
|
||||
path: spec/containers[]/image
|
||||
version: v1
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
input: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
spec:
|
||||
containers:
|
||||
`,
|
||||
expected: `
|
||||
expected: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
spec:
|
||||
containers: []
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("bar"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("bar"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "failure to set field on array entry, no sequence hint in path",
|
||||
fieldSpec: `
|
||||
|
||||
"failure to set field on array entry, no sequence hint in path": {
|
||||
fieldSpec: `
|
||||
path: spec/containers/image
|
||||
version: v1
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
input: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
spec:
|
||||
containers:
|
||||
`,
|
||||
expected: `
|
||||
expected: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
spec:
|
||||
containers:
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("bar"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("bar"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "filedname with slash '/'",
|
||||
fieldSpec: `
|
||||
"fieldname with slash '/'": {
|
||||
fieldSpec: `
|
||||
path: a/b\/c/d
|
||||
version: v1
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
input: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
a:
|
||||
b/c:
|
||||
d: foo
|
||||
`,
|
||||
expected: `
|
||||
expected: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
a:
|
||||
b/c:
|
||||
d: bar
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("bar"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("bar"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "filedname with multiple '/'",
|
||||
fieldSpec: `
|
||||
"fieldname with multiple '/'": {
|
||||
fieldSpec: `
|
||||
path: a/b\/c/d\/e/f
|
||||
version: v1
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
input: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
a:
|
||||
@@ -475,7 +509,7 @@ a:
|
||||
d/e:
|
||||
f: foo
|
||||
`,
|
||||
expected: `
|
||||
expected: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
a:
|
||||
@@ -483,25 +517,24 @@ a:
|
||||
d/e:
|
||||
f: bar
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("bar"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("bar"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilter_Filter(t *testing.T) {
|
||||
for i := range tests {
|
||||
test := tests[i]
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
err := yaml.Unmarshal([]byte(test.fieldSpec), &test.filter.FieldSpec)
|
||||
for n := range testCases {
|
||||
tc := testCases[n]
|
||||
t.Run(n, func(t *testing.T) {
|
||||
err := yaml.Unmarshal([]byte(tc.fieldSpec), &tc.filter.FieldSpec)
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
out := &bytes.Buffer{}
|
||||
rw := &kio.ByteReadWriter{
|
||||
Reader: bytes.NewBufferString(test.input),
|
||||
Reader: bytes.NewBufferString(tc.input),
|
||||
Writer: out,
|
||||
OmitReaderAnnotations: true,
|
||||
}
|
||||
@@ -509,11 +542,11 @@ func TestFilter_Filter(t *testing.T) {
|
||||
// run the filter
|
||||
err = kio.Pipeline{
|
||||
Inputs: []kio.Reader{rw},
|
||||
Filters: []kio.Filter{kio.FilterAll(test.filter)},
|
||||
Filters: []kio.Filter{kio.FilterAll(tc.filter)},
|
||||
Outputs: []kio.Writer{rw},
|
||||
}.Execute()
|
||||
if test.error != "" {
|
||||
if !assert.EqualError(t, err, test.error) {
|
||||
if tc.error != "" {
|
||||
if !assert.EqualError(t, err, tc.error) {
|
||||
t.FailNow()
|
||||
}
|
||||
// stop rest of test
|
||||
@@ -526,7 +559,7 @@ func TestFilter_Filter(t *testing.T) {
|
||||
|
||||
// check results
|
||||
if !assert.Equal(t,
|
||||
strings.TrimSpace(test.expected),
|
||||
strings.TrimSpace(tc.expected),
|
||||
strings.TrimSpace(out.String())) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
package fieldspec
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
// Return true for 'v' followed by a 1 or 2, and don't look at rest.
|
||||
// I.e. 'v1', 'v1beta1', 'v2', would return true.
|
||||
func looksLikeACoreApiVersion(s string) bool {
|
||||
if len(s) < 2 {
|
||||
return false
|
||||
}
|
||||
if s[0:1] != "v" {
|
||||
return false
|
||||
}
|
||||
return s[1:2] == "1" || s[1:2] == "2"
|
||||
}
|
||||
|
||||
// parseGV parses apiVersion field into group and version.
|
||||
func parseGV(apiVersion string) (group, version string) {
|
||||
// parse the group and version from the apiVersion field
|
||||
parts := strings.SplitN(apiVersion, "/", 2)
|
||||
group = parts[0]
|
||||
if len(parts) > 1 {
|
||||
version = parts[1]
|
||||
}
|
||||
// Special case the original "apiVersion" of what
|
||||
// we now call the "core" (empty) group.
|
||||
if version == "" && looksLikeACoreApiVersion(group) {
|
||||
version = group
|
||||
group = ""
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetGVK parses the metadata into a GVK
|
||||
func GetGVK(meta yaml.ResourceMeta) resid.Gvk {
|
||||
group, version := parseGV(meta.APIVersion)
|
||||
return resid.Gvk{
|
||||
Group: group,
|
||||
Version: version,
|
||||
Kind: meta.Kind,
|
||||
}
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
package fieldspec
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
func TestParseGV(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
input string
|
||||
expectedGroup string
|
||||
expectedVersion string
|
||||
}{
|
||||
"empty": {
|
||||
input: "",
|
||||
expectedGroup: "",
|
||||
expectedVersion: "",
|
||||
},
|
||||
"certSigning": {
|
||||
input: "certificates.k8s.io/v1beta1",
|
||||
expectedGroup: "certificates.k8s.io",
|
||||
expectedVersion: "v1beta1",
|
||||
},
|
||||
"extensions": {
|
||||
input: "extensions/v1beta1",
|
||||
expectedGroup: "extensions",
|
||||
expectedVersion: "v1beta1",
|
||||
},
|
||||
"normal": {
|
||||
input: "apps/v1",
|
||||
expectedGroup: "apps",
|
||||
expectedVersion: "v1",
|
||||
},
|
||||
"justApps": {
|
||||
input: "apps",
|
||||
expectedGroup: "apps",
|
||||
expectedVersion: "",
|
||||
},
|
||||
"coreV1": {
|
||||
input: "v1",
|
||||
expectedGroup: "",
|
||||
expectedVersion: "v1",
|
||||
},
|
||||
"coreV2": {
|
||||
input: "v2",
|
||||
expectedGroup: "",
|
||||
expectedVersion: "v2",
|
||||
},
|
||||
"coreV2Beta1": {
|
||||
input: "v2beta1",
|
||||
expectedGroup: "",
|
||||
expectedVersion: "v2beta1",
|
||||
},
|
||||
}
|
||||
|
||||
for tn, tc := range testCases {
|
||||
t.Run(tn, func(t *testing.T) {
|
||||
group, version := parseGV(tc.input)
|
||||
if !assert.Equal(t, tc.expectedGroup, group) {
|
||||
t.FailNow()
|
||||
}
|
||||
if !assert.Equal(t, tc.expectedVersion, version) {
|
||||
t.FailNow()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetGVK(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
input string
|
||||
expected resid.Gvk
|
||||
parseError string
|
||||
metaError string
|
||||
}{
|
||||
"empty": {
|
||||
input: `
|
||||
`,
|
||||
parseError: "EOF",
|
||||
},
|
||||
"junk": {
|
||||
input: `
|
||||
congress: effective
|
||||
`,
|
||||
metaError: "missing Resource metadata",
|
||||
},
|
||||
"normal": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
`,
|
||||
expected: resid.Gvk{Group: "apps", Version: "v1", Kind: "Deployment"},
|
||||
},
|
||||
"apiVersionOnlyWithSlash": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
`,
|
||||
expected: resid.Gvk{Group: "apps", Version: "v1", Kind: ""},
|
||||
},
|
||||
"apiVersionOnlyNoSlash1": {
|
||||
input: `
|
||||
apiVersion: apps
|
||||
`,
|
||||
expected: resid.Gvk{Group: "apps", Version: "", Kind: ""},
|
||||
},
|
||||
"apiVersionOnlyNoSlash2": {
|
||||
input: `
|
||||
apiVersion: v1
|
||||
`,
|
||||
expected: resid.Gvk{Group: "", Version: "v1", Kind: ""},
|
||||
},
|
||||
}
|
||||
|
||||
for tn, tc := range testCases {
|
||||
t.Run(tn, func(t *testing.T) {
|
||||
obj, err := yaml.Parse(tc.input)
|
||||
if len(tc.parseError) != 0 {
|
||||
if err == nil {
|
||||
t.Error("expected parse error")
|
||||
return
|
||||
}
|
||||
if !strings.Contains(err.Error(), tc.parseError) {
|
||||
t.Errorf("expected parse err '%s', got '%v'", tc.parseError, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
meta, err := obj.GetMeta()
|
||||
if len(tc.metaError) != 0 {
|
||||
if err == nil {
|
||||
t.Error("expected meta error")
|
||||
return
|
||||
}
|
||||
if !strings.Contains(err.Error(), tc.metaError) {
|
||||
t.Errorf("expected meta err '%s', got '%v'", tc.metaError, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
gvk := GetGVK(meta)
|
||||
if !assert.Equal(t, tc.expected, gvk) {
|
||||
t.FailNow()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
3
api/filters/iampolicygenerator/doc.go
Normal file
3
api/filters/iampolicygenerator/doc.go
Normal file
@@ -0,0 +1,3 @@
|
||||
// Package gkesagenerator contains a kio.Filter that that generates a
|
||||
// iampolicy-related resources for a given cloud provider
|
||||
package iampolicygenerator
|
||||
46
api/filters/iampolicygenerator/example_test.go
Normal file
46
api/filters/iampolicygenerator/example_test.go
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright 2021 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package iampolicygenerator
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
func ExampleFilter() {
|
||||
f := Filter{}
|
||||
var err = yaml.Unmarshal([]byte(`
|
||||
cloud: gke
|
||||
kubernetesService:
|
||||
namespace: k8s-namespace
|
||||
name: k8s-sa-name
|
||||
serviceAccount:
|
||||
name: gsa-name
|
||||
projectId: project-id
|
||||
`), &f)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = kio.Pipeline{
|
||||
Inputs: []kio.Reader{},
|
||||
Filters: []kio.Filter{f},
|
||||
Outputs: []kio.Writer{kio.ByteWriter{Writer: os.Stdout}},
|
||||
}.Execute()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// apiVersion: v1
|
||||
// kind: ServiceAccount
|
||||
// metadata:
|
||||
// annotations:
|
||||
// iam.gke.io/gcp-service-account: gsa-name@project-id.iam.gserviceaccount.com
|
||||
// name: k8s-sa-name
|
||||
// namespace: k8s-namespace
|
||||
}
|
||||
55
api/filters/iampolicygenerator/iampolicygenerator.go
Normal file
55
api/filters/iampolicygenerator/iampolicygenerator.go
Normal file
@@ -0,0 +1,55 @@
|
||||
// Copyright 2021 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package iampolicygenerator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
type Filter struct {
|
||||
IAMPolicyGenerator types.IAMPolicyGeneratorArgs `json:",inline,omitempty" yaml:",inline,omitempty"`
|
||||
}
|
||||
|
||||
// Filter adds a GKE service account object to nodes
|
||||
func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||
switch f.IAMPolicyGenerator.Cloud {
|
||||
case types.GKE:
|
||||
IAMPolicyResources, err := f.generateGkeIAMPolicyResources()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nodes = append(nodes, IAMPolicyResources...)
|
||||
default:
|
||||
return nil, fmt.Errorf("cloud provider %s not supported yet", f.IAMPolicyGenerator.Cloud)
|
||||
}
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
func (f Filter) generateGkeIAMPolicyResources() ([]*yaml.RNode, error) {
|
||||
var result []*yaml.RNode
|
||||
input := fmt.Sprintf(`
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
annotations:
|
||||
iam.gke.io/gcp-service-account: %s@%s.iam.gserviceaccount.com
|
||||
name: %s
|
||||
`, f.IAMPolicyGenerator.ServiceAccount.Name,
|
||||
f.IAMPolicyGenerator.ProjectId,
|
||||
f.IAMPolicyGenerator.KubernetesService.Name)
|
||||
|
||||
if f.IAMPolicyGenerator.Namespace != "" {
|
||||
input = input + fmt.Sprintf("\n namespace: %s", f.IAMPolicyGenerator.Namespace)
|
||||
}
|
||||
|
||||
sa, err := yaml.Parse(input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return append(result, sa), nil
|
||||
}
|
||||
75
api/filters/iampolicygenerator/iampolicygenerator_test.go
Normal file
75
api/filters/iampolicygenerator/iampolicygenerator_test.go
Normal file
@@ -0,0 +1,75 @@
|
||||
// Copyright 2021 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package iampolicygenerator
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
filtertest "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
)
|
||||
|
||||
func TestFilter(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
args types.IAMPolicyGeneratorArgs
|
||||
expected string
|
||||
}{
|
||||
"with namespace": {
|
||||
args: types.IAMPolicyGeneratorArgs{
|
||||
Cloud: types.GKE,
|
||||
KubernetesService: types.KubernetesService{
|
||||
Namespace: "k8s-namespace",
|
||||
Name: "k8s-sa-name",
|
||||
},
|
||||
ServiceAccount: types.ServiceAccount{
|
||||
Name: "gsa-name",
|
||||
ProjectId: "project-id",
|
||||
},
|
||||
},
|
||||
expected: `
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
annotations:
|
||||
iam.gke.io/gcp-service-account: gsa-name@project-id.iam.gserviceaccount.com
|
||||
name: k8s-sa-name
|
||||
namespace: k8s-namespace
|
||||
`,
|
||||
},
|
||||
"without namespace": {
|
||||
args: types.IAMPolicyGeneratorArgs{
|
||||
Cloud: types.GKE,
|
||||
KubernetesService: types.KubernetesService{
|
||||
Name: "k8s-sa-name",
|
||||
},
|
||||
ServiceAccount: types.ServiceAccount{
|
||||
Name: "gsa-name",
|
||||
ProjectId: "project-id",
|
||||
},
|
||||
},
|
||||
expected: `
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
annotations:
|
||||
iam.gke.io/gcp-service-account: gsa-name@project-id.iam.gserviceaccount.com
|
||||
name: k8s-sa-name
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for tn, tc := range testCases {
|
||||
t.Run(tn, func(t *testing.T) {
|
||||
f := Filter{
|
||||
IAMPolicyGenerator: tc.args,
|
||||
}
|
||||
actual := filtertest.RunFilter(t, "", f)
|
||||
if !assert.Equal(t, strings.TrimSpace(tc.expected), strings.TrimSpace(actual)) {
|
||||
t.FailNow()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
package imagetag
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/api/internal/utils"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
@@ -74,7 +75,7 @@ func (f findFieldsFilter) walk(node *yaml.RNode) error {
|
||||
return err
|
||||
}
|
||||
key := n.Key.YNode().Value
|
||||
if contains(f.fields, key) {
|
||||
if utils.StringSliceContains(f.fields, key) {
|
||||
return f.fieldCallback(n.Value)
|
||||
}
|
||||
return nil
|
||||
@@ -87,15 +88,6 @@ func (f findFieldsFilter) walk(node *yaml.RNode) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func contains(slice []string, str string) bool {
|
||||
for _, s := range slice {
|
||||
if s == str {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func checkImageTagsFn(imageTag types.Image) fieldCallback {
|
||||
return func(node *yaml.RNode) error {
|
||||
if node.YNode().Kind != yaml.SequenceNode {
|
||||
|
||||
@@ -8,9 +8,9 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
filtertest_test "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/resid"
|
||||
)
|
||||
|
||||
func TestLabels_Filter(t *testing.T) {
|
||||
|
||||
@@ -1,34 +1,40 @@
|
||||
package nameref
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"sigs.k8s.io/kustomize/api/filters/fieldspec"
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/resource"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
kyaml_filtersutil "sigs.k8s.io/kustomize/kyaml/filtersutil"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
"sigs.k8s.io/kustomize/kyaml/resid"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
// Filter updates a name references.
|
||||
type Filter struct {
|
||||
// Referrer is the object that refers to something else by a name,
|
||||
// a name that this filter seeks to update.
|
||||
// Referrer refers to another resource X by X's name.
|
||||
// E.g. A Deployment can refer to a ConfigMap.
|
||||
// The Deployment is the Referrer,
|
||||
// the ConfigMap is the ReferralTarget.
|
||||
// This filter seeks to repair the reference in Deployment, given
|
||||
// that the ConfigMap's name may have changed.
|
||||
Referrer *resource.Resource
|
||||
|
||||
// NameFieldToUpdate is the field in the Referrer that holds the
|
||||
// name requiring an update.
|
||||
NameFieldToUpdate types.FieldSpec `json:"nameFieldToUpdate,omitempty" yaml:"nameFieldToUpdate,omitempty"`
|
||||
// NameFieldToUpdate is the field in the Referrer
|
||||
// that holds the name requiring an update.
|
||||
// This is the field to write.
|
||||
NameFieldToUpdate types.FieldSpec
|
||||
|
||||
// Source of the new value for the name (in its name field).
|
||||
// ReferralTarget is the source of the new value for
|
||||
// the name, always in the 'metadata/name' field.
|
||||
// This is the field to read.
|
||||
ReferralTarget resid.Gvk
|
||||
|
||||
// Set of resources to hunt through to find the ReferralTarget.
|
||||
// Set of resources to scan to find the ReferralTarget.
|
||||
ReferralCandidates resmap.ResMap
|
||||
}
|
||||
|
||||
@@ -38,18 +44,32 @@ func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||
return kio.FilterAll(yaml.FilterFunc(f.run)).Filter(nodes)
|
||||
}
|
||||
|
||||
// The node passed in here is the same node as held in Referrer, and
|
||||
// The node passed in here is the same node as held in Referrer;
|
||||
// that's how the referrer's name field is updated.
|
||||
// However, this filter still needs the extra methods on Referrer
|
||||
// Currently, however, this filter still needs the extra methods on Referrer
|
||||
// to consult things like the resource Id, its namespace, etc.
|
||||
// TODO(3455): No filter should use the Resource api; all information
|
||||
// about names should come from annotations, with helper methods
|
||||
// on the RNode object. Resource should get stupider, RNode smarter.
|
||||
func (f Filter) run(node *yaml.RNode) (*yaml.RNode, error) {
|
||||
err := node.PipeE(fieldspec.Filter{
|
||||
if err := f.confirmNodeMatchesReferrer(node); err != nil {
|
||||
// sanity check.
|
||||
return nil, err
|
||||
}
|
||||
f.NameFieldToUpdate.Gvk = f.Referrer.GetGvk()
|
||||
if err := node.PipeE(fieldspec.Filter{
|
||||
FieldSpec: f.NameFieldToUpdate,
|
||||
SetValue: f.set,
|
||||
})
|
||||
return node, err
|
||||
}); err != nil {
|
||||
return nil, errors.Wrapf(
|
||||
err, "updating name reference in '%s' field of '%s'",
|
||||
f.NameFieldToUpdate.Path, f.Referrer.CurId().String())
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
// This function is called on the node found at FieldSpec.Path.
|
||||
// It's some node in the Referrer.
|
||||
func (f Filter) set(node *yaml.RNode) error {
|
||||
if yaml.IsMissingOrNull(node) {
|
||||
return nil
|
||||
@@ -65,100 +85,107 @@ func (f Filter) set(node *yaml.RNode) error {
|
||||
setMappingFn: f.setMapping,
|
||||
}, node)
|
||||
default:
|
||||
return fmt.Errorf(
|
||||
"node is expected to be either a string or a slice of string or a map of string")
|
||||
return fmt.Errorf("node must be a scalar, sequence or map")
|
||||
}
|
||||
}
|
||||
|
||||
// Replace name field within a map RNode and leverage the namespace field.
|
||||
// This method used when NameFieldToUpdate doesn't lead to
|
||||
// one scalar field (typically called 'name'), but rather
|
||||
// leads to a map field (called anything). In this case we
|
||||
// must complete the field path, looking for both a 'name'
|
||||
// and a 'namespace' field to help select the proper
|
||||
// ReferralTarget to read the name and namespace from.
|
||||
func (f Filter) setMapping(node *yaml.RNode) error {
|
||||
if node.YNode().Kind != yaml.MappingNode {
|
||||
return fmt.Errorf("expect a mapping node")
|
||||
}
|
||||
nameNode, err := node.Pipe(yaml.FieldMatcher{Name: "name"})
|
||||
if err != nil || nameNode == nil {
|
||||
return fmt.Errorf("cannot find field 'name' in node")
|
||||
}
|
||||
namespaceNode, err := node.Pipe(yaml.FieldMatcher{Name: "namespace"})
|
||||
if err != nil {
|
||||
return fmt.Errorf("error when find field 'namespace'")
|
||||
return errors.Wrap(err, "trying to match 'name' field")
|
||||
}
|
||||
|
||||
// name will not be updated if the namespace doesn't match
|
||||
subset := f.ReferralCandidates.Resources()
|
||||
if namespaceNode != nil {
|
||||
namespace := namespaceNode.YNode().Value
|
||||
bynamespace := f.ReferralCandidates.GroupedByOriginalNamespace()
|
||||
if _, ok := bynamespace[namespace]; !ok {
|
||||
bynamespace = f.ReferralCandidates.GroupedByCurrentNamespace()
|
||||
if _, ok := bynamespace[namespace]; !ok {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
subset = bynamespace[namespace]
|
||||
if nameNode == nil {
|
||||
// This is a _configuration_ error; the field path
|
||||
// specified in NameFieldToUpdate.Path doesn't resolve
|
||||
// to a map with a 'name' field, so we have no idea what
|
||||
// field to update with a new name.
|
||||
return fmt.Errorf("path config error; no 'name' field in node")
|
||||
}
|
||||
|
||||
oldName := nameNode.YNode().Value
|
||||
res, err := f.selectReferral(oldName, subset)
|
||||
if err != nil || res == nil {
|
||||
// Nil res means nothing to do.
|
||||
candidates, err := f.filterMapCandidatesByNamespace(node)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.recordTheReferral(res)
|
||||
if res.GetName() == oldName && res.GetNamespace() == "" {
|
||||
oldName := nameNode.YNode().Value
|
||||
referral, err := f.selectReferral(oldName, candidates)
|
||||
if err != nil || referral == nil {
|
||||
// Nil referral means nothing to do.
|
||||
return err
|
||||
}
|
||||
f.recordTheReferral(referral)
|
||||
if referral.GetName() == oldName && referral.GetNamespace() == "" {
|
||||
// The name has not changed, nothing to do.
|
||||
return nil
|
||||
}
|
||||
err = node.PipeE(yaml.FieldSetter{
|
||||
if err = node.PipeE(yaml.FieldSetter{
|
||||
Name: "name",
|
||||
StringValue: res.GetName(),
|
||||
})
|
||||
if err != nil {
|
||||
StringValue: referral.GetName(),
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if res.GetNamespace() != "" {
|
||||
// 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.
|
||||
err = node.PipeE(yaml.FieldSetter{
|
||||
Name: "namespace",
|
||||
StringValue: res.GetNamespace(),
|
||||
})
|
||||
if referral.GetNamespace() == "" {
|
||||
// Don't write an empty string into the namespace field, as
|
||||
// it should not replace the value "default". The empty
|
||||
// string is handled as a wild card here, not as an implicit
|
||||
// specification of the "default" k8s namespace.
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
return node.PipeE(yaml.FieldSetter{
|
||||
Name: "namespace",
|
||||
StringValue: referral.GetNamespace(),
|
||||
})
|
||||
}
|
||||
|
||||
func (f Filter) filterMapCandidatesByNamespace(
|
||||
node *yaml.RNode) ([]*resource.Resource, error) {
|
||||
namespaceNode, err := node.Pipe(yaml.FieldMatcher{Name: "namespace"})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "trying to match 'namespace' field")
|
||||
}
|
||||
if namespaceNode == nil {
|
||||
return f.ReferralCandidates.Resources(), nil
|
||||
}
|
||||
namespace := namespaceNode.YNode().Value
|
||||
nsMap := f.ReferralCandidates.GroupedByOriginalNamespace()
|
||||
if candidates, ok := nsMap[namespace]; ok {
|
||||
return candidates, nil
|
||||
}
|
||||
nsMap = f.ReferralCandidates.GroupedByCurrentNamespace()
|
||||
// This could be nil, or an empty list.
|
||||
return nsMap[namespace], nil
|
||||
}
|
||||
|
||||
func (f Filter) setScalar(node *yaml.RNode) error {
|
||||
res, err := f.selectReferral(
|
||||
referral, err := f.selectReferral(
|
||||
node.YNode().Value, f.ReferralCandidates.Resources())
|
||||
if err != nil || res == nil {
|
||||
// Nil res means nothing to do.
|
||||
if err != nil || referral == nil {
|
||||
// Nil referral means nothing to do.
|
||||
return err
|
||||
}
|
||||
f.recordTheReferral(res)
|
||||
if res.GetName() == node.YNode().Value {
|
||||
f.recordTheReferral(referral)
|
||||
if referral.GetName() == node.YNode().Value {
|
||||
// The name has not changed, nothing to do.
|
||||
return nil
|
||||
}
|
||||
return node.PipeE(yaml.FieldSetter{StringValue: res.GetName()})
|
||||
return node.PipeE(yaml.FieldSetter{StringValue: referral.GetName()})
|
||||
}
|
||||
|
||||
// In the resource, make a note that it is referred to by the referrer.
|
||||
func (f Filter) recordTheReferral(res *resource.Resource) {
|
||||
res.AppendRefBy(f.Referrer.CurId())
|
||||
}
|
||||
|
||||
func (f Filter) isRoleRef() bool {
|
||||
return strings.HasSuffix(f.NameFieldToUpdate.Path, "roleRef/name")
|
||||
// In the resource, make a note that it is referred to by the Referrer.
|
||||
func (f Filter) recordTheReferral(referral *resource.Resource) {
|
||||
referral.AppendRefBy(f.Referrer.CurId())
|
||||
}
|
||||
|
||||
// getRoleRefGvk returns a Gvk in the roleRef field. Return error
|
||||
// if the roleRef, roleRef/apiGroup or roleRef/kind is missing.
|
||||
func getRoleRefGvk(res json.Marshaler) (*resid.Gvk, error) {
|
||||
n, err := kyaml_filtersutil.GetRNode(res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
func getRoleRefGvk(n *resource.Resource) (*resid.Gvk, error) {
|
||||
roleRef, err := n.Pipe(yaml.Lookup("roleRef"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -188,82 +215,182 @@ func getRoleRefGvk(res json.Marshaler) (*resid.Gvk, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f Filter) filterReferralCandidates(
|
||||
matches []*resource.Resource) []*resource.Resource {
|
||||
var ret []*resource.Resource
|
||||
for _, m := range matches {
|
||||
// If target kind is not ServiceAccount, we shouldn't consider condidates which
|
||||
// doesn't have same namespace.
|
||||
if f.ReferralTarget.Kind != "ServiceAccount" &&
|
||||
m.GetNamespace() != f.Referrer.GetNamespace() {
|
||||
continue
|
||||
// sieveFunc returns true if the resource argument satisfies some criteria.
|
||||
type sieveFunc func(*resource.Resource) bool
|
||||
|
||||
// doSieve uses a function to accept or ignore resources from a list.
|
||||
// If list is nil, returns immediately.
|
||||
// It's a filter obviously, but that term is overloaded here.
|
||||
func doSieve(list []*resource.Resource, fn sieveFunc) (s []*resource.Resource) {
|
||||
for _, r := range list {
|
||||
if fn(r) {
|
||||
s = append(s, r)
|
||||
}
|
||||
if !f.Referrer.PrefixesSuffixesEquals(m) {
|
||||
continue
|
||||
}
|
||||
ret = append(ret, m)
|
||||
}
|
||||
return ret
|
||||
return
|
||||
}
|
||||
|
||||
// selectReferral picks the referral among a subset of candidates.
|
||||
// The content of the candidateSubset 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 acceptAll(r *resource.Resource) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func previousNameMatches(name string) sieveFunc {
|
||||
return func(r *resource.Resource) bool {
|
||||
for _, id := range r.PrevIds() {
|
||||
if id.Name == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func previousIdSelectedByGvk(gvk *resid.Gvk) sieveFunc {
|
||||
return func(r *resource.Resource) bool {
|
||||
for _, id := range r.PrevIds() {
|
||||
if id.IsSelected(gvk) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// If the we are updating a 'roleRef/name' field, the 'apiGroup' and 'kind'
|
||||
// fields in the same 'roleRef' map must be considered.
|
||||
// If either object is cluster-scoped, there can be a referral.
|
||||
// E.g. a RoleBinding (which exists in a namespace) can refer
|
||||
// to a ClusterRole (cluster-scoped) object.
|
||||
// https://kubernetes.io/docs/reference/access-authn-authz/rbac/#role-and-clusterrole
|
||||
// Likewise, a ClusterRole can refer to a Secret (in a namespace).
|
||||
// Objects in different namespaces generally cannot refer to other
|
||||
// with some exceptions (e.g. RoleBinding and ServiceAccount are both
|
||||
// namespaceable, but the former can refer to accounts in other namespaces).
|
||||
func (f Filter) roleRefFilter() sieveFunc {
|
||||
if !strings.HasSuffix(f.NameFieldToUpdate.Path, "roleRef/name") {
|
||||
return acceptAll
|
||||
}
|
||||
roleRefGvk, err := getRoleRefGvk(f.Referrer)
|
||||
if err != nil {
|
||||
return acceptAll
|
||||
}
|
||||
return previousIdSelectedByGvk(roleRefGvk)
|
||||
}
|
||||
|
||||
func prefixSuffixEquals(other resource.ResCtx) sieveFunc {
|
||||
return func(r *resource.Resource) bool {
|
||||
return r.PrefixesSuffixesEquals(other)
|
||||
}
|
||||
}
|
||||
|
||||
func (f Filter) sameCurrentNamespaceAsReferrer() sieveFunc {
|
||||
referrerCurId := f.Referrer.CurId()
|
||||
if referrerCurId.IsClusterScoped() {
|
||||
// If the referrer is cluster-scoped, let anything through.
|
||||
return acceptAll
|
||||
}
|
||||
return func(r *resource.Resource) bool {
|
||||
if r.CurId().IsClusterScoped() {
|
||||
// Allow cluster-scoped through.
|
||||
return true
|
||||
}
|
||||
if r.GetKind() == "ServiceAccount" {
|
||||
// Allow service accounts through, even though they
|
||||
// are in a namespace. A RoleBinding in another namespace
|
||||
// can reference them.
|
||||
return true
|
||||
}
|
||||
return referrerCurId.IsNsEquals(r.CurId())
|
||||
}
|
||||
}
|
||||
|
||||
// selectReferral picks the best referral from a list of candidates.
|
||||
func (f Filter) selectReferral(
|
||||
// The name referral that may need to be updated.
|
||||
oldName string,
|
||||
candidateSubset []*resource.Resource) (*resource.Resource, error) {
|
||||
var roleRefGvk *resid.Gvk
|
||||
if f.isRoleRef() {
|
||||
var err error
|
||||
roleRefGvk, err = getRoleRefGvk(f.Referrer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
candidates []*resource.Resource) (*resource.Resource, error) {
|
||||
candidates = doSieve(candidates, previousNameMatches(oldName))
|
||||
candidates = doSieve(candidates, previousIdSelectedByGvk(&f.ReferralTarget))
|
||||
candidates = doSieve(candidates, f.roleRefFilter())
|
||||
candidates = doSieve(candidates, f.sameCurrentNamespaceAsReferrer())
|
||||
if len(candidates) == 1 {
|
||||
return candidates[0], nil
|
||||
}
|
||||
for _, res := range candidateSubset {
|
||||
if res.GetOriginalName() != oldName {
|
||||
continue
|
||||
}
|
||||
id := res.OrgId()
|
||||
if !id.IsSelected(&f.ReferralTarget) {
|
||||
continue
|
||||
}
|
||||
// If the we are processing a roleRef, the apiGroup and Kind in the
|
||||
// roleRef are needed to be considered.
|
||||
if f.isRoleRef() && !id.IsSelected(roleRefGvk) {
|
||||
continue
|
||||
}
|
||||
matches := f.ReferralCandidates.GetMatchingResourcesByOriginalId(id.Equals)
|
||||
// If there's more than one match,
|
||||
// filter the matches by prefix and suffix
|
||||
if len(matches) > 1 {
|
||||
filteredMatches := f.filterReferralCandidates(matches)
|
||||
if len(filteredMatches) > 1 {
|
||||
return nil, fmt.Errorf(
|
||||
"multiple matches for %s:\n %v",
|
||||
id, getIds(filteredMatches))
|
||||
}
|
||||
// Check is the match the resource we are working on
|
||||
if len(filteredMatches) == 0 || res != filteredMatches[0] {
|
||||
continue
|
||||
}
|
||||
}
|
||||
// In the resource, note that it is referenced
|
||||
// by the referrer.
|
||||
res.AppendRefBy(f.Referrer.CurId())
|
||||
// Return transformed name of the object,
|
||||
// complete with prefixes, hashes, etc.
|
||||
return res, nil
|
||||
candidates = doSieve(candidates, prefixSuffixEquals(f.Referrer))
|
||||
if len(candidates) == 1 {
|
||||
return candidates[0], nil
|
||||
}
|
||||
return nil, nil
|
||||
if len(candidates) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
if allNamesAreTheSame(candidates) {
|
||||
// Just take the first one.
|
||||
return candidates[0], nil
|
||||
}
|
||||
ids := getIds(candidates)
|
||||
f.failureDetails(candidates)
|
||||
return nil, fmt.Errorf(" found multiple possible referrals: %s", ids)
|
||||
}
|
||||
|
||||
func getIds(rs []*resource.Resource) []string {
|
||||
func (f Filter) failureDetails(resources []*resource.Resource) {
|
||||
fmt.Printf(
|
||||
"\n**** Too many possible referral targets to referrer:\n%s\n",
|
||||
f.Referrer.MustYaml())
|
||||
for i, r := range resources {
|
||||
fmt.Printf(
|
||||
"--- possible referral %d:\n%s", i, r.MustYaml())
|
||||
fmt.Println("------")
|
||||
}
|
||||
}
|
||||
|
||||
func allNamesAreTheSame(resources []*resource.Resource) bool {
|
||||
name := resources[0].GetName()
|
||||
for i := 1; i < len(resources); i++ {
|
||||
if name != resources[i].GetName() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func getIds(rs []*resource.Resource) string {
|
||||
var result []string
|
||||
for _, r := range rs {
|
||||
result = append(result, r.CurId().String()+"\n")
|
||||
result = append(result, r.CurId().String())
|
||||
}
|
||||
return result
|
||||
return strings.Join(result, ", ")
|
||||
}
|
||||
|
||||
func checkEqual(k, a, b string) error {
|
||||
if a != b {
|
||||
return fmt.Errorf(
|
||||
"node-referrerOriginal '%s' mismatch '%s' != '%s'",
|
||||
k, a, b)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f Filter) confirmNodeMatchesReferrer(node *yaml.RNode) error {
|
||||
meta, err := node.GetMeta()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gvk := f.Referrer.GetGvk()
|
||||
if err = checkEqual(
|
||||
"APIVersion", meta.APIVersion, gvk.ApiVersion()); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = checkEqual(
|
||||
"Kind", meta.Kind, gvk.Kind); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = checkEqual(
|
||||
"Name", meta.Name, f.Referrer.GetName()); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = checkEqual(
|
||||
"Namespace", meta.Namespace, f.Referrer.GetNamespace()); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -6,22 +6,22 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"sigs.k8s.io/kustomize/api/provider"
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
filtertest_test "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/resid"
|
||||
)
|
||||
|
||||
func TestNamerefFilter(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
input string
|
||||
candidates string
|
||||
expected string
|
||||
filter Filter
|
||||
originalNames []string
|
||||
referrerOriginal string
|
||||
candidates string
|
||||
referrerFinal string
|
||||
filter Filter
|
||||
originalNames []string
|
||||
}{
|
||||
"simple scalar": {
|
||||
input: `
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
@@ -40,8 +40,8 @@ kind: NotSecret
|
||||
metadata:
|
||||
name: newName2
|
||||
`,
|
||||
originalNames: []string{"oldName", ""},
|
||||
expected: `
|
||||
originalNames: []string{"oldName", "newName2"},
|
||||
referrerFinal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
@@ -59,7 +59,7 @@ ref:
|
||||
},
|
||||
},
|
||||
"sequence": {
|
||||
input: `
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
@@ -79,8 +79,8 @@ kind: NotSecret
|
||||
metadata:
|
||||
name: newName2
|
||||
`,
|
||||
originalNames: []string{"oldName1", ""},
|
||||
expected: `
|
||||
originalNames: []string{"oldName1", "newName2"},
|
||||
referrerFinal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
@@ -99,7 +99,7 @@ seq:
|
||||
},
|
||||
},
|
||||
"mapping": {
|
||||
input: `
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
@@ -118,8 +118,8 @@ kind: NotSecret
|
||||
metadata:
|
||||
name: newName2
|
||||
`,
|
||||
originalNames: []string{"oldName", ""},
|
||||
expected: `
|
||||
originalNames: []string{"oldName", "newName2"},
|
||||
referrerFinal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
@@ -137,36 +137,43 @@ map:
|
||||
},
|
||||
},
|
||||
"mapping with namespace": {
|
||||
input: `
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
namespace: someNs
|
||||
map:
|
||||
name: oldName
|
||||
namespace: oldNs
|
||||
namespace: someNs
|
||||
`,
|
||||
candidates: `
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName
|
||||
namespace: oldNs
|
||||
namespace: someNs
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: NotSecret
|
||||
metadata:
|
||||
name: newName2
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: thirdName
|
||||
`,
|
||||
originalNames: []string{"oldName", ""},
|
||||
expected: `
|
||||
originalNames: []string{"oldName", "oldName", "oldName"},
|
||||
referrerFinal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
namespace: someNs
|
||||
map:
|
||||
name: newName
|
||||
namespace: oldNs
|
||||
namespace: someNs
|
||||
`,
|
||||
filter: Filter{
|
||||
NameFieldToUpdate: types.FieldSpec{Path: "map"},
|
||||
@@ -178,7 +185,7 @@ map:
|
||||
},
|
||||
},
|
||||
"null value": {
|
||||
input: `
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
@@ -197,8 +204,8 @@ kind: NotSecret
|
||||
metadata:
|
||||
name: newName2
|
||||
`,
|
||||
originalNames: []string{"oldName", ""},
|
||||
expected: `
|
||||
originalNames: []string{"oldName", "newName2"},
|
||||
referrerFinal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
@@ -220,13 +227,13 @@ map:
|
||||
for tn, tc := range testCases {
|
||||
t.Run(tn, func(t *testing.T) {
|
||||
factory := provider.NewDefaultDepProvider().GetResourceFactory()
|
||||
referrer, err := factory.FromBytes([]byte(tc.input))
|
||||
referrer, err := factory.FromBytes([]byte(tc.referrerOriginal))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tc.filter.Referrer = referrer
|
||||
|
||||
resMapFactory := resmap.NewFactory(factory, nil)
|
||||
resMapFactory := resmap.NewFactory(factory)
|
||||
candidatesRes, err := factory.SliceFromBytesWithNames(
|
||||
tc.originalNames, []byte(tc.candidates))
|
||||
if err != nil {
|
||||
@@ -236,10 +243,10 @@ map:
|
||||
candidates := resMapFactory.FromResourceSlice(candidatesRes)
|
||||
tc.filter.ReferralCandidates = candidates
|
||||
|
||||
result := filtertest_test.RunFilter(t, tc.referrerOriginal, tc.filter)
|
||||
if !assert.Equal(t,
|
||||
strings.TrimSpace(tc.expected),
|
||||
strings.TrimSpace(
|
||||
filtertest_test.RunFilter(t, tc.input, tc.filter))) {
|
||||
strings.TrimSpace(tc.referrerFinal),
|
||||
strings.TrimSpace(result)) {
|
||||
t.FailNow()
|
||||
}
|
||||
})
|
||||
@@ -248,14 +255,14 @@ map:
|
||||
|
||||
func TestNamerefFilterUnhappy(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
input string
|
||||
candidates string
|
||||
expected string
|
||||
filter Filter
|
||||
originalNames []string
|
||||
referrerOriginal string
|
||||
candidates string
|
||||
referrerFinal string
|
||||
filter Filter
|
||||
originalNames []string
|
||||
}{
|
||||
"multiple match": {
|
||||
input: `
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
@@ -275,7 +282,7 @@ metadata:
|
||||
name: newName2
|
||||
`,
|
||||
originalNames: []string{"oldName", "oldName"},
|
||||
expected: "",
|
||||
referrerFinal: "",
|
||||
filter: Filter{
|
||||
NameFieldToUpdate: types.FieldSpec{Path: "ref/name"},
|
||||
ReferralTarget: resid.Gvk{
|
||||
@@ -286,7 +293,7 @@ metadata:
|
||||
},
|
||||
},
|
||||
"no name": {
|
||||
input: `
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
@@ -306,7 +313,7 @@ metadata:
|
||||
name: newName2
|
||||
`,
|
||||
originalNames: []string{"oldName", "oldName"},
|
||||
expected: "",
|
||||
referrerFinal: "",
|
||||
filter: Filter{
|
||||
NameFieldToUpdate: types.FieldSpec{Path: "ref"},
|
||||
ReferralTarget: resid.Gvk{
|
||||
@@ -321,13 +328,13 @@ metadata:
|
||||
for tn, tc := range testCases {
|
||||
t.Run(tn, func(t *testing.T) {
|
||||
factory := provider.NewDefaultDepProvider().GetResourceFactory()
|
||||
referrer, err := factory.FromBytes([]byte(tc.input))
|
||||
referrer, err := factory.FromBytes([]byte(tc.referrerOriginal))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tc.filter.Referrer = referrer
|
||||
|
||||
resMapFactory := resmap.NewFactory(factory, nil)
|
||||
resMapFactory := resmap.NewFactory(factory)
|
||||
candidatesRes, err := factory.SliceFromBytesWithNames(
|
||||
tc.originalNames, []byte(tc.candidates))
|
||||
if err != nil {
|
||||
@@ -337,11 +344,11 @@ metadata:
|
||||
candidates := resMapFactory.FromResourceSlice(candidatesRes)
|
||||
tc.filter.ReferralCandidates = candidates
|
||||
|
||||
_, err = filtertest_test.RunFilterE(t, tc.input, tc.filter)
|
||||
_, err = filtertest_test.RunFilterE(t, tc.referrerOriginal, tc.filter)
|
||||
if err == nil {
|
||||
t.Fatalf("expect an error")
|
||||
}
|
||||
if tc.expected != "" && !assert.EqualError(t, err, tc.expected) {
|
||||
if tc.referrerFinal != "" && !assert.EqualError(t, err, tc.referrerFinal) {
|
||||
t.FailNow()
|
||||
}
|
||||
})
|
||||
@@ -350,19 +357,19 @@ metadata:
|
||||
|
||||
func TestCandidatesWithDifferentPrefixSuffix(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
input string
|
||||
candidates string
|
||||
expected string
|
||||
filter Filter
|
||||
originalNames []string
|
||||
prefix []string
|
||||
suffix []string
|
||||
inputPrefix string
|
||||
inputSuffix string
|
||||
err bool
|
||||
referrerOriginal string
|
||||
candidates string
|
||||
referrerFinal string
|
||||
filter Filter
|
||||
originalNames []string
|
||||
prefix []string
|
||||
suffix []string
|
||||
inputPrefix string
|
||||
inputSuffix string
|
||||
err bool
|
||||
}{
|
||||
"prefix match": {
|
||||
input: `
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
@@ -386,7 +393,7 @@ metadata:
|
||||
suffix: []string{"", "suffix2"},
|
||||
inputPrefix: "prefix1",
|
||||
inputSuffix: "",
|
||||
expected: `
|
||||
referrerFinal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
@@ -405,7 +412,7 @@ ref:
|
||||
err: false,
|
||||
},
|
||||
"suffix match": {
|
||||
input: `
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
@@ -429,7 +436,7 @@ metadata:
|
||||
suffix: []string{"suffix1", "suffix2"},
|
||||
inputPrefix: "",
|
||||
inputSuffix: "suffix1",
|
||||
expected: `
|
||||
referrerFinal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
@@ -448,7 +455,7 @@ ref:
|
||||
err: false,
|
||||
},
|
||||
"prefix suffix both match": {
|
||||
input: `
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
@@ -472,7 +479,7 @@ metadata:
|
||||
suffix: []string{"suffix1", "suffix2"},
|
||||
inputPrefix: "prefix1",
|
||||
inputSuffix: "suffix1",
|
||||
expected: `
|
||||
referrerFinal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
@@ -491,7 +498,7 @@ ref:
|
||||
err: false,
|
||||
},
|
||||
"multiple match: both": {
|
||||
input: `
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
@@ -515,7 +522,7 @@ metadata:
|
||||
suffix: []string{"suffix", "suffix"},
|
||||
inputPrefix: "prefix",
|
||||
inputSuffix: "suffix",
|
||||
expected: "",
|
||||
referrerFinal: "",
|
||||
filter: Filter{
|
||||
NameFieldToUpdate: types.FieldSpec{Path: "ref/name"},
|
||||
ReferralTarget: resid.Gvk{
|
||||
@@ -527,7 +534,7 @@ metadata:
|
||||
err: true,
|
||||
},
|
||||
"multiple match: only prefix": {
|
||||
input: `
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
@@ -551,7 +558,7 @@ metadata:
|
||||
suffix: []string{"", ""},
|
||||
inputPrefix: "prefix",
|
||||
inputSuffix: "",
|
||||
expected: "",
|
||||
referrerFinal: "",
|
||||
filter: Filter{
|
||||
NameFieldToUpdate: types.FieldSpec{Path: "ref/name"},
|
||||
ReferralTarget: resid.Gvk{
|
||||
@@ -563,7 +570,7 @@ metadata:
|
||||
err: true,
|
||||
},
|
||||
"multiple match: only suffix": {
|
||||
input: `
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
@@ -587,7 +594,7 @@ metadata:
|
||||
suffix: []string{"suffix", "suffix"},
|
||||
inputPrefix: "",
|
||||
inputSuffix: "suffix",
|
||||
expected: "",
|
||||
referrerFinal: "",
|
||||
filter: Filter{
|
||||
NameFieldToUpdate: types.FieldSpec{Path: "ref/name"},
|
||||
ReferralTarget: resid.Gvk{
|
||||
@@ -599,7 +606,7 @@ metadata:
|
||||
err: true,
|
||||
},
|
||||
"no match: neither match": {
|
||||
input: `
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
@@ -623,7 +630,7 @@ metadata:
|
||||
suffix: []string{"suffix1", "suffix2"},
|
||||
inputPrefix: "prefix",
|
||||
inputSuffix: "suffix",
|
||||
expected: `
|
||||
referrerFinal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
@@ -642,7 +649,7 @@ ref:
|
||||
err: false,
|
||||
},
|
||||
"no match: prefix doesn't match": {
|
||||
input: `
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
@@ -666,7 +673,7 @@ metadata:
|
||||
suffix: []string{"suffix", "suffix"},
|
||||
inputPrefix: "prefix",
|
||||
inputSuffix: "suffix",
|
||||
expected: `
|
||||
referrerFinal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
@@ -685,7 +692,7 @@ ref:
|
||||
err: false,
|
||||
},
|
||||
"no match: suffix doesn't match": {
|
||||
input: `
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
@@ -709,7 +716,7 @@ metadata:
|
||||
suffix: []string{"suffix1", "suffix2"},
|
||||
inputPrefix: "prefix",
|
||||
inputSuffix: "suffix",
|
||||
expected: `
|
||||
referrerFinal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
@@ -732,7 +739,7 @@ ref:
|
||||
for tn, tc := range testCases {
|
||||
t.Run(tn, func(t *testing.T) {
|
||||
factory := provider.NewDefaultDepProvider().GetResourceFactory()
|
||||
referrer, err := factory.FromBytes([]byte(tc.input))
|
||||
referrer, err := factory.FromBytes([]byte(tc.referrerOriginal))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -744,7 +751,7 @@ ref:
|
||||
}
|
||||
tc.filter.Referrer = referrer
|
||||
|
||||
resMapFactory := resmap.NewFactory(factory, nil)
|
||||
resMapFactory := resmap.NewFactory(factory)
|
||||
candidatesRes, err := factory.SliceFromBytesWithNames(
|
||||
tc.originalNames, []byte(tc.candidates))
|
||||
if err != nil {
|
||||
@@ -764,13 +771,15 @@ ref:
|
||||
|
||||
if !tc.err {
|
||||
if !assert.Equal(t,
|
||||
strings.TrimSpace(tc.expected),
|
||||
strings.TrimSpace(tc.referrerFinal),
|
||||
strings.TrimSpace(
|
||||
filtertest_test.RunFilter(t, tc.input, tc.filter))) {
|
||||
filtertest_test.RunFilter(
|
||||
t, tc.referrerOriginal, tc.filter))) {
|
||||
t.FailNow()
|
||||
}
|
||||
} else {
|
||||
_, err := filtertest_test.RunFilterE(t, tc.input, tc.filter)
|
||||
_, err := filtertest_test.RunFilterE(
|
||||
t, tc.referrerOriginal, tc.filter)
|
||||
if err == nil {
|
||||
t.Fatalf("an error is expected")
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
package namespace
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/api/filters/fieldspec"
|
||||
"sigs.k8s.io/kustomize/api/filters/filtersutil"
|
||||
"sigs.k8s.io/kustomize/api/filters/fsslice"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
"sigs.k8s.io/kustomize/kyaml/resid"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
@@ -54,16 +54,11 @@ func (ns Filter) run(node *yaml.RNode) (*yaml.RNode, error) {
|
||||
// hacks applies the namespace transforms that are hardcoded rather
|
||||
// than specified through FieldSpecs.
|
||||
func (ns Filter) hacks(obj *yaml.RNode) error {
|
||||
meta, err := obj.GetMeta()
|
||||
if err != nil {
|
||||
gvk := resid.GvkFromNode(obj)
|
||||
if err := ns.metaNamespaceHack(obj, gvk); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ns.metaNamespaceHack(obj, meta); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ns.roleBindingHack(obj, meta)
|
||||
return ns.roleBindingHack(obj, gvk)
|
||||
}
|
||||
|
||||
// metaNamespaceHack is a hack for implementing the namespace transform
|
||||
@@ -74,9 +69,8 @@ func (ns Filter) hacks(obj *yaml.RNode) error {
|
||||
// This hack should be updated to allow individual resources to specify
|
||||
// if they are cluster scoped through either an annotation on the resources,
|
||||
// or through inlined OpenAPI on the resource as a YAML comment.
|
||||
func (ns Filter) metaNamespaceHack(obj *yaml.RNode, meta yaml.ResourceMeta) error {
|
||||
gvk := fieldspec.GetGVK(meta)
|
||||
if !gvk.IsNamespaceableKind() {
|
||||
func (ns Filter) metaNamespaceHack(obj *yaml.RNode, gvk resid.Gvk) error {
|
||||
if gvk.IsClusterScoped() {
|
||||
return nil
|
||||
}
|
||||
f := fsslice.Filter{
|
||||
@@ -104,8 +98,8 @@ func (ns Filter) metaNamespaceHack(obj *yaml.RNode, meta yaml.ResourceMeta) erro
|
||||
// ...
|
||||
// - name: "something-else" # this will not have the namespace set
|
||||
// ...
|
||||
func (ns Filter) roleBindingHack(obj *yaml.RNode, meta yaml.ResourceMeta) error {
|
||||
if meta.Kind != roleBindingKind && meta.Kind != clusterRoleBindingKind {
|
||||
func (ns Filter) roleBindingHack(obj *yaml.RNode, gvk resid.Gvk) error {
|
||||
if gvk.Kind != roleBindingKind && gvk.Kind != clusterRoleBindingKind {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -118,7 +112,6 @@ func (ns Filter) roleBindingHack(obj *yaml.RNode, meta yaml.ResourceMeta) error
|
||||
|
||||
// add the namespace to each "subject" with name: default
|
||||
err = obj.VisitElements(func(o *yaml.RNode) error {
|
||||
// copied from kunstruct based kustomize NamespaceTransformer plugin
|
||||
// The only case we need to force the namespace
|
||||
// if for the "service account". "default" is
|
||||
// kind of hardcoded here for right now.
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
package patchstrategicmerge
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/api/konfig"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml/merge2"
|
||||
@@ -29,7 +28,7 @@ func (pf Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !konfig.FlagEnableKyamlDefaultValue || r != nil {
|
||||
if r != nil {
|
||||
result = append(result, r)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -672,6 +672,63 @@ spec:
|
||||
ports:
|
||||
- containerPort: 80
|
||||
- containerPort: 8080
|
||||
`,
|
||||
},
|
||||
|
||||
// Test for issue #3513
|
||||
// Currently broken; when one port has only containerPort, the output
|
||||
// should not merge containerPort 8301 together
|
||||
// This occurs because when protocol is missing on the first port,
|
||||
// the merge code uses [containerPort] as the merge key rather than
|
||||
// [containerPort, protocol]
|
||||
"list map keys - protocol only present on some ports": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: test-deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: consul
|
||||
image: "dashicorp/consul:1.9.1"
|
||||
ports:
|
||||
- containerPort: 8500
|
||||
name: http
|
||||
- containerPort: 8301
|
||||
protocol: "TCP"
|
||||
- containerPort: 8301
|
||||
protocol: "UDP"
|
||||
`,
|
||||
patch: yaml.MustParse(`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: test-deployment
|
||||
labels:
|
||||
test: label
|
||||
`),
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: test-deployment
|
||||
labels:
|
||||
test: label
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: consul
|
||||
image: "dashicorp/consul:1.9.1"
|
||||
ports:
|
||||
- containerPort: 8500
|
||||
name: http
|
||||
- containerPort: 8301
|
||||
protocol: "TCP"
|
||||
- containerPort: 8301
|
||||
protocol: "UDP"
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -243,16 +243,18 @@ metadata:
|
||||
data:
|
||||
slice:
|
||||
- false`,
|
||||
expectedError: `obj 'apiVersion: apps/v1
|
||||
expectedError: `considering field 'data/slice' of object
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
annotations:
|
||||
config.kubernetes.io/index: '0'
|
||||
internal.config.kubernetes.io/index: '0'
|
||||
data:
|
||||
slice:
|
||||
- false
|
||||
' at path 'data/slice': invalid value type expect a string`,
|
||||
: invalid value type expect a string`,
|
||||
filter: Filter{
|
||||
MappingFunc: makeMf(map[string]interface{}{
|
||||
"VAR": int64(5),
|
||||
@@ -268,15 +270,17 @@ metadata:
|
||||
name: dep
|
||||
data:
|
||||
1: str`,
|
||||
expectedError: `obj 'apiVersion: apps/v1
|
||||
expectedError: `considering field 'data' of object
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
annotations:
|
||||
config.kubernetes.io/index: '0'
|
||||
internal.config.kubernetes.io/index: '0'
|
||||
data:
|
||||
1: str
|
||||
' at path 'data': invalid map key: value='1', tag='` + yaml.NodeTagInt + `'`,
|
||||
: invalid map key: value='1', tag='` + yaml.NodeTagInt + `'`,
|
||||
filter: Filter{
|
||||
MappingFunc: makeMf(map[string]interface{}{
|
||||
"VAR": int64(5),
|
||||
|
||||
4
api/filters/replacement/doc.go
Normal file
4
api/filters/replacement/doc.go
Normal file
@@ -0,0 +1,4 @@
|
||||
// Package replacement contains a kio.Filter implementation of the kustomize
|
||||
// replacement transformer (accepts sources and looks for targets to replace
|
||||
// their values with values from the sources).
|
||||
package replacement
|
||||
68
api/filters/replacement/example_test.go
Normal file
68
api/filters/replacement/example_test.go
Normal file
@@ -0,0 +1,68 @@
|
||||
// Copyright 2021 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package replacement
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
func ExampleFilter() {
|
||||
f := Filter{}
|
||||
err := yaml.Unmarshal([]byte(`
|
||||
replacements:
|
||||
- source:
|
||||
kind: Foo2
|
||||
fieldPath: spec.replicas
|
||||
targets:
|
||||
- select:
|
||||
kind: Foo1
|
||||
fieldPaths:
|
||||
- spec.replicas`), &f)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = kio.Pipeline{
|
||||
Inputs: []kio.Reader{&kio.ByteReader{Reader: bytes.NewBufferString(`
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo1
|
||||
metadata:
|
||||
name: instance
|
||||
spec:
|
||||
replicas: 3
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo2
|
||||
metadata:
|
||||
name: instance
|
||||
spec:
|
||||
replicas: 99
|
||||
`)}},
|
||||
Filters: []kio.Filter{f},
|
||||
Outputs: []kio.Writer{kio.ByteWriter{Writer: os.Stdout}},
|
||||
}.Execute()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// apiVersion: example.com/v1
|
||||
// kind: Foo1
|
||||
// metadata:
|
||||
// name: instance
|
||||
// spec:
|
||||
// replicas: 99
|
||||
// ---
|
||||
// apiVersion: example.com/v1
|
||||
// kind: Foo2
|
||||
// metadata:
|
||||
// name: instance
|
||||
// spec:
|
||||
// replicas: 99
|
||||
}
|
||||
221
api/filters/replacement/replacement.go
Normal file
221
api/filters/replacement/replacement.go
Normal file
@@ -0,0 +1,221 @@
|
||||
// Copyright 2021 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package replacement
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/internal/utils"
|
||||
"sigs.k8s.io/kustomize/api/resource"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/resid"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
type Filter struct {
|
||||
Replacements []types.Replacement `json:"replacements,omitempty" yaml:"replacements,omitempty"`
|
||||
}
|
||||
|
||||
// Filter replaces values of targets with values from sources
|
||||
func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||
for _, r := range f.Replacements {
|
||||
if r.Source == nil || r.Targets == nil {
|
||||
return nil, fmt.Errorf("replacements must specify a source and at least one target")
|
||||
}
|
||||
value, err := getReplacement(nodes, &r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nodes, err = applyReplacement(nodes, value, r.Targets)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
func applyReplacement(nodes []*yaml.RNode, value *yaml.RNode, targets []*types.TargetSelector) ([]*yaml.RNode, error) {
|
||||
for _, t := range targets {
|
||||
if t.Select == nil {
|
||||
return nil, fmt.Errorf("target must specify resources to select")
|
||||
}
|
||||
if len(t.FieldPaths) == 0 {
|
||||
t.FieldPaths = []string{types.DefaultReplacementFieldPath}
|
||||
}
|
||||
for _, n := range nodes {
|
||||
ids, err := utils.MakeResIds(n)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// filter targets by label and annotation selectors
|
||||
selectByAnnoAndLabel, err := selectByAnnoAndLabel(n, t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !selectByAnnoAndLabel {
|
||||
continue
|
||||
}
|
||||
|
||||
// filter targets by matching resource IDs
|
||||
for _, id := range ids {
|
||||
if id.IsSelectedBy(t.Select.ResId) && !rejectId(t.Reject, &id) {
|
||||
err := applyToNode(n, value, t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
func selectByAnnoAndLabel(n *yaml.RNode, t *types.TargetSelector) (bool, error) {
|
||||
if matchesSelect, err := matchesAnnoAndLabelSelector(n, t.Select); !matchesSelect || err != nil {
|
||||
return false, err
|
||||
}
|
||||
for _, reject := range t.Reject {
|
||||
if reject.AnnotationSelector == "" && reject.LabelSelector == "" {
|
||||
continue
|
||||
}
|
||||
if m, err := matchesAnnoAndLabelSelector(n, reject); m || err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func matchesAnnoAndLabelSelector(n *yaml.RNode, selector *types.Selector) (bool, error) {
|
||||
r := resource.Resource{RNode: *n}
|
||||
annoMatch, err := r.MatchesAnnotationSelector(selector.AnnotationSelector)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
labelMatch, err := r.MatchesLabelSelector(selector.LabelSelector)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return annoMatch && labelMatch, nil
|
||||
}
|
||||
|
||||
func rejectId(rejects []*types.Selector, id *resid.ResId) bool {
|
||||
for _, r := range rejects {
|
||||
if !r.ResId.IsEmpty() && id.IsSelectedBy(r.ResId) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func applyToNode(node *yaml.RNode, value *yaml.RNode, target *types.TargetSelector) error {
|
||||
for _, fp := range target.FieldPaths {
|
||||
fieldPath := utils.SmarterPathSplitter(fp, ".")
|
||||
var t *yaml.RNode
|
||||
var err error
|
||||
if target.Options != nil && target.Options.Create {
|
||||
t, err = node.Pipe(yaml.LookupCreate(value.YNode().Kind, fieldPath...))
|
||||
} else {
|
||||
t, err = node.Pipe(yaml.Lookup(fieldPath...))
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if t != nil {
|
||||
if err = setTargetValue(target.Options, t, value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setTargetValue(options *types.FieldOptions, t *yaml.RNode, value *yaml.RNode) error {
|
||||
value = value.Copy()
|
||||
if options != nil && options.Delimiter != "" {
|
||||
if t.YNode().Kind != yaml.ScalarNode {
|
||||
return fmt.Errorf("delimiter option can only be used with scalar nodes")
|
||||
}
|
||||
tv := strings.Split(t.YNode().Value, options.Delimiter)
|
||||
v := yaml.GetValue(value)
|
||||
// TODO: Add a way to remove an element
|
||||
switch {
|
||||
case options.Index < 0: // prefix
|
||||
tv = append([]string{v}, tv...)
|
||||
case options.Index >= len(tv): // suffix
|
||||
tv = append(tv, v)
|
||||
default: // replace an element
|
||||
tv[options.Index] = v
|
||||
}
|
||||
value.YNode().Value = strings.Join(tv, options.Delimiter)
|
||||
}
|
||||
t.SetYNode(value.YNode())
|
||||
return nil
|
||||
}
|
||||
|
||||
func getReplacement(nodes []*yaml.RNode, r *types.Replacement) (*yaml.RNode, error) {
|
||||
source, err := selectSourceNode(nodes, r.Source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if r.Source.FieldPath == "" {
|
||||
r.Source.FieldPath = types.DefaultReplacementFieldPath
|
||||
}
|
||||
fieldPath := utils.SmarterPathSplitter(r.Source.FieldPath, ".")
|
||||
|
||||
rn, err := source.Pipe(yaml.Lookup(fieldPath...))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rn.IsNilOrEmpty() {
|
||||
return nil, fmt.Errorf("fieldPath `%s` is missing for replacement source %s", r.Source.FieldPath, r.Source)
|
||||
}
|
||||
|
||||
return getRefinedValue(r.Source.Options, rn)
|
||||
}
|
||||
|
||||
func getRefinedValue(options *types.FieldOptions, rn *yaml.RNode) (*yaml.RNode, error) {
|
||||
if options == nil || options.Delimiter == "" {
|
||||
return rn, nil
|
||||
}
|
||||
if rn.YNode().Kind != yaml.ScalarNode {
|
||||
return nil, fmt.Errorf("delimiter option can only be used with scalar nodes")
|
||||
}
|
||||
value := strings.Split(yaml.GetValue(rn), options.Delimiter)
|
||||
if options.Index >= len(value) || options.Index < 0 {
|
||||
return nil, fmt.Errorf("options.index %d is out of bounds for value %s", options.Index, yaml.GetValue(rn))
|
||||
}
|
||||
n := rn.Copy()
|
||||
n.YNode().Value = value[options.Index]
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// selectSourceNode finds the node that matches the selector, returning
|
||||
// an error if multiple or none are found
|
||||
func selectSourceNode(nodes []*yaml.RNode, selector *types.SourceSelector) (*yaml.RNode, error) {
|
||||
var matches []*yaml.RNode
|
||||
for _, n := range nodes {
|
||||
ids, err := utils.MakeResIds(n)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, id := range ids {
|
||||
if id.IsSelectedBy(selector.ResId) {
|
||||
if len(matches) > 0 {
|
||||
return nil, fmt.Errorf(
|
||||
"multiple matches for selector %s", selector)
|
||||
}
|
||||
matches = append(matches, n)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(matches) == 0 {
|
||||
return nil, fmt.Errorf("nothing selected by %s", selector)
|
||||
}
|
||||
return matches[0], nil
|
||||
}
|
||||
1839
api/filters/replacement/replacement_test.go
Normal file
1839
api/filters/replacement/replacement_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ package valueadd
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/filesys"
|
||||
"sigs.k8s.io/kustomize/kyaml/filesys"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
@@ -98,7 +98,17 @@ var _ kio.Filter = Filter{}
|
||||
func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||
_, err := kio.FilterAll(yaml.FilterFunc(
|
||||
func(node *yaml.RNode) (*yaml.RNode, error) {
|
||||
fields := strings.Split(f.FieldPath, "/")
|
||||
var fields []string
|
||||
// if there is forward slash '/' in the field name, a back slash '\'
|
||||
// will be used to escape it.
|
||||
for _, f := range strings.Split(f.FieldPath, "/") {
|
||||
if len(fields) > 0 && strings.HasSuffix(fields[len(fields)-1], "\\") {
|
||||
concatField := strings.TrimSuffix(fields[len(fields)-1], "\\") + "/" + f
|
||||
fields = append(fields[:len(fields)-1], concatField)
|
||||
} else {
|
||||
fields = append(fields, f)
|
||||
}
|
||||
}
|
||||
// TODO: support SequenceNode.
|
||||
// Presumably here one could look for array indices (digits) at
|
||||
// the end of the field path (as described in IETF RFC 6902 JSON),
|
||||
|
||||
@@ -94,6 +94,20 @@ spec:
|
||||
FilePathPosition: 2,
|
||||
},
|
||||
},
|
||||
"backSlash": {
|
||||
input: `
|
||||
kind: SomeKind
|
||||
`,
|
||||
expectedOutput: `
|
||||
kind: SomeKind
|
||||
spec:
|
||||
resourceRef/external: valueAdded
|
||||
`,
|
||||
filter: Filter{
|
||||
Value: "valueAdded",
|
||||
FieldPath: "spec/resourceRef\\/external",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for tn, tc := range testCases {
|
||||
|
||||
26
api/go.mod
26
api/go.mod
@@ -1,28 +1,16 @@
|
||||
module sigs.k8s.io/kustomize/api
|
||||
|
||||
go 1.15
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/evanphx/json-patch v4.5.0+incompatible
|
||||
github.com/evanphx/json-patch v4.11.0+incompatible
|
||||
github.com/go-errors/errors v1.0.1
|
||||
github.com/go-openapi/spec v0.19.5
|
||||
github.com/golangci/golangci-lint v1.21.0
|
||||
github.com/google/go-cmp v0.3.0
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||
github.com/hashicorp/go-multierror v1.1.0
|
||||
github.com/imdario/mergo v0.3.5
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/stretchr/testify v1.4.0
|
||||
github.com/yujunz/go-getter v1.5.1-lite.0.20201201013212-6d9c071adddf
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e
|
||||
gopkg.in/yaml.v2 v2.3.0
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
|
||||
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/kustomize/kyaml v0.10.6
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/stretchr/testify v1.5.1
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e
|
||||
sigs.k8s.io/kustomize/kyaml v0.13.0
|
||||
sigs.k8s.io/yaml v1.2.0
|
||||
)
|
||||
|
||||
replace sigs.k8s.io/kustomize/kyaml => ../kyaml
|
||||
|
||||
459
api/go.sum
459
api/go.sum
@@ -1,43 +1,17 @@
|
||||
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/360EntSecGroup-Skylar/excelize v1.4.1/go.mod h1:vnax29X2usfl7HHkBrX5EvSCJcmH3dT9luvxzu8iGAE=
|
||||
github.com/Azure/go-autorest/autorest v0.9.0/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/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
|
||||
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/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/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg=
|
||||
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/purell v1.1.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/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
|
||||
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/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
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/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas=
|
||||
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4=
|
||||
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/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
@@ -45,302 +19,100 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
|
||||
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/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
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/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
|
||||
github.com/dustmop/soup v1.1.2-0.20190516214245-38228baa104e/go.mod h1:CgNC6SGbT+Xb8wGGvzilttZL1mc5sQ/5KkcxsZttMIk=
|
||||
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633 h1:H2pdYOb3KQ1/YsqVWoWNLQO+fusocsw354rqGTZtAgw=
|
||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/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/evanphx/json-patch v4.11.0+incompatible h1:glyUF9yIYtMHzn8xaKw5rMhdWcwsYV8dZHIq5567/xs=
|
||||
github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
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/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||
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-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
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/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
|
||||
github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
|
||||
github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
|
||||
github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk=
|
||||
github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU=
|
||||
github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
|
||||
github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
|
||||
github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94=
|
||||
github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
|
||||
github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
|
||||
github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
|
||||
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.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
|
||||
github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
|
||||
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
|
||||
github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o=
|
||||
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
|
||||
github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
|
||||
github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
|
||||
github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
|
||||
github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs=
|
||||
github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk=
|
||||
github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA=
|
||||
github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64=
|
||||
github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4=
|
||||
github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
|
||||
github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
|
||||
github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
|
||||
github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY=
|
||||
github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
|
||||
github.com/go-openapi/spec v0.19.5 h1:Xm0Ao53uqnk9QE/LlYV5DEU09UAgpliA85QoT9LzqPw=
|
||||
github.com/go-openapi/spec v0.19.5/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk=
|
||||
github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
|
||||
github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
|
||||
github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY=
|
||||
github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU=
|
||||
github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk=
|
||||
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
|
||||
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
|
||||
github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
|
||||
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-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
|
||||
github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA=
|
||||
github.com/go-openapi/validate v0.19.8/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4=
|
||||
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/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM=
|
||||
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/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/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-20190930125516-244bba706f1a h1:iR3fYXUjHCR97qWS8ch1y9zPNsgXThGwjKPrYfqMPks=
|
||||
github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU=
|
||||
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-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/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
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 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
||||
github.com/google/go-cmp v0.3.0/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/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
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.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
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/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
|
||||
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
||||
github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU=
|
||||
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/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/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.0 h1:wvCrVc9TjDls6+YGAF2hAifE1E5U1+b4tH6KdvN3Gig=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
|
||||
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
|
||||
github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo=
|
||||
github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I=
|
||||
github.com/hashicorp/go-version v1.1.0 h1:bPIoEKD27tNdebFGGxxYwcL4nepeY4j1QP23PFRGzg0=
|
||||
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
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 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q=
|
||||
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/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
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/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
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-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/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/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM=
|
||||
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
|
||||
github.com/markbates/pkger v0.17.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI=
|
||||
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.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.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
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/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
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/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0=
|
||||
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4=
|
||||
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/paulmach/orb v0.1.3/go.mod h1:VFlX/8C+IQ1p6FTRRKzKoOPJnvEtA5G0Veuqwbu//Vk=
|
||||
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||
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/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
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=
|
||||
@@ -352,256 +124,107 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8
|
||||
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/qri-io/starlib v0.4.2-0.20200213133954-ff2e8cd5ef8d h1:K6eOUihrFLdZjZnA4XlRp864fmWXv9YTIk7VPLhRacA=
|
||||
github.com/qri-io/starlib v0.4.2-0.20200213133954-ff2e8cd5ef8d/go.mod h1:7DPO4domFU579Ga6E61sB9VFNaniPVwJP5C4bBCu3wA=
|
||||
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-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 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
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/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
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/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8=
|
||||
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
|
||||
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/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
|
||||
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.2.3-0.20181224173747-660f15d67dbb/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/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
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/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
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/ulikunitz/xz v0.5.8 h1:ERv8V6GKqVi23rgu5cj9pVfVzJbOqAY2Ntl88O6c2nQ=
|
||||
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
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.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/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca h1:1CFlNzQhALwjS9mBAUkycX616GzgsuYUOCHA5+HSlXI=
|
||||
github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
github.com/yujunz/go-getter v1.5.1-lite.0.20201201013212-6d9c071adddf h1:gvEmqF83GB8R5XtrMseJb6A6R0OCtNAS8f4TmZg2dGc=
|
||||
github.com/yujunz/go-getter v1.5.1-lite.0.20201201013212-6d9c071adddf/go.mod h1:bL0Pr07HEdsMZ1WBqZIxXj96r5LnFsY4LgPaPEGkw1k=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
|
||||
go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
|
||||
go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.starlark.net v0.0.0-20190528202925-30ae18b8564f/go.mod h1:c1/X6cHgvdXj6pUlmWKMkuqRnW4K8x2vwt6JAaaircg=
|
||||
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 h1:+FNtrFTmVw0YZGpBGX56XDee331t6JAXeK2bcyhLOOc=
|
||||
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o=
|
||||
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-20190320223903-b7391e95e576/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-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392 h1:ACG4HJsFiNMf47Y4PeRoebLNy/2lXT9EtprMuTFWt1M=
|
||||
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-20180218175443-cbe0f9307d01/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-20181005035420-146acd28ed58/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-20190320064053-1272bf9dcd53/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-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/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
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 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
|
||||
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-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
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-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c h1:Vco5b+cuG5NNfORVxZy6bYZQ7rsigisU1WQFkvQ0L5E=
|
||||
golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
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 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/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-20190125232054-d66bd3c5d5a6/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-20190617190820-da514acc4774/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-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/tools v0.0.0-20191119224855-298f0cb1881e h1:aZzprAO9/8oim3qStq3wc1Xuxx4QmAGriC4VU4ojemQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/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=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/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/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=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
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/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
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-20191114184206-e782cd3c129f h1:GiPwtSzdP43eI1hpPCbROQCCIgCuiMMNF8YUVLF3vJo=
|
||||
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/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
||||
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
|
||||
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e h1:KLHHjkdQFomZy8+06csTWZ0m1343QqxZhR2LJ1OxCYM=
|
||||
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw=
|
||||
sigs.k8s.io/kustomize/kyaml v0.13.0 h1:9c+ETyNfSrVhxvphs+K2dzT3dh5oVPPEqPOE/cUpScY=
|
||||
sigs.k8s.io/kustomize/kyaml v0.13.0/go.mod h1:FTJxEZ86ScK184NpGSAQcfEqee0nul8oLCK30D47m4E=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
|
||||
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
|
||||
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
||||
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=
|
||||
|
||||
@@ -20,12 +20,12 @@ func SortArrayAndComputeHash(s []string) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return Encode(Hash(string(data)))
|
||||
return encode(hex256(string(data)))
|
||||
}
|
||||
|
||||
// Copied from https://github.com/kubernetes/kubernetes
|
||||
// /blob/master/pkg/kubectl/util/hash/hash.go
|
||||
func Encode(hex string) (string, error) {
|
||||
func encode(hex string) (string, error) {
|
||||
if len(hex) < 10 {
|
||||
return "", fmt.Errorf(
|
||||
"input length must be at least 10")
|
||||
@@ -48,23 +48,18 @@ func Encode(hex string) (string, error) {
|
||||
return string(enc), nil
|
||||
}
|
||||
|
||||
// Hash returns the hex form of the sha256 of the argument.
|
||||
func Hash(data string) string {
|
||||
// hex256 returns the hex form of the sha256 of the argument.
|
||||
func hex256(data string) string {
|
||||
return fmt.Sprintf("%x", sha256.Sum256([]byte(data)))
|
||||
}
|
||||
|
||||
// HashRNode returns the hash value of input RNode
|
||||
func HashRNode(node *yaml.RNode) (string, error) {
|
||||
// get node kind
|
||||
kindNode, err := node.Pipe(yaml.FieldMatcher{Name: "kind"})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
kind := kindNode.YNode().Value
|
||||
// Hasher computes the hash of an RNode.
|
||||
type Hasher struct{}
|
||||
|
||||
// calculate hash for different kinds
|
||||
encoded := ""
|
||||
switch kind {
|
||||
// Hash returns a hash of the argument.
|
||||
func (h *Hasher) Hash(node *yaml.RNode) (r string, err error) {
|
||||
var encoded string
|
||||
switch node.GetKind() {
|
||||
case "ConfigMap":
|
||||
encoded, err = encodeConfigMap(node)
|
||||
case "Secret":
|
||||
@@ -77,10 +72,11 @@ func HashRNode(node *yaml.RNode) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return Encode(Hash(encoded))
|
||||
return encode(hex256(encoded))
|
||||
}
|
||||
|
||||
func getNodeValues(node *yaml.RNode, paths []string) (map[string]interface{}, error) {
|
||||
func getNodeValues(
|
||||
node *yaml.RNode, paths []string) (map[string]interface{}, error) {
|
||||
values := make(map[string]interface{})
|
||||
for _, p := range paths {
|
||||
vn, err := node.Pipe(yaml.Lookup(p))
|
||||
@@ -117,8 +113,11 @@ func encodeConfigMap(node *yaml.RNode) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
m := map[string]interface{}{"kind": "ConfigMap", "name": values["metadata/name"],
|
||||
"data": values["data"]}
|
||||
m := map[string]interface{}{
|
||||
"kind": "ConfigMap",
|
||||
"name": values["metadata/name"],
|
||||
"data": values["data"],
|
||||
}
|
||||
if _, ok := values["binaryData"].(map[string]interface{}); ok {
|
||||
m["binaryData"] = values["binaryData"]
|
||||
}
|
||||
|
||||
@@ -32,10 +32,10 @@ func TestSortArrayAndComputeHash(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHash(t *testing.T) {
|
||||
func Test_hex256(t *testing.T) {
|
||||
// hash the empty string to be sure that sha256 is being used
|
||||
expect := "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
sum := Hash("")
|
||||
sum := hex256("")
|
||||
if expect != sum {
|
||||
t.Errorf("expected hash %q but got %q", expect, sum)
|
||||
}
|
||||
@@ -56,7 +56,7 @@ kind: ConfigMap`, "6ct58987ht", ""},
|
||||
{"one key", `
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
data:
|
||||
data:
|
||||
one: ""`, "9g67k2htb6", ""},
|
||||
// three keys (tests sorting order)
|
||||
{"three keys", `
|
||||
@@ -93,17 +93,17 @@ data:
|
||||
binaryData:
|
||||
two: ""`, "698h7c7t9m", ""},
|
||||
}
|
||||
|
||||
h := &Hasher{}
|
||||
for _, c := range cases {
|
||||
node, err := yaml.Parse(c.cmYaml)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
h, err := HashRNode(node)
|
||||
hashed, err := h.Hash(node)
|
||||
if SkipRest(t, c.desc, err, c.err) {
|
||||
continue
|
||||
}
|
||||
if c.hash != h {
|
||||
if c.hash != hashed {
|
||||
t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h)
|
||||
}
|
||||
}
|
||||
@@ -154,35 +154,34 @@ type: my-type
|
||||
data:
|
||||
one: ""`, "74bd68bm66", ""},
|
||||
}
|
||||
|
||||
h := &Hasher{}
|
||||
for _, c := range cases {
|
||||
node, err := yaml.Parse(c.secretYaml)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
h, err := HashRNode(node)
|
||||
hashed, err := h.Hash(node)
|
||||
if SkipRest(t, c.desc, err, c.err) {
|
||||
continue
|
||||
}
|
||||
if c.hash != h {
|
||||
if c.hash != hashed {
|
||||
t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnstructuredHash(t *testing.T) {
|
||||
cases := []struct {
|
||||
desc string
|
||||
unstructured string
|
||||
hash string
|
||||
err string
|
||||
func TestBasicHash(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
res string
|
||||
hash string
|
||||
err string
|
||||
}{
|
||||
{"minimal", `
|
||||
"minimal": {`
|
||||
apiVersion: test/v1
|
||||
kind: TestResource
|
||||
metadata:
|
||||
name: my-resource`, "244782mkb7", ""},
|
||||
{"with spec", `
|
||||
"with spec": {`
|
||||
apiVersion: test/v1
|
||||
kind: TestResource
|
||||
metadata:
|
||||
@@ -191,19 +190,22 @@ spec:
|
||||
foo: 1
|
||||
bar: abc`, "59m2mdccg4", ""},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
node, err := yaml.Parse(c.unstructured)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
h, err := HashRNode(node)
|
||||
if SkipRest(t, c.desc, err, c.err) {
|
||||
continue
|
||||
}
|
||||
if c.hash != h {
|
||||
t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h)
|
||||
}
|
||||
h := &Hasher{}
|
||||
for n := range cases {
|
||||
c := cases[n]
|
||||
t.Run(n, func(t *testing.T) {
|
||||
node, err := yaml.Parse(c.res)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
hashed, err := h.Hash(node)
|
||||
if SkipRest(t, n, err, c.err) {
|
||||
return
|
||||
}
|
||||
if c.hash != hashed {
|
||||
t.Errorf("case %q, expect hash %q but got %q", n, c.hash, h)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,7 +224,7 @@ kind: ConfigMap`, `{"data":"","kind":"ConfigMap","name":""}`, ""},
|
||||
{"one key", `
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
data:
|
||||
data:
|
||||
one: ""`, `{"data":{"one":""},"kind":"ConfigMap","name":""}`, ""},
|
||||
// three keys (tests sorting order)
|
||||
{"three keys", `
|
||||
@@ -334,9 +336,10 @@ data:
|
||||
}
|
||||
}
|
||||
|
||||
// SkipRest returns true if there was a non-nil error or if we expected an error that didn't happen,
|
||||
// and logs the appropriate error on the test object.
|
||||
// The return value indicates whether we should skip the rest of the test case due to the error result.
|
||||
// SkipRest returns true if there was a non-nil error or if we expected an
|
||||
// error that didn't happen, and logs the appropriate error on the test object.
|
||||
// The return value indicates whether we should skip the rest of the test case
|
||||
// due to the error result.
|
||||
func SkipRest(t *testing.T, desc string, err error, contains string) bool {
|
||||
if err != nil {
|
||||
if len(contains) == 0 {
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
package ifc
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
// Validator provides functions to validate annotations and labels
|
||||
@@ -38,87 +38,10 @@ type Loader interface {
|
||||
Cleanup() error
|
||||
}
|
||||
|
||||
// Kunstructured represents a Kubernetes Resource Model object.
|
||||
type Kunstructured interface {
|
||||
// Several uses.
|
||||
Copy() Kunstructured
|
||||
|
||||
// GetAnnotations returns the k8s annotations.
|
||||
GetAnnotations() map[string]string
|
||||
|
||||
// GetData returns a top-level "data" field, as in a ConfigMap.
|
||||
GetDataMap() map[string]string
|
||||
|
||||
// Used by ResAccumulator and ReplacementTransformer.
|
||||
GetFieldValue(string) (interface{}, error)
|
||||
|
||||
// Used by Resource.OrgId
|
||||
GetGvk() resid.Gvk
|
||||
|
||||
// Used by resource.Factory.SliceFromBytes
|
||||
GetKind() string
|
||||
|
||||
// GetLabels returns the k8s labels.
|
||||
GetLabels() map[string]string
|
||||
|
||||
// Used by Resource.CurId and resource factory.
|
||||
GetName() string
|
||||
|
||||
// Used by special case code in
|
||||
// ResMap.SubsetThatCouldBeReferencedByResource
|
||||
GetSlice(path string) ([]interface{}, error)
|
||||
|
||||
// GetString returns the value of a string field.
|
||||
// Used by Resource.GetNamespace
|
||||
GetString(string) (string, error)
|
||||
|
||||
// Several uses.
|
||||
Map() map[string]interface{}
|
||||
|
||||
// Used by Resource.AsYAML and Resource.String
|
||||
MarshalJSON() ([]byte, error)
|
||||
|
||||
// Used by resWrangler.Select
|
||||
MatchesAnnotationSelector(selector string) (bool, error)
|
||||
|
||||
// Used by resWrangler.Select
|
||||
MatchesLabelSelector(selector string) (bool, error)
|
||||
|
||||
// SetAnnotations replaces the k8s annotations.
|
||||
SetAnnotations(map[string]string)
|
||||
|
||||
// SetDataMap sets a top-level "data" field, as in a ConfigMap.
|
||||
SetDataMap(map[string]string)
|
||||
|
||||
// Used by PatchStrategicMergeTransformer.
|
||||
SetGvk(resid.Gvk)
|
||||
|
||||
// SetLabels replaces the k8s labels.
|
||||
SetLabels(map[string]string)
|
||||
|
||||
// SetName changes the name.
|
||||
SetName(string)
|
||||
|
||||
// SetNamespace changes the namespace.
|
||||
SetNamespace(string)
|
||||
|
||||
// Needed, for now, by kyaml/filtersutil.ApplyToJSON.
|
||||
UnmarshalJSON([]byte) error
|
||||
}
|
||||
|
||||
// KunstructuredFactory makes instances of Kunstructured.
|
||||
type KunstructuredFactory interface {
|
||||
SliceFromBytes([]byte) ([]Kunstructured, error)
|
||||
FromMap(m map[string]interface{}) Kunstructured
|
||||
Hasher() KunstructuredHasher
|
||||
MakeConfigMap(kvLdr KvLoader, args *types.ConfigMapArgs) (Kunstructured, error)
|
||||
MakeSecret(kvLdr KvLoader, args *types.SecretArgs) (Kunstructured, error)
|
||||
}
|
||||
|
||||
// KunstructuredHasher returns a hash of the argument
|
||||
// KustHasher returns a hash of the argument
|
||||
// or an error.
|
||||
type KunstructuredHasher interface {
|
||||
Hash(Kunstructured) (string, error)
|
||||
type KustHasher interface {
|
||||
Hash(*yaml.RNode) (string, error)
|
||||
}
|
||||
|
||||
// See core.v1.SecretTypeOpaque
|
||||
|
||||
@@ -7,19 +7,26 @@ 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"
|
||||
"k8s.io/kube-openapi/pkg/validation/spec"
|
||||
"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/kustomize/kyaml/filesys"
|
||||
"sigs.k8s.io/kustomize/kyaml/resid"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
type myProperties map[string]spec.Schema
|
||||
type nameToApiMap map[string]common.OpenAPIDefinition
|
||||
// OpenAPIDefinition describes single type.
|
||||
// Normally these definitions are auto-generated using gen-openapi.
|
||||
// Same as in k8s.io / kube-openapi / pkg / common.
|
||||
type OpenAPIDefinition struct {
|
||||
Schema spec.Schema
|
||||
Dependencies []string
|
||||
}
|
||||
|
||||
type myProperties = map[string]spec.Schema
|
||||
type nameToApiMap map[string]OpenAPIDefinition
|
||||
|
||||
// LoadConfigFromCRDs parse CRD schemas from paths into a TransformerConfig
|
||||
func LoadConfigFromCRDs(
|
||||
@@ -162,7 +169,7 @@ func loadCrdIntoConfig(
|
||||
err = theConfig.AddNamereferenceFieldSpec(
|
||||
builtinconfig.NameBackReferences{
|
||||
Gvk: resid.Gvk{Kind: kind, Version: version},
|
||||
FieldSpecs: []types.FieldSpec{
|
||||
Referrers: []types.FieldSpec{
|
||||
makeFs(theGvk, append(path, propName, nameKey))},
|
||||
})
|
||||
if err != nil {
|
||||
@@ -171,9 +178,12 @@ func loadCrdIntoConfig(
|
||||
}
|
||||
}
|
||||
if property.Ref.GetURL() != nil {
|
||||
loadCrdIntoConfig(
|
||||
err = loadCrdIntoConfig(
|
||||
theConfig, theGvk, theMap,
|
||||
property.Ref.String(), append(path, propName))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -7,13 +7,13 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/filesys"
|
||||
"sigs.k8s.io/kustomize/api/loader"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
. "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/loader"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/filesys"
|
||||
"sigs.k8s.io/kustomize/kyaml/resid"
|
||||
)
|
||||
|
||||
// This defines two CRD's: Bee and MyKind.
|
||||
@@ -140,21 +140,19 @@ func TestLoadCRDs(t *testing.T) {
|
||||
nbrs := []builtinconfig.NameBackReferences{
|
||||
{
|
||||
Gvk: resid.Gvk{Kind: "Secret", Version: "v1"},
|
||||
FieldSpecs: []types.FieldSpec{
|
||||
Referrers: []types.FieldSpec{
|
||||
{
|
||||
CreateIfNotPresent: false,
|
||||
Gvk: resid.Gvk{Kind: "MyKind"},
|
||||
Path: "spec/secretRef/name",
|
||||
Gvk: resid.Gvk{Kind: "MyKind"},
|
||||
Path: "spec/secretRef/name",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Gvk: resid.Gvk{Kind: "Bee", Version: "v1beta1"},
|
||||
FieldSpecs: []types.FieldSpec{
|
||||
Referrers: []types.FieldSpec{
|
||||
{
|
||||
CreateIfNotPresent: false,
|
||||
Gvk: resid.Gvk{Kind: "MyKind"},
|
||||
Path: "spec/beeRef/name",
|
||||
Gvk: resid.Gvk{Kind: "MyKind"},
|
||||
Path: "spec/beeRef/name",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -165,16 +163,13 @@ func TestLoadCRDs(t *testing.T) {
|
||||
}
|
||||
|
||||
fSys := filesys.MakeFsInMemory()
|
||||
fSys.WriteFile("/testpath/crd.json", []byte(crdContent))
|
||||
err := fSys.WriteFile("/testpath/crd.json", []byte(crdContent))
|
||||
require.NoError(t, err)
|
||||
ldr, err := loader.NewLoader(loader.RestrictionRootOnly, "/testpath", fSys)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error:%v", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
actualTc, err := LoadConfigFromCRDs(ldr, []string{"crd.json"})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error:%v", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
if !reflect.DeepEqual(actualTc, expectedTc) {
|
||||
t.Fatalf("expected\n %v\n but got\n %v\n", expectedTc, actualTc)
|
||||
}
|
||||
|
||||
@@ -4,19 +4,26 @@
|
||||
package accumulator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/filters/nameref"
|
||||
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/resource"
|
||||
"sigs.k8s.io/kustomize/kyaml/resid"
|
||||
)
|
||||
|
||||
type nameReferenceTransformer struct {
|
||||
backRefs []builtinconfig.NameBackReferences
|
||||
}
|
||||
|
||||
const doDebug = false
|
||||
|
||||
var _ resmap.Transformer = &nameReferenceTransformer{}
|
||||
|
||||
type filterMap map[*resource.Resource][]nameref.Filter
|
||||
|
||||
// newNameReferenceTransformer constructs a nameReferenceTransformer
|
||||
// with a given slice of NameBackReferences.
|
||||
func newNameReferenceTransformer(
|
||||
@@ -29,16 +36,64 @@ func newNameReferenceTransformer(
|
||||
|
||||
// Transform updates name references in resource A that
|
||||
// refer to resource B, given that B's name may have
|
||||
// changed. A is the referrer, B is the referralTarget.
|
||||
// changed.
|
||||
//
|
||||
// For example, a HorizontalPodAutoscaler (HPA)
|
||||
// necessarily refers to a Deployment, the thing that
|
||||
// the HPA scales. The Deployment's name might change
|
||||
// (e.g. prefix added), and the reference in the HPA
|
||||
// has to be fixed.
|
||||
// an HPA scales. In this case:
|
||||
//
|
||||
// In the outer loop over the ResMap below, say we
|
||||
// encounter a specific HPA. Then, in scanning the set
|
||||
// - the HPA instance is the Referrer,
|
||||
// - the Deployment instance is the ReferralTarget.
|
||||
//
|
||||
// If the Deployment's name changes, e.g. a prefix is added,
|
||||
// then the HPA's reference to the Deployment must be fixed.
|
||||
//
|
||||
func (t *nameReferenceTransformer) Transform(m resmap.ResMap) error {
|
||||
fMap := t.determineFilters(m.Resources())
|
||||
debug(fMap)
|
||||
for r, fList := range fMap {
|
||||
c := m.SubsetThatCouldBeReferencedByResource(r)
|
||||
for _, f := range fList {
|
||||
f.Referrer = r
|
||||
f.ReferralCandidates = c
|
||||
if err := f.Referrer.ApplyFilter(f); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func debug(fMap filterMap) {
|
||||
if !doDebug {
|
||||
return
|
||||
}
|
||||
fmt.Printf("filterMap has %d entries:\n", len(fMap))
|
||||
rCount := 0
|
||||
for r, fList := range fMap {
|
||||
yml, _ := r.AsYAML()
|
||||
rCount++
|
||||
fmt.Printf(`
|
||||
---- %3d. possible referrer -------------
|
||||
%s
|
||||
---------`, rCount, string(yml),
|
||||
)
|
||||
for i, f := range fList {
|
||||
fmt.Printf(`
|
||||
%3d/%3d update: %s
|
||||
from: %s
|
||||
`, rCount, i+1, f.NameFieldToUpdate.Path, f.ReferralTarget,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Produce a map from referrer resources that might need to be fixed
|
||||
// to filters that might fix them. The keys to this map are potential
|
||||
// referrers, so won't include resources like ConfigMap or Secret.
|
||||
//
|
||||
// In the inner loop over the resources below, say we
|
||||
// encounter an HPA instance. Then, in scanning the set
|
||||
// of all known backrefs, we encounter an entry like
|
||||
//
|
||||
// - kind: Deployment
|
||||
@@ -48,64 +103,60 @@ func newNameReferenceTransformer(
|
||||
//
|
||||
// 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.
|
||||
// Deployment.
|
||||
//
|
||||
// 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 (t *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 _, referralTarget := range t.backRefs {
|
||||
for _, fSpec := range referralTarget.FieldSpecs {
|
||||
if referrer.OrgId().IsSelected(&fSpec.Gvk) {
|
||||
if candidates == nil {
|
||||
// This excludes objects from other namespaces.
|
||||
// In most realistic uses, it returns all elements of m,
|
||||
// (since they're all in the same namespace).
|
||||
candidates = m.SubsetThatCouldBeReferencedByResource(referrer)
|
||||
}
|
||||
// One way to get here is with, say, a referrer that's an
|
||||
// HPA, and a target that's a Deployment (one of the
|
||||
// Deployment's fieldSpecs selects an HPA). Now we look
|
||||
// through the candidates to see if one is a Deployment
|
||||
// (the target), and if so, get the Deployment's name and
|
||||
// write it into the referrer, at the field specfied in
|
||||
// fSpec.
|
||||
err := referrer.ApplyFilter(nameref.Filter{
|
||||
Referrer: referrer,
|
||||
NameFieldToUpdate: fSpec,
|
||||
ReferralTarget: referralTarget.Gvk,
|
||||
ReferralCandidates: candidates,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
// This means that a filter will need to hunt for the right Deployment,
|
||||
// obtain it's new name, and write that name into the HPA's
|
||||
// 'spec/scaleTargetRef/name' field. Return a filter that can do that.
|
||||
func (t *nameReferenceTransformer) determineFilters(
|
||||
resources []*resource.Resource) (fMap filterMap) {
|
||||
|
||||
// We cache the resource OrgId values because they don't change and otherwise are very visible in a memory pprof
|
||||
resourceOrgIds := make([]resid.ResId, len(resources))
|
||||
for i, resource := range resources {
|
||||
resourceOrgIds[i] = resource.OrgId()
|
||||
}
|
||||
|
||||
fMap = make(filterMap)
|
||||
for _, backReference := range t.backRefs {
|
||||
for _, referrerSpec := range backReference.Referrers {
|
||||
for i, res := range resources {
|
||||
if resourceOrgIds[i].IsSelected(&referrerSpec.Gvk) {
|
||||
// If this is true, the res might be a referrer, and if
|
||||
// so, the name reference it holds might need an update.
|
||||
if resHasField(res, referrerSpec.Path) {
|
||||
// Optimization - the referrer has the field
|
||||
// that might need updating.
|
||||
fMap[res] = append(fMap[res], nameref.Filter{
|
||||
// Name field to write in the Referrer.
|
||||
// If the path specified here isn't found in
|
||||
// the Referrer, nothing happens (no error,
|
||||
// no field creation).
|
||||
NameFieldToUpdate: referrerSpec,
|
||||
// Specification of object class to read from.
|
||||
// Always read from metadata/name field.
|
||||
ReferralTarget: backReference.Gvk,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return fMap
|
||||
}
|
||||
|
||||
// TODO: check res for field existence here to avoid extra work.
|
||||
// res.GetFieldValue, which uses yaml.Lookup under the hood, doesn't know
|
||||
// how to parse fieldspec-style paths that make no distinction
|
||||
// between maps and sequences. This means it cannot lookup commonly
|
||||
// used "indeterminate" paths like
|
||||
// spec/containers/env/valueFrom/configMapKeyRef/name
|
||||
// ('containers' is a list, not a map).
|
||||
// However, the fieldspec filter does know how to handle this;
|
||||
// extract that code and call it here?
|
||||
func resHasField(res *resource.Resource, path string) bool {
|
||||
return true
|
||||
// fld := strings.Join(utils.PathSplitter(path), ".")
|
||||
// _, e := res.GetFieldValue(fld)
|
||||
// return e == nil
|
||||
}
|
||||
|
||||
@@ -9,11 +9,13 @@ import (
|
||||
|
||||
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
||||
"sigs.k8s.io/kustomize/api/provider"
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
resmaptest_test "sigs.k8s.io/kustomize/api/testutils/resmaptest"
|
||||
"sigs.k8s.io/kustomize/kyaml/resid"
|
||||
)
|
||||
|
||||
const notEqualErrFmt = "expected (self) doesn't match actual (other): %v"
|
||||
|
||||
func TestNameReferenceHappyRun(t *testing.T) {
|
||||
m := resmaptest_test.NewRmBuilderDefault(t).AddWithName(
|
||||
"cm1",
|
||||
@@ -470,7 +472,7 @@ func TestNameReferenceHappyRun(t *testing.T) {
|
||||
}
|
||||
|
||||
if err = expected.ErrorIfNotEqualLists(m); err != nil {
|
||||
t.Fatalf("actual doesn't match expected: %v", err)
|
||||
t.Fatalf(notEqualErrFmt, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -518,7 +520,19 @@ func TestNameReferenceUnhappyRun(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}).ResMap(),
|
||||
expectedErr: "cannot find field 'name' in node"},
|
||||
expectedErr: `updating name reference in 'rules/resourceNames' field of ` +
|
||||
`'rbac.authorization.k8s.io_v1_ClusterRole|~X|cr'` +
|
||||
`: considering field 'rules/resourceNames' of object
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: cr
|
||||
rules:
|
||||
- resourceNames:
|
||||
foo: bar
|
||||
resources:
|
||||
- secrets
|
||||
: visit traversal on path: [resourceNames]: path config error; no 'name' field in node`},
|
||||
}
|
||||
|
||||
nrt := newNameReferenceTransformer(builtinconfig.MakeDefaultConfig().NameReference)
|
||||
@@ -529,7 +543,7 @@ func TestNameReferenceUnhappyRun(t *testing.T) {
|
||||
}
|
||||
|
||||
if !strings.Contains(err.Error(), test.expectedErr) {
|
||||
t.Fatalf("Incorrect error.\nExpected: %s, but got %v",
|
||||
t.Fatalf("Incorrect error.\nExpected:\n %s\nGot:\n%v",
|
||||
test.expectedErr, err)
|
||||
}
|
||||
}
|
||||
@@ -587,7 +601,7 @@ func TestNameReferencePersistentVolumeHappyRun(t *testing.T) {
|
||||
v2.AppendRefBy(c2.CurId())
|
||||
|
||||
if err := m1.ErrorIfNotEqualLists(m2); err != nil {
|
||||
t.Fatalf("actual doesn't match expected: %v", err)
|
||||
t.Fatalf(notEqualErrFmt, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -723,7 +737,7 @@ func TestNameReferenceNamespace(t *testing.T) {
|
||||
|
||||
m.RemoveBuildAnnotations()
|
||||
if err = expected.ErrorIfNotEqualLists(m); err != nil {
|
||||
t.Fatalf("actual doesn't match expected: %v", err)
|
||||
t.Fatalf(notEqualErrFmt, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -871,9 +885,9 @@ func TestNameReferenceClusterWide(t *testing.T) {
|
||||
}).ResMap()
|
||||
|
||||
clusterRoleId := resid.NewResId(
|
||||
resid.Gvk{Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "ClusterRole"}, modifiedname)
|
||||
resid.NewGvk("rbac.authorization.k8s.io", "v1", "ClusterRole"), modifiedname)
|
||||
clusterRoleBindingId := resid.NewResId(
|
||||
resid.Gvk{Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "ClusterRoleBinding"}, modifiedname)
|
||||
resid.NewGvk("rbac.authorization.k8s.io", "v1", "ClusterRoleBinding"), modifiedname)
|
||||
clusterRole, _ := expected.GetByCurrentId(clusterRoleId)
|
||||
clusterRole.AppendRefBy(clusterRoleBindingId)
|
||||
|
||||
@@ -883,9 +897,11 @@ func TestNameReferenceClusterWide(t *testing.T) {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
expected.RemoveBuildAnnotations()
|
||||
m.RemoveBuildAnnotations()
|
||||
|
||||
if err = expected.ErrorIfNotEqualLists(m); err != nil {
|
||||
t.Fatalf("actual doesn't match expected: %v", err)
|
||||
t.Fatalf(notEqualErrFmt, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -998,9 +1014,11 @@ func TestNameReferenceNamespaceTransformation(t *testing.T) {
|
||||
}).ResMap()
|
||||
|
||||
clusterRoleId := resid.NewResId(
|
||||
resid.Gvk{Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "ClusterRole"}, modifiedname)
|
||||
resid.NewGvk("rbac.authorization.k8s.io", "v1", "ClusterRole"),
|
||||
modifiedname)
|
||||
clusterRoleBindingId := resid.NewResId(
|
||||
resid.Gvk{Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "ClusterRoleBinding"}, modifiedname)
|
||||
resid.NewGvk("rbac.authorization.k8s.io", "v1", "ClusterRoleBinding"),
|
||||
modifiedname)
|
||||
clusterRole, _ := expected.GetByCurrentId(clusterRoleId)
|
||||
clusterRole.AppendRefBy(clusterRoleBindingId)
|
||||
|
||||
@@ -1012,7 +1030,7 @@ func TestNameReferenceNamespaceTransformation(t *testing.T) {
|
||||
|
||||
m.RemoveBuildAnnotations()
|
||||
if err = expected.ErrorIfNotEqualLists(m); err != nil {
|
||||
t.Fatalf("actual doesn't match expected: %v", err)
|
||||
t.Fatalf(notEqualErrFmt, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1049,6 +1067,6 @@ func TestNameReferenceCandidateSelection(t *testing.T) {
|
||||
|
||||
m.RemoveBuildAnnotations()
|
||||
if err = expected.ErrorIfNotEqualLists(m); err != nil {
|
||||
t.Fatalf("actual doesn't match expected: %v", err)
|
||||
t.Fatalf(notEqualErrFmt, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,11 +7,10 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/konfig"
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
resmaptest_test "sigs.k8s.io/kustomize/api/testutils/resmaptest"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/resid"
|
||||
)
|
||||
|
||||
func TestRefVarTransformer(t *testing.T) {
|
||||
@@ -24,14 +23,12 @@ func TestRefVarTransformer(t *testing.T) {
|
||||
res resmap.ResMap
|
||||
unused []string
|
||||
}
|
||||
testCases := []struct {
|
||||
description string
|
||||
given given
|
||||
expected expected
|
||||
errMessage string
|
||||
testCases := map[string]struct {
|
||||
given given
|
||||
expected expected
|
||||
errMessage string
|
||||
}{
|
||||
{
|
||||
description: "var replacement in map[string]",
|
||||
"var replacement in map[string]": {
|
||||
given: given{
|
||||
varMap: map[string]interface{}{
|
||||
"FOO": "replacementForFoo",
|
||||
@@ -106,8 +103,7 @@ func TestRefVarTransformer(t *testing.T) {
|
||||
unused: []string{"BAR"},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "var replacement panic in map[string]",
|
||||
"var replacement panic in map[string]": {
|
||||
given: given{
|
||||
varMap: map[string]interface{}{},
|
||||
fs: []types.FieldSpec{
|
||||
@@ -124,22 +120,17 @@ func TestRefVarTransformer(t *testing.T) {
|
||||
"slice": []interface{}{5}, // noticeably *not* a []string
|
||||
}}).ResMap(),
|
||||
},
|
||||
// TODO(#3304): DECISION - kyaml better; not a bug.
|
||||
errMessage: konfig.IfApiMachineryElseKyaml(
|
||||
`obj '{"apiVersion": "v1", "data": {"slice": [5]}, "kind": "ConfigMap", "metadata": {"name": "cm1"}}
|
||||
' at path 'data/slice': invalid value type expect a string`,
|
||||
`obj 'apiVersion: v1
|
||||
errMessage: `considering field 'data/slice' of object
|
||||
apiVersion: v1
|
||||
data:
|
||||
slice:
|
||||
- 5
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: cm1
|
||||
' at path 'data/slice': invalid value type expect a string`,
|
||||
),
|
||||
: invalid value type expect a string`,
|
||||
},
|
||||
{
|
||||
description: "var replacement in nil",
|
||||
"var replacement in nil": {
|
||||
given: given{
|
||||
varMap: map[string]interface{}{},
|
||||
fs: []types.FieldSpec{
|
||||
@@ -171,20 +162,18 @@ metadata:
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.description, func(t *testing.T) {
|
||||
// arrange
|
||||
for tn, tc := range testCases {
|
||||
t.Run(tn, func(t *testing.T) {
|
||||
tr := newRefVarTransformer(tc.given.varMap, tc.given.fs)
|
||||
|
||||
// act
|
||||
err := tr.Transform(tc.given.res)
|
||||
|
||||
// assert
|
||||
if tc.errMessage != "" {
|
||||
if err == nil {
|
||||
t.Fatalf("missing expected error %v", tc.errMessage)
|
||||
} else if err.Error() != tc.errMessage {
|
||||
t.Fatalf("actual error doesn't match expected error: \nACTUAL: %v\nEXPECTED: %v", err.Error(), tc.errMessage)
|
||||
t.Fatalf(`actual error doesn't match expected error:
|
||||
ACTUAL: %v
|
||||
EXPECTED: %v`,
|
||||
err.Error(), tc.errMessage)
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
@@ -194,7 +183,13 @@ metadata:
|
||||
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)
|
||||
t.Fatalf(`actual doesn't match expected:
|
||||
ACTUAL:
|
||||
%v
|
||||
EXPECTED:
|
||||
%v
|
||||
ERR: %v`,
|
||||
a, e, err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -9,9 +9,9 @@ import (
|
||||
"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"
|
||||
"sigs.k8s.io/kustomize/kyaml/resid"
|
||||
)
|
||||
|
||||
// ResAccumulator accumulates resources and the rules
|
||||
@@ -72,12 +72,12 @@ 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() {
|
||||
if targetId.Namespace != "" || targetId.IsClusterScoped() {
|
||||
// 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)
|
||||
matched := ra.resMap.GetMatchingResourcesByAnyId(idMatcher)
|
||||
if len(matched) > 1 {
|
||||
return fmt.Errorf(
|
||||
"found %d resId matches for var %s "+
|
||||
@@ -107,6 +107,7 @@ func (ra *ResAccumulator) findVarValueFromResources(v types.Var) (interface{}, e
|
||||
for _, res := range ra.resMap.Resources() {
|
||||
for _, varName := range res.GetRefVarNames() {
|
||||
if varName == v.Name {
|
||||
//nolint: staticcheck
|
||||
s, err := res.GetFieldValue(v.FieldRef.FieldPath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf(
|
||||
@@ -167,3 +168,23 @@ func (ra *ResAccumulator) FixBackReferences() (err error) {
|
||||
return ra.Transform(
|
||||
newNameReferenceTransformer(ra.tConfig.NameReference))
|
||||
}
|
||||
|
||||
// Intersection drops the resources which "other" does not have.
|
||||
func (ra *ResAccumulator) Intersection(other resmap.ResMap) error {
|
||||
for _, curId := range ra.resMap.AllIds() {
|
||||
toDelete := true
|
||||
for _, otherId := range other.AllIds() {
|
||||
if otherId == curId {
|
||||
toDelete = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if toDelete {
|
||||
err := ra.resMap.Remove(curId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -10,14 +10,15 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
. "sigs.k8s.io/kustomize/api/internal/accumulator"
|
||||
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
||||
"sigs.k8s.io/kustomize/api/provider"
|
||||
"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"
|
||||
"sigs.k8s.io/kustomize/kyaml/resid"
|
||||
)
|
||||
|
||||
func makeResAccumulator(t *testing.T) *ResAccumulator {
|
||||
@@ -224,20 +225,26 @@ func TestResolveVarConflicts(t *testing.T) {
|
||||
// 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))
|
||||
err := rm0.Append(rf.FromMap(fooAws))
|
||||
require.NoError(t, err)
|
||||
ac0 := MakeEmptyAccumulator()
|
||||
ac0.AppendAll(rm0)
|
||||
ac0.MergeVars([]types.Var{varFoo})
|
||||
err = ac0.AppendAll(rm0)
|
||||
require.NoError(t, err)
|
||||
err = ac0.MergeVars([]types.Var{varFoo})
|
||||
require.NoError(t, err)
|
||||
|
||||
rm1 := resmap.New()
|
||||
rm1.Append(rf.FromMap(barAws))
|
||||
err = rm1.Append(rf.FromMap(barAws))
|
||||
require.NoError(t, err)
|
||||
ac1 := MakeEmptyAccumulator()
|
||||
ac1.AppendAll(rm1)
|
||||
ac1.MergeVars([]types.Var{varBar})
|
||||
err = ac1.AppendAll(rm1)
|
||||
require.NoError(t, err)
|
||||
err = ac1.MergeVars([]types.Var{varBar})
|
||||
require.NoError(t, err)
|
||||
|
||||
// validate that two vars of the same name which reference the same concrete
|
||||
// value do not produce a conflict.
|
||||
err := ac0.MergeAccumulator(ac1)
|
||||
err = ac0.MergeAccumulator(ac1)
|
||||
if err == nil {
|
||||
t.Fatalf("see bug gh-1600")
|
||||
}
|
||||
@@ -246,10 +253,13 @@ func TestResolveVarConflicts(t *testing.T) {
|
||||
// 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))
|
||||
err = rm2.Append(rf.FromMap(barGcp))
|
||||
require.NoError(t, err)
|
||||
ac2 := MakeEmptyAccumulator()
|
||||
ac2.AppendAll(rm2)
|
||||
ac2.MergeVars([]types.Var{varBar})
|
||||
err = ac2.AppendAll(rm2)
|
||||
require.NoError(t, err)
|
||||
err = ac2.MergeVars([]types.Var{varBar})
|
||||
require.NoError(t, err)
|
||||
err = ac1.MergeAccumulator(ac2)
|
||||
if err == nil {
|
||||
t.Fatalf("dupe vars w/ different concrete values should conflict")
|
||||
@@ -344,20 +354,21 @@ func TestResolveVarsWithNoambiguation(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}}).
|
||||
// Make it seem like this resource
|
||||
// went through a prefix transformer.
|
||||
Add(map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Service",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "backendOne",
|
||||
"name": "sub-backendOne",
|
||||
"annotations": map[string]interface{}{
|
||||
"internal.config.kubernetes.io/previousKinds": "Service",
|
||||
"internal.config.kubernetes.io/previousNames": "backendOne",
|
||||
"internal.config.kubernetes.io/previousNamespaces": "default",
|
||||
"internal.config.kubernetes.io/prefixes": "sub-",
|
||||
},
|
||||
}}).ResMap()
|
||||
|
||||
// Make it seem like this resource
|
||||
// went through a prefix transformer.
|
||||
r := m.GetByIndex(1)
|
||||
r.AddNamePrefix("sub-")
|
||||
r.SetName("sub-backendOne")
|
||||
r.SetOriginalName("backendOne", true)
|
||||
|
||||
err = ra2.AppendAll(m)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
@@ -399,7 +410,8 @@ func find(name string, resMap resmap.ResMap) *resource.Resource {
|
||||
func getCommand(r *resource.Resource) string {
|
||||
var m map[string]interface{}
|
||||
var c []interface{}
|
||||
m, _ = r.Map()["spec"].(map[string]interface{})
|
||||
resourceMap, _ := r.Map()
|
||||
m, _ = resourceMap["spec"].(map[string]interface{})
|
||||
m, _ = m["template"].(map[string]interface{})
|
||||
m, _ = m["spec"].(map[string]interface{})
|
||||
c, _ = m["containers"].([]interface{})
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package conflict
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/resource"
|
||||
)
|
||||
|
||||
type cdFactory struct{}
|
||||
|
||||
var _ resource.ConflictDetectorFactory = &cdFactory{}
|
||||
|
||||
// NewFactory returns a new conflict detector factory.
|
||||
func NewFactory() resource.ConflictDetectorFactory {
|
||||
return &cdFactory{}
|
||||
}
|
||||
|
||||
// New returns an instance of smPatchMergeOnlyDetector.
|
||||
func (c cdFactory) New(_ resid.Gvk) (resource.ConflictDetector, error) {
|
||||
return &smPatchMergeOnlyDetector{}, nil
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package conflict
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/api/resource"
|
||||
)
|
||||
|
||||
// smPatchMergeOnlyDetector ignores conflicts,
|
||||
// but does real strategic merge patching.
|
||||
// This is part of an effort to eliminate dependence on
|
||||
// apimachinery package to allow kustomize integration
|
||||
// into kubectl (#2506 and #1500)
|
||||
type smPatchMergeOnlyDetector struct{}
|
||||
|
||||
var _ resource.ConflictDetector = &smPatchMergeOnlyDetector{}
|
||||
|
||||
func (c *smPatchMergeOnlyDetector) HasConflict(
|
||||
_, _ *resource.Resource) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// There's at least one case that doesn't work. Suppose one has a
|
||||
// Deployment with a volume with the bizarre "emptyDir: {}" entry.
|
||||
// If you want to get rid of this entry via a patch containing
|
||||
// the entry "emptyDir: null", then the following won't work,
|
||||
// because null entries are eliminated.
|
||||
func (c *smPatchMergeOnlyDetector) MergePatches(
|
||||
r, patch *resource.Resource) (*resource.Resource, error) {
|
||||
err := r.ApplySmPatch(patch)
|
||||
return r, err
|
||||
}
|
||||
@@ -1,428 +0,0 @@
|
||||
## 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 implementations
|
||||
|
||||
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.
|
||||
@@ -1,176 +0,0 @@
|
||||
# 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.
|
||||
@@ -1,195 +0,0 @@
|
||||
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, "kustomize")
|
||||
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("", " ")
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
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"]
|
||||
@@ -1,29 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
FROM golang:1.14 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 []
|
||||
@@ -1,206 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/internal/crawl/utils"
|
||||
|
||||
"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
|
||||
)
|
||||
|
||||
type CrawlMode int
|
||||
|
||||
const (
|
||||
CrawlUnknown CrawlMode = iota
|
||||
// Crawl all the kustomization files in all the repositories of a Github user
|
||||
CrawlUser
|
||||
// Crawl all the kustomization files in a Github repo
|
||||
CrawlRepo
|
||||
// Crawl all the documents in the index
|
||||
CrawlIndex
|
||||
// Crawl all the kustomization files on Github
|
||||
CrawlGithub
|
||||
// Crawl all the documents in the index and crawling all the kustomization files on Github
|
||||
CrawlIndexAndGithub
|
||||
)
|
||||
|
||||
func NewCrawlMode(s string) CrawlMode {
|
||||
switch s {
|
||||
case "github-user":
|
||||
return CrawlUser
|
||||
case "github-repo":
|
||||
return CrawlRepo
|
||||
case "index+github":
|
||||
return CrawlIndexAndGithub
|
||||
case "index":
|
||||
return CrawlIndex
|
||||
case "github":
|
||||
return CrawlGithub
|
||||
default:
|
||||
return CrawlUnknown
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
indexNamePtr := flag.String(
|
||||
"index", "kustomize", "The name of the ElasticSearch index.")
|
||||
modePtr := flag.String("mode", "index+github",
|
||||
`The crawling mode, which can be one of [github-user, github-repo, index, github, index+github].
|
||||
* github-user: crawl all the kustomization files in all the repositories of a Github user (--github-user must be specified for this mode).
|
||||
* github-repo: crawl all the kustomization files in a Github repository (--github-repo must be specified for this mode).
|
||||
* index: crawl all the documents in the index.
|
||||
* gihub: crawl all the kustomization files on Github.
|
||||
* index+github: crawl all the documents in the index and crawling all the kustomization files on Github.`)
|
||||
githubUserPtr := flag.String("github-user", "",
|
||||
"A github user name (e.g., kubernetes-sigs). This flag is required for the `github-user` mode.")
|
||||
githubRepoPtr := flag.String("github-repo", "",
|
||||
"A github repository name (e.g., kubernetes-sigs/kustomize). This flag is required for the `github-repo` mode.")
|
||||
flag.Parse()
|
||||
|
||||
githubToken := os.Getenv(githubAccessTokenVar)
|
||||
if githubToken == "" {
|
||||
log.Printf("Must set the variable '%s' to make github requests.\n",
|
||||
githubAccessTokenVar)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
idx, err := index.NewKustomizeIndex(ctx, *indexNamePtr)
|
||||
if err != nil {
|
||||
log.Printf("Could not create an index: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
cacheURL := os.Getenv(redisCacheURL)
|
||||
cache, err := redis.DialURL(cacheURL)
|
||||
clientCache := &http.Client{}
|
||||
if err != nil {
|
||||
log.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.
|
||||
indexFunc := func(cdoc crawler.CrawledDocument, mode index.Mode) error {
|
||||
switch d := cdoc.(type) {
|
||||
case *doc.KustomizationDocument:
|
||||
switch mode {
|
||||
case index.Delete:
|
||||
log.Printf("Deleting: %v", d)
|
||||
return idx.Delete(d.ID())
|
||||
default:
|
||||
log.Printf("Inserting: %v", d)
|
||||
return idx.Put(d.ID(), d)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("type %T not supported", d)
|
||||
}
|
||||
}
|
||||
|
||||
// seen tracks the IDs of all the documents in the index and their corresponding file types.
|
||||
// This helps avoid indexing a given document multiple times.
|
||||
seen := utils.NewSeenMap()
|
||||
|
||||
mode := NewCrawlMode(*modePtr)
|
||||
|
||||
ghCrawlerConstructor := func(user, repo string) crawler.Crawler {
|
||||
if user != "" {
|
||||
return github.NewCrawler(githubToken, retryCount, clientCache,
|
||||
github.QueryWith(
|
||||
github.Filename("kustomization.yaml"),
|
||||
github.Filename("kustomization.yml"),
|
||||
github.Filename("kustomization"),
|
||||
github.User(user)),
|
||||
)
|
||||
} else if repo != "" {
|
||||
return github.NewCrawler(githubToken, retryCount, clientCache,
|
||||
github.QueryWith(
|
||||
github.Filename("kustomization.yaml"),
|
||||
github.Filename("kustomization.yml"),
|
||||
github.Filename("kustomization"),
|
||||
github.Repo(repo)),
|
||||
)
|
||||
} else {
|
||||
return github.NewCrawler(githubToken, retryCount, clientCache,
|
||||
github.QueryWith(
|
||||
github.Filename("kustomization.yaml"),
|
||||
github.Filename("kustomization.yml"),
|
||||
github.Filename("kustomization")),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
query := []byte(`{ "query":{ "match_all":{} } }`)
|
||||
it := idx.IterateQuery(query, 10000, 60*time.Second)
|
||||
|
||||
switch mode {
|
||||
case CrawlIndexAndGithub:
|
||||
crawlers := []crawler.Crawler{ghCrawlerConstructor("", "")}
|
||||
crawler.CrawlFromSeedIterator(ctx, it, crawlers, docConverter, indexFunc, seen)
|
||||
crawler.CrawlGithub(ctx, crawlers, docConverter, indexFunc, seen)
|
||||
case CrawlIndex:
|
||||
crawlers := []crawler.Crawler{ghCrawlerConstructor("", "")}
|
||||
crawler.CrawlFromSeedIterator(ctx, it, crawlers, docConverter, indexFunc, seen)
|
||||
case CrawlGithub:
|
||||
crawlers := []crawler.Crawler{ghCrawlerConstructor("", "")}
|
||||
// add all the documents in the index into seen.
|
||||
// this greatly reduces the time overhead of CrawlGithub.
|
||||
for it.Next() {
|
||||
for _, hit := range it.Value().Hits.Hits {
|
||||
d := hit.Document.Document
|
||||
seen.Set(d.ID(), d.FileType)
|
||||
}
|
||||
}
|
||||
if err := it.Err(); err != nil {
|
||||
log.Fatalf("Error iterating the index: %v\n", err)
|
||||
}
|
||||
|
||||
crawler.CrawlGithub(ctx, crawlers, docConverter, indexFunc, seen)
|
||||
case CrawlUser:
|
||||
if *githubUserPtr == "" {
|
||||
flag.Usage()
|
||||
log.Fatalf("Please specify a github user with the github-user flag!")
|
||||
}
|
||||
crawlers := []crawler.Crawler{ghCrawlerConstructor(*githubUserPtr, "")}
|
||||
crawler.CrawlGithub(ctx, crawlers, docConverter, indexFunc, seen)
|
||||
case CrawlRepo:
|
||||
if *githubRepoPtr == "" {
|
||||
flag.Usage()
|
||||
log.Fatalf("Please specify a github repository with the github-repo flag!")
|
||||
}
|
||||
crawlers := []crawler.Crawler{ghCrawlerConstructor("", *githubRepoPtr)}
|
||||
crawler.CrawlGithub(ctx, crawlers, docConverter, indexFunc, seen)
|
||||
case CrawlUnknown:
|
||||
flag.Usage()
|
||||
log.Fatalf("The --mode flag must be one of [github-user, github-repo, index, github, index+github].")
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
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 ./cmd/kustomize_stats
|
||||
|
||||
FROM scratch
|
||||
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||
COPY --from=build /go/bin/kustomize_stats /
|
||||
ENTRYPOINT ["/kustomize_stats"]
|
||||
@@ -1,249 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/internal/crawl/doc"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/internal/crawl/index"
|
||||
)
|
||||
|
||||
// iterateArr adds each item in arr into countMap.
|
||||
func iterateArr(arr []string, countMap map[string]int) {
|
||||
for _, item := range arr {
|
||||
if _, ok := countMap[item]; !ok {
|
||||
countMap[item] = 1
|
||||
} else {
|
||||
countMap[item]++
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// SortMapKeyByValueInt takes a map as its input, sorts its keys according to their values
|
||||
// in the map, and outputs the sorted keys as a slice.
|
||||
func SortMapKeyByValueInt(m map[string]int) []string {
|
||||
keys := make([]string, 0, len(m))
|
||||
for key := range m {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
// sort keys according to their values in the map m
|
||||
sort.Slice(keys, func(i, j int) bool { return m[keys[i]] > m[keys[j]] })
|
||||
return keys
|
||||
}
|
||||
|
||||
// SortMapKeyByValue takes a map as its input, sorts its keys according to their values
|
||||
// in the map, and outputs the sorted keys as a slice.
|
||||
func SortMapKeyByValueLen(m map[string][]string) []string {
|
||||
keys := make([]string, 0, len(m))
|
||||
for key := range m {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
// sort keys according to their values in the map m
|
||||
sort.Slice(keys, func(i, j int) bool { return len(m[keys[i]]) > len(m[keys[j]]) })
|
||||
return keys
|
||||
}
|
||||
|
||||
func GeneratorOrTransformerStats(docs []*doc.KustomizationDocument) {
|
||||
n := len(docs)
|
||||
if n == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
fileType := docs[0].FileType
|
||||
fmt.Printf("There are totally %d %s files.\n", n, fileType)
|
||||
|
||||
GitRepositorySummary(docs, fileType)
|
||||
|
||||
// key of kindToUrls: a string in the KustomizationDocument.Kinds field
|
||||
// value of kindToUrls: a slice of string urls defining a given kind.
|
||||
kindToUrls := make(map[string][]string)
|
||||
|
||||
for _, d := range docs {
|
||||
url := fmt.Sprintf("%s/blob/%s/%s", d.RepositoryURL, d.DefaultBranch, d.FilePath)
|
||||
for _, kind := range d.Kinds {
|
||||
if _, ok := kindToUrls[kind]; !ok {
|
||||
kindToUrls[kind] = []string{url}
|
||||
} else {
|
||||
kindToUrls[kind] = append(kindToUrls[kind], url)
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Printf("There are totally %d kinds of %s\n", len(kindToUrls), fileType)
|
||||
sortedKeys := SortMapKeyByValueLen(kindToUrls)
|
||||
for _, k := range sortedKeys {
|
||||
sort.Strings(kindToUrls[k])
|
||||
fmt.Printf("%s kind %s appears %d times\n", fileType, k, len(kindToUrls[k]))
|
||||
for _, url := range kindToUrls[k] {
|
||||
fmt.Printf("%s\n", url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GitRepositorySummary counts the distribution of docs:
|
||||
// 1) how many git repositories are these docs from?
|
||||
// 2) how many docs are from each git repository?
|
||||
func GitRepositorySummary(docs []*doc.KustomizationDocument, fileType string) {
|
||||
m := make(map[string]int)
|
||||
for _, d := range docs {
|
||||
if _, ok := m[d.RepositoryURL]; ok {
|
||||
m[d.RepositoryURL]++
|
||||
} else {
|
||||
m[d.RepositoryURL] = 1
|
||||
}
|
||||
}
|
||||
sortedKeys := SortMapKeyByValueInt(m)
|
||||
topN := 10
|
||||
i := 0
|
||||
for _, k := range sortedKeys {
|
||||
if i >= topN {
|
||||
break
|
||||
}
|
||||
fmt.Printf("%d %s are from %s\n", m[k], fileType, k)
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
topKindsPtr := flag.Int(
|
||||
"kinds", -1,
|
||||
`the number of kubernetes object kinds to be listed according to their popularities.
|
||||
By default, all the kinds will be listed.
|
||||
If you only want to list the 10 most popular kinds, set the flag to 10.`)
|
||||
topIdentifiersPtr := flag.Int(
|
||||
"identifiers", -1,
|
||||
`the number of identifiers to be listed according to their popularities.
|
||||
By default, all the identifiers will be listed.
|
||||
If you only want to list the 10 most popular identifiers, set the flag to 10.`)
|
||||
topKustomizeFeaturesPtr := flag.Int(
|
||||
"kustomize-features", -1,
|
||||
`the number of kustomize features to be listed according to their popularities.
|
||||
By default, all the features will be listed.
|
||||
If you only want to list the 10 most popular features, set the flag to 10.`)
|
||||
indexNamePtr := flag.String(
|
||||
"index", "kustomize", "The name of the ElasticSearch index.")
|
||||
flag.Parse()
|
||||
|
||||
ctx := context.Background()
|
||||
idx, err := index.NewKustomizeIndex(ctx, *indexNamePtr)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not create an index: %v\n", err)
|
||||
}
|
||||
|
||||
// count tracks the number of documents in the index
|
||||
count := 0
|
||||
|
||||
// kustomizationFilecount tracks the number of kustomization files in the index
|
||||
kustomizationFilecount := 0
|
||||
|
||||
kindsMap := make(map[string]int)
|
||||
identifiersMap := make(map[string]int)
|
||||
kustomizeIdentifiersMap := make(map[string]int)
|
||||
|
||||
// ids tracks the unique IDs of the documents in the index
|
||||
ids := make(map[string]struct{})
|
||||
|
||||
// generatorFiles include all the non-kustomization files whose FileType is generator
|
||||
generatorFiles := make([]*doc.KustomizationDocument, 0)
|
||||
|
||||
// transformersFiles include all the non-kustomization files whose FileType is transformer
|
||||
transformersFiles := make([]*doc.KustomizationDocument, 0)
|
||||
|
||||
checksums := make(map[string]int)
|
||||
|
||||
// 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 {
|
||||
sum := fmt.Sprintf("%x", sha256.Sum256([]byte(hit.Document.DocumentData)))
|
||||
if _, ok := checksums[sum]; ok {
|
||||
checksums[sum]++
|
||||
} else {
|
||||
checksums[sum] = 1
|
||||
}
|
||||
|
||||
// check whether there is any duplicate IDs in the index
|
||||
if _, ok := ids[hit.ID]; !ok {
|
||||
ids[hit.ID] = struct{}{}
|
||||
} else {
|
||||
log.Printf("Found duplicate ID (%s)\n", hit.ID)
|
||||
}
|
||||
|
||||
count++
|
||||
iterateArr(hit.Document.Kinds, kindsMap)
|
||||
iterateArr(hit.Document.Identifiers, identifiersMap)
|
||||
|
||||
if doc.IsKustomizationFile(hit.Document.FilePath) {
|
||||
kustomizationFilecount++
|
||||
iterateArr(hit.Document.Identifiers, kustomizeIdentifiersMap)
|
||||
|
||||
} else {
|
||||
switch hit.Document.FileType {
|
||||
case "generator":
|
||||
generatorFiles = append(generatorFiles, hit.Document.Copy())
|
||||
case "transformer":
|
||||
transformersFiles = append(transformersFiles, hit.Document.Copy())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := it.Err(); err != nil {
|
||||
log.Fatalf("Error iterating: %v\n", err)
|
||||
}
|
||||
|
||||
sortedKindsMapKeys := SortMapKeyByValueInt(kindsMap)
|
||||
sortedIdentifiersMapKeys := SortMapKeyByValueInt(identifiersMap)
|
||||
sortedKustomizeIdentifiersMapKeys := SortMapKeyByValueInt(kustomizeIdentifiersMap)
|
||||
|
||||
fmt.Printf(`The count of unique document IDs in the kustomize index: %d
|
||||
There are %d documents in the kustomize index.
|
||||
%d kinds of kubernetes objects are customized:`, len(ids), count, len(kindsMap))
|
||||
fmt.Printf("\n")
|
||||
|
||||
kindCount := 0
|
||||
for _, key := range sortedKindsMapKeys {
|
||||
if *topKindsPtr < 0 || (*topKindsPtr >= 0 && kindCount < *topKindsPtr) {
|
||||
fmt.Printf("\tkind `%s` is customimzed in %d documents\n", key, kindsMap[key])
|
||||
kindCount++
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("%d kinds of identifiers are found:\n", len(identifiersMap))
|
||||
identifierCount := 0
|
||||
for _, key := range sortedIdentifiersMapKeys {
|
||||
if *topIdentifiersPtr < 0 || (*topIdentifiersPtr >= 0 && identifierCount < *topIdentifiersPtr) {
|
||||
fmt.Printf("\tidentifier `%s` appears in %d documents\n", key, identifiersMap[key])
|
||||
identifierCount++
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf(`There are %d kustomization files in the kustomize index.
|
||||
%d kinds of kustomize features are found:`, kustomizationFilecount, len(kustomizeIdentifiersMap))
|
||||
fmt.Printf("\n")
|
||||
kustomizeFeatureCount := 0
|
||||
for _, key := range sortedKustomizeIdentifiersMapKeys {
|
||||
if *topKustomizeFeaturesPtr < 0 || (*topKustomizeFeaturesPtr >= 0 && kustomizeFeatureCount < *topKustomizeFeaturesPtr) {
|
||||
fmt.Printf("\tfeature `%s` is used in %d documents\n", key, kustomizeIdentifiersMap[key])
|
||||
kustomizeFeatureCount++
|
||||
}
|
||||
}
|
||||
|
||||
GeneratorOrTransformerStats(generatorFiles)
|
||||
GeneratorOrTransformerStats(transformersFiles)
|
||||
|
||||
fmt.Printf("There are total %d checksums of document contents\n", len(checksums))
|
||||
sortedChecksums := SortMapKeyByValueInt(checksums)
|
||||
sortedChecksums = sortedChecksums[:20]
|
||||
fmt.Printf("The top 20 checksums are:\n")
|
||||
for _, key := range sortedChecksums {
|
||||
fmt.Printf("checksum %s apprears %d\n", key, checksums[key])
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
This binary takes as its input a json file including GKE logs (which can be
|
||||
[exported](https://cloud.google.com/logging/docs/export/configure_export_v2) into
|
||||
[Cloud Storage](https://cloud.google.com/storage/docs/)),
|
||||
and extracts the `textPayload` field of each log entry.
|
||||
|
||||
Here is an log entry example:
|
||||
|
||||
{"insertId":"1sxuh4jg5lw6w10","labels":{"compute.googleapis.com/resource_name":"gke-crawler2-default-pool-5e55ea05-gzgv","container.googleapis.com/namespace_name":"default","container.googleapis.com/pod_name":"kustomize-stats-5bczg","container.googleapis.com/stream":"stdout"},"logName":"projects/haiyanmeng-gke-dev/logs/kustomize-stats","receiveTimestamp":"2020-01-06T23:33:07.012831742Z","resource":{"labels":{"cluster_name":"crawler2","container_name":"kustomize-stats","instance_id":"8183086081854184383","namespace_id":"default","pod_id":"kustomize-stats-5bczg","project_id":"haiyanmeng-gke-dev","zone":"us-central1-a"},"type":"container"},"severity":"INFO","textPayload":"The kustomize index already exists\n","timestamp":"2020-01-06T23:32:46.628930547Z"}
|
||||
@@ -1,7 +0,0 @@
|
||||
wget <log-file-url> -O log
|
||||
go build .
|
||||
./log-parser log >out
|
||||
cat out | grep "kind \`" | cut -d\` -f2 | tail -n 50
|
||||
cat out | grep "kind \`" | awk '{print $6}' | tail -n 50
|
||||
cat out | grep "feature \`" | grep -v "\`resources\`" | grep -v -e "\`apiVersion\`" | grep -v -e "\`apiversion\`" | cut -d\` -f2
|
||||
cat out | grep "feature \`" | grep -v "\`resources\`" | grep -v -e "\`apiVersion\`" | grep -v -e "\`apiversion\`" | awk '{print $6}'
|
||||
@@ -1,49 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) != 2 {
|
||||
log.Fatalf("The usage of the command is: \n\t%s <log-file.json>", os.Args[0])
|
||||
}
|
||||
|
||||
file, err := os.Open(os.Args[1])
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
closeFile := func(file *os.File) {
|
||||
if err := file.Close(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
defer closeFile(file)
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
|
||||
var entry interface{}
|
||||
if err := json.Unmarshal([]byte(line), &entry); err != nil {
|
||||
log.Printf("failed to unmarshal a log entry: %s\n", line)
|
||||
}
|
||||
|
||||
m := entry.(map[string]interface{})
|
||||
if payload, ok := m["textPayload"]; ok {
|
||||
// use fmt.Printf here instead of log.Printf to avoid the time and code location info the log package provides
|
||||
fmt.Printf("%s", payload)
|
||||
} else {
|
||||
log.Printf("the log entry does not have the `textPayload` field: %s\n", line)
|
||||
}
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
configmapGenerator:
|
||||
- name: elasticsearch-config
|
||||
literals:
|
||||
- es-url="http://esbasic-master:9200"
|
||||
- plugin-index-name="plugin"
|
||||
@@ -1 +0,0 @@
|
||||
github_api_secret.txt
|
||||
@@ -1,2 +0,0 @@
|
||||
<ADD YOUR GITHUB PERSONAL ACCESS TOKEN HERE WITHOUT A TRAILING NEWLINE>
|
||||
Run: printf "<your-token>" > github_api_secret.txt
|
||||
@@ -1,15 +0,0 @@
|
||||
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
|
||||
@@ -1,34 +0,0 @@
|
||||
apiVersion: batch/v1beta1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: crawler-cronjob
|
||||
spec:
|
||||
# run the cronjob at 00:00 every 7 days
|
||||
schedule: "0 0 */7 * *"
|
||||
jobTemplate:
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
restartPolicy: OnFailure
|
||||
containers:
|
||||
- name: crawler
|
||||
image: gcr.io/haiyanmeng-gke-dev/crawler:v1
|
||||
command: ["/crawler"]
|
||||
args: ["--mode=index+github", "--github-repo=kubernetes-sigs/kustomize", "--index=kustomize"]
|
||||
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
|
||||
@@ -1,3 +0,0 @@
|
||||
resources:
|
||||
- ../base
|
||||
- cronjob.yaml
|
||||
@@ -1,53 +0,0 @@
|
||||
The crawler job can run in one of the following mode:
|
||||
|
||||
# Crawling all the documents in the index and crawling all the kustomization files on Github
|
||||
|
||||
This is the default setting of the crawler job. The `command` and `args` field
|
||||
of the container should be:
|
||||
|
||||
```
|
||||
command: ["/crawler"]
|
||||
```
|
||||
|
||||
Or
|
||||
|
||||
```
|
||||
command: ["/crawler"]
|
||||
args: ["--mode=index+github"]
|
||||
```
|
||||
|
||||
# Crawling all the documents in the index
|
||||
|
||||
The `command` and `args` field of the container should be:
|
||||
|
||||
```
|
||||
command: ["/crawler"]
|
||||
args: ["--mode=index"]
|
||||
```
|
||||
|
||||
# Crawling all the kustomization files on Github
|
||||
|
||||
The `command` and `args` field of the container should be:
|
||||
|
||||
```
|
||||
command: ["/crawler"]
|
||||
args: ["--mode=github"]
|
||||
```
|
||||
|
||||
# Crawling all the kustomization files in a Github repo
|
||||
|
||||
The `command` and `args` field of the container should be like:
|
||||
|
||||
```
|
||||
command: ["/crawler"]
|
||||
args: ["--mode=github-repo", "--github-repo=kubernetes-sigs/kustomize"]
|
||||
```
|
||||
|
||||
# Crawling all the kustomization files in all the repositories of a Github user
|
||||
|
||||
The `command` and `args` field of the container should be like:
|
||||
|
||||
```
|
||||
command: ["/crawler"]
|
||||
args: ["--github-user", "--github-user=kubernetes-sigs"]
|
||||
```
|
||||
@@ -1,35 +0,0 @@
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: crawler
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
restartPolicy: OnFailure
|
||||
containers:
|
||||
- name: crawler
|
||||
image: gcr.io/haiyanmeng-gke-dev/crawler:v1
|
||||
imagePullPolicy: Always
|
||||
command: ["/crawler"]
|
||||
args: ["--mode=github-repo", "--github-repo=kubernetes-sigs/kustomize", "--index=kustomize"]
|
||||
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
|
||||
@@ -1,3 +0,0 @@
|
||||
resources:
|
||||
- ../base
|
||||
- job.yaml
|
||||
@@ -1,20 +0,0 @@
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: kustomize-stats
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
restartPolicy: OnFailure
|
||||
containers:
|
||||
- name: kustomize-stats
|
||||
image: gcr.io/haiyanmeng-gke-dev/kustomize_stats:v1
|
||||
imagePullPolicy: Always
|
||||
command: ["/kustomize_stats"]
|
||||
args: ["--index=kustomize", "--kinds=51", "--identifiers=50", "--kustomize-features=50"]
|
||||
env:
|
||||
- name: ELASTICSEARCH_URL
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: elasticsearch-config
|
||||
key: es-url
|
||||
@@ -1,3 +0,0 @@
|
||||
resources:
|
||||
- ../base
|
||||
- job.yaml
|
||||
@@ -1,23 +0,0 @@
|
||||
# ESBackup depends on ESCluster, and is depended by ESSnapshot.
|
||||
# Creating `esbackup/kustomize-backbup` will create the `kustomize-backup` snapshot repository.
|
||||
# Deleting `esbackup/kustomize-backbup` will delete the `kustomize-backup` snapshot repository.
|
||||
# Deleting `esbackup/kustomize-backbup` will NOT delete the snapshots in the `kustomize-backup` snapshot repository, instead makes all the snapshots in the repository inaccessible.
|
||||
# Deleting `esbackup/kustomize-backbup` will NOT delete the essnapshot objects depending on it, but will cause those essnapshot objects to be reconciled, which update the status of the essnapshot objects to reflect the fact that the esbackup object is missing.
|
||||
# If you delete the `kustomize-backup` snapshot repository directly without deleting `esbackup/kustomize-backbup`, the ESBackup object will not recreate the snapshot repository.
|
||||
apiVersion: elasticsearch.cloud.google.com/v1alpha1
|
||||
kind: ESBackup
|
||||
metadata:
|
||||
name: kustomize-backup
|
||||
spec:
|
||||
storage:
|
||||
gcs:
|
||||
# the bucket must exist for the snapshot repository to be created successfully.
|
||||
bucket: kustomize-backup
|
||||
# the path does not need to exist.
|
||||
# If the path does not exist, the controller will create the folder in the GCS bucket.
|
||||
# If the path already exists and includes snapshots, these snapshots can be used.
|
||||
path: kustomize
|
||||
secret:
|
||||
name: kustomizesa
|
||||
escluster:
|
||||
name: esbasic
|
||||
@@ -1,51 +0,0 @@
|
||||
# ESCluster is depended by ESBackup and ESRestore.
|
||||
apiVersion: elasticsearch.cloud.google.com/v1alpha1
|
||||
kind: ESCluster
|
||||
metadata:
|
||||
name: esbasic
|
||||
spec:
|
||||
plugin:
|
||||
pluginList:
|
||||
- repository-gcs
|
||||
- ingest-user-agent
|
||||
- ingest-geoip
|
||||
# To set `gcpserviceaccount`,
|
||||
# First, create and download a GCP service account into a json file, named `sakey.json` following the instruction:
|
||||
# https://www.elastic.co/guide/en/elasticsearch/plugins/6.5/repository-gcs-usage.html#repository-gcs-using-service-account
|
||||
# Second, create a secret for the service account using the following command:
|
||||
# $ kubectl create secret generic kustomizesa --from-file=./sakey.json
|
||||
gcpserviceaccount:
|
||||
name: kustomizesa
|
||||
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"]'
|
||||
@@ -1,19 +0,0 @@
|
||||
# ESRestore depends on both ESCluster and ESSnapshot.
|
||||
# Creating `esrestore/kustomize-restore` will restore the `kuostmize` index in the `kustomize-snapshot` snapshot to a new index named `kusotmize-restore`.
|
||||
# Deleting `esrestore/kustomize-restore` will not delete the restored index.
|
||||
# Deleting `esrestore/kustomize-restore` should happen before deleting `essnapshot/kustomize-snapshot`.
|
||||
# After the restore is complete, if the `kusotmize-restore` index is deleted manually, the ESRestore object will NOT restore the `kustomize` index to it again.
|
||||
# The correct way of using ESRestore is: create a ESRestore object to restore the index; delete the ESRestore object after the restore is complete.
|
||||
apiVersion: elasticsearch.cloud.google.com/v1alpha1
|
||||
kind: ESRestore
|
||||
metadata:
|
||||
name: kustomize-restore
|
||||
spec:
|
||||
include_global_state: true
|
||||
ignore_unavailable: true
|
||||
rename_pattern: kustomize
|
||||
rename_replacement: kustomize-restore
|
||||
essnapshot:
|
||||
name: kustomize-snapshot
|
||||
escluster:
|
||||
name: esbasic
|
||||
@@ -1,23 +0,0 @@
|
||||
# ESSnapshot depends on ESBackup, and is depended by ESRestore.
|
||||
# Creating `essnapshot/kustomize-snapshot` will create a snapshot named `kustomize-snapshot` in the `kustomize-backup` snapshot repository.
|
||||
# After being created, the `kustomize-snapshot` snapshot will not be automatically updated when the `kuostomize` index is updated.
|
||||
# If you delete `essnapshot/kustomize-snapshot` and recreate it, the new snapshot will capture the current status of the `kustomize` index.
|
||||
# Deleting `essnapshot/kustomize-snapshot` will delete the snapshot.
|
||||
# Deleting `essnapshot/kustomize-snapshot` should happen before deleting `esbackup/kustomize-backup`.
|
||||
# If the `kustomize-snapshot` snapshot is deleted directly without deleting `essnapshot/kustomize-snapshot`, the ESSnapshot object will recreate the snapshot.
|
||||
# The correct way of using ESSnapshot is: create an ESSnapshot object to create a snapshot, keep the ESSnapshot object until the snapshot is no longer needed.
|
||||
# To update the snapshot to capture the latest version of the index, you can either:
|
||||
# 1) delete the snapshot, and wait for the ESSnapshot object to recreate the snapshot;
|
||||
# 2) delete the ESSnapshot object, and recreate the ESSnapshot object.
|
||||
apiVersion: elasticsearch.cloud.google.com/v1alpha1
|
||||
kind: ESSnapshot
|
||||
metadata:
|
||||
name: kustomize-snapshot
|
||||
spec:
|
||||
# indices are optional. If not specified all indices are selected.
|
||||
indices:
|
||||
- kustomize
|
||||
include_global_state: true
|
||||
ignore_unavailable: true
|
||||
esbackup:
|
||||
name: kustomize-backup
|
||||
@@ -1,7 +0,0 @@
|
||||
resources:
|
||||
- redis.yaml
|
||||
- service.yaml
|
||||
|
||||
commonLabels:
|
||||
app: redis
|
||||
tier: document-keystore
|
||||
@@ -1,37 +0,0 @@
|
||||
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
|
||||
@@ -1,10 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: redis-docs-keystore
|
||||
spec:
|
||||
clusterIP: None
|
||||
ports:
|
||||
- protocol: "TCP"
|
||||
port: 6379
|
||||
targetPort: redis-docs-port
|
||||
@@ -1,7 +0,0 @@
|
||||
resources:
|
||||
- redis.yaml
|
||||
- service.yaml
|
||||
|
||||
commonLabels:
|
||||
app: redis
|
||||
tier: http-cache
|
||||
@@ -1,16 +0,0 @@
|
||||
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
|
||||
@@ -1,10 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: redis-http-cache
|
||||
spec:
|
||||
clusterIP: None
|
||||
ports:
|
||||
- protocol: "TCP"
|
||||
port: 6379
|
||||
targetPort: http-cache-port
|
||||
@@ -1,35 +0,0 @@
|
||||
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"
|
||||
@@ -1,4 +0,0 @@
|
||||
resources:
|
||||
- ../../base
|
||||
- deployment.yaml
|
||||
- service.yaml
|
||||
@@ -1,14 +0,0 @@
|
||||
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: ""
|
||||
@@ -1,23 +0,0 @@
|
||||
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
|
||||
@@ -1,4 +0,0 @@
|
||||
resources:
|
||||
- ../../base
|
||||
- deployment.yaml
|
||||
- service.yaml
|
||||
@@ -1,14 +0,0 @@
|
||||
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: ""
|
||||
@@ -1,365 +0,0 @@
|
||||
// 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"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/internal/crawl/utils"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/internal/crawl/index"
|
||||
|
||||
_ "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, seen utils.SeenMap) 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
|
||||
|
||||
SetDefaultBranch(*doc.Document)
|
||||
|
||||
Match(*doc.Document) bool
|
||||
}
|
||||
|
||||
type CrawledDocument interface {
|
||||
ID() string
|
||||
GetDocument() *doc.Document
|
||||
// Get all the Documents directly referred in a Document.
|
||||
// For a Document representing a non-kustomization file, an empty slice will be returned.
|
||||
// For a Document representing a kustomization file:
|
||||
// the `includeResources` parameter determines whether the documents referred in the `resources` field are returned or not;
|
||||
// the `includeTransformers` parameter determines whether the documents referred in the `transformers` field are returned or not;
|
||||
// the `includeGenerators` parameter determines whether the documents referred in the `generators` field are returned or not.
|
||||
GetResources(includeResources, includeTransformers, includeGenerators bool) ([]*doc.Document, error)
|
||||
WasCached() bool
|
||||
}
|
||||
|
||||
type CrawlSeed []*doc.Document
|
||||
|
||||
type IndexFunc func(CrawledDocument, index.Mode) 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 utils.SeenMap, stack *CrawlSeed) {
|
||||
|
||||
seen.Set(cdoc.ID(), cdoc.GetDocument().FileType)
|
||||
|
||||
match.SetDefaultBranch(cdoc.GetDocument())
|
||||
|
||||
// Insert into index
|
||||
if err := indx(cdoc, index.InsertOrUpdate); err != nil {
|
||||
logger.Printf("Failed to insert or update doc(%s): %v",
|
||||
cdoc.GetDocument().Path(), err)
|
||||
return
|
||||
}
|
||||
|
||||
deps, err := cdoc.GetResources(true, true, true)
|
||||
if err != nil {
|
||||
logger.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, dep := range deps {
|
||||
if seen.Seen(dep.ID()) && seen.Value(dep.ID()) == dep.FileType {
|
||||
continue
|
||||
}
|
||||
*stack = append(*stack, dep)
|
||||
}
|
||||
}
|
||||
|
||||
func doCrawl(ctx context.Context, docsPtr *CrawlSeed, crawlers []Crawler, conv Converter, indx IndexFunc,
|
||||
seen utils.SeenMap, stack *CrawlSeed, refreshDoc bool, updateFileType bool) {
|
||||
|
||||
UpdatedDocCount := 0
|
||||
seenDocCount := 0
|
||||
cachedDocCount := 0
|
||||
findMatchErrCount := 0
|
||||
FetchDocumentErrCount := 0
|
||||
SetCreatedErrCount := 0
|
||||
convErrCount := 0
|
||||
deleteDocCount := 0
|
||||
crawledDocCount := 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)]
|
||||
|
||||
crawledDocCount++
|
||||
logger.Printf("Crawling doc %d: %s", crawledDocCount, tail.Path())
|
||||
|
||||
if seen.Seen(tail.ID()) {
|
||||
if !updateFileType || seen.Value(tail.ID()) == tail.FileType {
|
||||
logger.Printf("this doc has been seen before")
|
||||
seenDocCount++
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if tail.WasCached() {
|
||||
logger.Printf("doc(%s) is cached already", tail.Path())
|
||||
cachedDocCount++
|
||||
continue
|
||||
}
|
||||
|
||||
match := findMatch(tail, crawlers)
|
||||
if match == nil {
|
||||
logIfErr(fmt.Errorf("%v could not match any crawler", tail))
|
||||
findMatchErrCount++
|
||||
continue
|
||||
}
|
||||
|
||||
if tail.User == "" {
|
||||
tail.User = doc.UserName(tail.RepositoryURL)
|
||||
}
|
||||
|
||||
// If the Document represents a kustomization root, FetchDcoument will change
|
||||
// the `filePath` field of the Document by adding `kustomization.yaml` or
|
||||
// `kustomization.yml` or `kustomization` into the the field.
|
||||
// Therefore, it is necessary to add the ID of the Document into seen before
|
||||
// calling FetchDocument. Otherwise, the binary may enter into an infinite loop
|
||||
// if a kustomization file points to its kustmozation root in its `resources` or
|
||||
// `bases` field.
|
||||
seen.Set(tail.ID(), tail.FileType)
|
||||
|
||||
if refreshDoc || tail.DefaultBranch == "" {
|
||||
match.SetDefaultBranch(tail)
|
||||
}
|
||||
|
||||
if refreshDoc || tail.DocumentData == "" {
|
||||
if err := match.FetchDocument(ctx, tail); err != nil {
|
||||
logger.Printf("FetchDocument failed on doc(%s): %v", tail.Path(), err)
|
||||
FetchDocumentErrCount++
|
||||
// delete the document from the index
|
||||
cdoc := &doc.KustomizationDocument{
|
||||
Document: *tail,
|
||||
}
|
||||
seen.Set(cdoc.ID(), tail.FileType)
|
||||
if err := indx(cdoc, index.Delete); err != nil {
|
||||
logger.Printf("Failed to delete doc(%s): %v", cdoc.Path(), err)
|
||||
}
|
||||
deleteDocCount++
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if refreshDoc || tail.CreationTime == nil {
|
||||
if err := match.SetCreated(ctx, tail); err != nil {
|
||||
logger.Printf("SetCreated failed on doc(%s): %v", tail.Path(), err)
|
||||
SetCreatedErrCount++
|
||||
}
|
||||
}
|
||||
|
||||
cdoc, err := conv(tail)
|
||||
// If conv returns an error, cdoc can still be added into the index so that
|
||||
// cdoc.Document can be searched.
|
||||
if err != nil {
|
||||
logger.Printf("conv failed on doc(%s): %v", tail.Path(), err)
|
||||
convErrCount++
|
||||
}
|
||||
|
||||
UpdatedDocCount++
|
||||
addBranches(cdoc, match, indx, seen, stack)
|
||||
}
|
||||
logger.Printf("Summary of doCrawl:\n")
|
||||
logger.Printf("\t%d documents were updated\n", UpdatedDocCount)
|
||||
logger.Printf("\t%d documents were seen by the crawler already and skipped\n", seenDocCount)
|
||||
logger.Printf("\t%d documents were cached already and skipped\n", cachedDocCount)
|
||||
logger.Printf("\t%d documents didn't have a matching crawler and skipped\n", findMatchErrCount)
|
||||
logger.Printf("\t%d documents cannot be fetched, %d out of them are deleted\n",
|
||||
FetchDocumentErrCount, deleteDocCount)
|
||||
logger.Printf("\t%d documents cannot update its creation time but still were inserted or updated in the index\n", SetCreatedErrCount)
|
||||
logger.Printf("\t%d documents cannot be converted but still were inserted or updated in the index\n", convErrCount)
|
||||
}
|
||||
|
||||
// CrawlFromSeedIterator iterates all the documents in the index and call CrawlFromSeed for each document.
|
||||
func CrawlFromSeedIterator(ctx context.Context, it *index.KustomizeIterator, crawlers []Crawler,
|
||||
conv Converter, indx IndexFunc, seen utils.SeenMap) {
|
||||
docCount := 0
|
||||
for it.Next() {
|
||||
for _, hit := range it.Value().Hits.Hits {
|
||||
docCount++
|
||||
logger.Printf("updating document %d from seed\n", docCount)
|
||||
|
||||
singleSeed := CrawlSeed{&(hit.Document.Document)}
|
||||
CrawlFromSeed(ctx, singleSeed, crawlers, conv, indx, seen)
|
||||
}
|
||||
}
|
||||
if err := it.Err(); err != nil {
|
||||
log.Fatalf("Error iterating the index: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 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 utils.SeenMap) {
|
||||
|
||||
// stack tracks the documents directly referred in the seed.
|
||||
stack := make(CrawlSeed, 0)
|
||||
|
||||
// each unique document in seed will be crawled once.
|
||||
doCrawl(ctx, &seed, crawlers, conv, indx, seen, &stack, true, false)
|
||||
|
||||
logger.Printf("crawling %d new documents referred by doc\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, false, true)
|
||||
}
|
||||
|
||||
// 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, seen utils.SeenMap) []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, seen)
|
||||
}(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 utils.SeenMap) {
|
||||
|
||||
// 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()
|
||||
docCount := 0
|
||||
for cdoc := range ch {
|
||||
docCount++
|
||||
logger.Printf("Processing doc %d found on Github", docCount)
|
||||
// all the docs here are kustomization files found by querying Github, and
|
||||
// their `FileType` fields all should be empty.
|
||||
if seen.Seen(cdoc.ID()) {
|
||||
logger.Printf("the doc has been seen before")
|
||||
continue
|
||||
}
|
||||
match := findMatch(cdoc.GetDocument(), crawlers)
|
||||
if match == nil {
|
||||
logIfErr(fmt.Errorf(
|
||||
"%v could not match any crawler", cdoc))
|
||||
continue
|
||||
}
|
||||
|
||||
// stack tracks the documents directly referred in the document.
|
||||
stack := make(CrawlSeed, 0)
|
||||
|
||||
addBranches(cdoc, match, indx, seen, &stack)
|
||||
|
||||
if len(stack) > 0 {
|
||||
// here the documents referred in a kustomization file are crawled separately,
|
||||
// to avoid accumulating all the referred documents into a single gigantic
|
||||
// mem-inentive stack.
|
||||
logger.Printf("crawling the %d new documents referred in doc %d",
|
||||
len(stack), docCount)
|
||||
doCrawl(ctx, &stack, crawlers, conv, indx, seen, &stack, false, true)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
logger.Println("processing the documents found from crawling github")
|
||||
if errs := CrawlGithubRunner(ctx, ch, crawlers, seen); errs != nil {
|
||||
for _, err := range errs {
|
||||
logIfErr(err)
|
||||
}
|
||||
}
|
||||
close(ch)
|
||||
wg.Wait()
|
||||
}
|
||||
@@ -1,343 +0,0 @@
|
||||
package crawler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/internal/crawl/utils"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/internal/crawl/index"
|
||||
|
||||
"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) SetDefaultBranch(d *doc.Document) {}
|
||||
|
||||
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, _ utils.SeenMap) 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) {
|
||||
log.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()
|
||||
|
||||
seen := utils.NewSeenMap()
|
||||
errs := CrawlGithubRunner(context.Background(),
|
||||
output, test.tc, seen)
|
||||
|
||||
// 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) {
|
||||
log.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, mode index.Mode) error {
|
||||
visited[d.ID()]++
|
||||
return nil
|
||||
},
|
||||
utils.NewSeenMap(),
|
||||
)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,768 +0,0 @@
|
||||
// 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/utils"
|
||||
|
||||
"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
|
||||
// branchMap maps github repositories to their default branches
|
||||
branchMap map[string]string
|
||||
}
|
||||
|
||||
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,
|
||||
branchMap: map[string]string{},
|
||||
}
|
||||
}
|
||||
|
||||
func (gc githubCrawler) SetDefaultBranch(d *doc.Document) {
|
||||
url := gc.client.ReposRequest(d.RepositoryFullName())
|
||||
defaultBranch, err := gc.client.GetDefaultBranch(url, d.RepositoryURL, gc.branchMap)
|
||||
if err != nil {
|
||||
logger.Printf(
|
||||
"(error: %v) setting default_branch to master\n", err)
|
||||
defaultBranch = "master"
|
||||
}
|
||||
d.DefaultBranch = defaultBranch
|
||||
gc.branchMap[d.RepositoryURL] = d.DefaultBranch
|
||||
}
|
||||
|
||||
func (gc githubCrawler) DefaultBranch(repo string) string {
|
||||
return gc.branchMap[repo]
|
||||
}
|
||||
|
||||
// Implements crawler.Crawler.
|
||||
func (gc githubCrawler) Crawl(ctx context.Context,
|
||||
output chan<- crawler.CrawledDocument, seen utils.SeenMap) error {
|
||||
|
||||
ranges := []RangeWithin{
|
||||
RangeWithin{
|
||||
start: uint64(0),
|
||||
end: githubMaxFileSize,
|
||||
},
|
||||
}
|
||||
|
||||
errs := make(multiError, 0)
|
||||
for len(ranges) > 0 {
|
||||
logger.Printf("Current ranges: %v (len: %d)\n", ranges, len(ranges))
|
||||
tailRange := ranges[len(ranges)-1]
|
||||
ranges = ranges[:(len(ranges) - 1)]
|
||||
reProcessQueryRanges, err := gc.CrawlSingleRange(ctx, output, seen, tailRange.start, tailRange.end)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
ranges = append(ranges, reProcessQueryRanges...)
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gc githubCrawler) CrawlSingleRange(ctx context.Context,
|
||||
output chan<- crawler.CrawledDocument, seen utils.SeenMap,
|
||||
lowerBound, upperBound uint64) ([]RangeWithin, error) {
|
||||
|
||||
log.Printf("CrawlSingleRange [%d, %d]", lowerBound, upperBound)
|
||||
|
||||
noETagClient := GhClient{
|
||||
RequestConfig: gc.client.RequestConfig,
|
||||
client: &http.Client{Timeout: gc.client.client.Timeout},
|
||||
retryCount: gc.client.retryCount,
|
||||
accessToken: gc.client.accessToken,
|
||||
}
|
||||
|
||||
var reProcessQueryRanges []RangeWithin
|
||||
|
||||
var ranges []string
|
||||
var err error
|
||||
// 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.
|
||||
for i := 0; i < 5; i++ {
|
||||
ranges, err = FindRangesForRepoSearch(newCache(noETagClient, gc.query),
|
||||
lowerBound, upperBound)
|
||||
if err == nil {
|
||||
logger.Printf("FindRangesForRepoSearch succeeded after %d retries", i)
|
||||
break
|
||||
} else {
|
||||
time.Sleep(time.Minute)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return reProcessQueryRanges, 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)
|
||||
queryResult := RangeQueryResult{}
|
||||
for _, query := range ranges {
|
||||
reProcessQuery, rangeResult, err := processQuery(ctx, gc.client, query, output, seen, gc.branchMap)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
queryResult.Add(rangeResult)
|
||||
if reProcessQuery {
|
||||
// if the size of a range is 0, such as [245, 245], and reProcessQuery is true,
|
||||
// it means that there are more than 1000 results for the query range.
|
||||
// Reprocessing the query range will not help because the GitHub Search API
|
||||
// only provides up to 1,000 results for each search.
|
||||
if RangeSizes(query).Size() == 0 {
|
||||
logger.Printf("range size is 0 includes more than 1000 results: %s", query)
|
||||
} else {
|
||||
reProcessQueryRanges = append(reProcessQueryRanges, RangeSizes(query))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.Printf("Summary of Crawl: %s", queryResult.String())
|
||||
|
||||
if len(errs) > 0 {
|
||||
return reProcessQueryRanges, errs
|
||||
}
|
||||
|
||||
return reProcessQueryRanges, nil
|
||||
}
|
||||
|
||||
// FetchDocument first tries to fetch the document with d.FilePath. If it fails,
|
||||
// it will try to add each string in konfig.RecognizedKustomizationFileNames() to
|
||||
// d.FilePath, and try to fetch the document again.
|
||||
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 resp == nil {
|
||||
return fmt.Errorf("empty http response (url: %s; path: %s), error: %v",
|
||||
url, path, err)
|
||||
}
|
||||
if err == nil && resp.StatusCode == http.StatusOK {
|
||||
d.IsSame = httpclient.FromCache(resp.Header)
|
||||
defer CloseResponseBody(resp)
|
||||
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, errGetRawUserContent := gc.client.GetRawUserContent(url)
|
||||
if err := handle(resp, errGetRawUserContent, ""); err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, file := range konfig.RecognizedKustomizationFileNames() {
|
||||
resp, errGetRawUserContent = gc.client.GetRawUserContent(url + "/" + file)
|
||||
if err = handle(resp, errGetRawUserContent, "/"+file); err == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
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")
|
||||
}
|
||||
|
||||
type RangeQueryResult struct {
|
||||
totalDocCnt uint64
|
||||
seenDocCnt uint64
|
||||
newDocCnt uint64
|
||||
errorCnt uint64
|
||||
}
|
||||
|
||||
func (r *RangeQueryResult) Add(other RangeQueryResult) {
|
||||
r.totalDocCnt += other.totalDocCnt
|
||||
r.newDocCnt += other.newDocCnt
|
||||
r.seenDocCnt += other.seenDocCnt
|
||||
r.errorCnt += other.errorCnt
|
||||
}
|
||||
|
||||
func (r *RangeQueryResult) String() string {
|
||||
return fmt.Sprintf("got %d files from API. "+
|
||||
"%d have been seen before. %d are new and sent to the output channel."+
|
||||
" %d have kustomizationResultAdapter errors.",
|
||||
r.totalDocCnt, r.seenDocCnt, r.newDocCnt, r.errorCnt)
|
||||
}
|
||||
|
||||
// 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, seen utils.SeenMap,
|
||||
branchMap map[string]string) (bool, RangeQueryResult, 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)
|
||||
}()
|
||||
|
||||
reProcessQuery := false
|
||||
|
||||
errs := make(multiError, 0)
|
||||
result := RangeQueryResult{}
|
||||
pageID := 1
|
||||
for page := range queryPages {
|
||||
if page.Error != nil {
|
||||
errs = append(errs, page.Error)
|
||||
continue
|
||||
}
|
||||
pageResult := RangeQueryResult{}
|
||||
for _, file := range page.Parsed.Items {
|
||||
k, err := kustomizationResultAdapter(gcl, file, seen, branchMap)
|
||||
if err != nil {
|
||||
logger.Printf("kustomizationResultAdapter failed: %v", err)
|
||||
errs = append(errs, err)
|
||||
pageResult.errorCnt++
|
||||
}
|
||||
if k != nil {
|
||||
pageResult.newDocCnt++
|
||||
output <- k
|
||||
} else {
|
||||
pageResult.seenDocCnt++
|
||||
}
|
||||
pageResult.totalDocCnt++
|
||||
}
|
||||
|
||||
logger.Printf("processQuery [TotalCount %d - page %d]: %s",
|
||||
page.Parsed.TotalCount, pageID, pageResult.String())
|
||||
result.Add(pageResult)
|
||||
|
||||
pageID++
|
||||
|
||||
if page.Parsed.TotalCount > githubMaxResultsPerQuery {
|
||||
reProcessQuery = true
|
||||
}
|
||||
}
|
||||
|
||||
logger.Printf("Summary of processQuery: %s", result.String())
|
||||
|
||||
return reProcessQuery, result, errs
|
||||
}
|
||||
|
||||
func kustomizationResultAdapter(gcl GhClient, k GhFileSpec, seen utils.SeenMap,
|
||||
branchMap map[string]string) (crawler.CrawledDocument, error) {
|
||||
url := gcl.ReposRequest(k.Repository.FullName)
|
||||
defaultBranch, err := gcl.GetDefaultBranch(url, k.Repository.URL, branchMap)
|
||||
if err != nil {
|
||||
logger.Printf(
|
||||
"(error: %v) setting default_branch to master\n", err)
|
||||
defaultBranch = "master"
|
||||
}
|
||||
|
||||
// document here is a kustomization file found by querying Github, whose
|
||||
// `FileType` field should be empty.
|
||||
document := doc.Document{
|
||||
FilePath: k.Path,
|
||||
DefaultBranch: defaultBranch,
|
||||
RepositoryURL: k.Repository.URL,
|
||||
User: doc.UserName(k.Repository.URL),
|
||||
}
|
||||
|
||||
if seen.Seen(document.ID()) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
data, err := gcl.GetFileData(k)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d := doc.KustomizationDocument{
|
||||
Document: doc.Document{
|
||||
DocumentData: string(data),
|
||||
FilePath: k.Path,
|
||||
DefaultBranch: defaultBranch,
|
||||
RepositoryURL: k.Repository.URL,
|
||||
User: doc.UserName(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.parseGithubResponseWithRetry(query)
|
||||
|
||||
if response.Error != nil {
|
||||
return response.Error
|
||||
}
|
||||
|
||||
output <- response
|
||||
|
||||
for response.LastURL != "" && response.NextURL != "" {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
default:
|
||||
response = gcl.parseGithubResponseWithRetry(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)
|
||||
}
|
||||
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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 CloseResponseBody(resp)
|
||||
data, err = ioutil.ReadAll(resp.Body)
|
||||
return data, err
|
||||
}
|
||||
|
||||
func CloseResponseBody(resp *http.Response) {
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
log.Printf("failed to close response body: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// GetDefaultBranch gets the default branch of a github repository.
|
||||
// m is a map which maps a github repository to its default branch.
|
||||
// If repo is already in m, the default branch for url will be obtained from m;
|
||||
// otherwise, a query will be made to github to obtain the default branch.
|
||||
func (gcl GhClient) GetDefaultBranch(url, repo string, m map[string]string) (string, error) {
|
||||
if v, ok := m[repo]; ok {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
resp, err := gcl.GetReposData(url)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf(
|
||||
"'%s' could not get default_branch: %v", url, err)
|
||||
}
|
||||
defer CloseResponseBody(resp)
|
||||
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 CloseResponseBody(resp)
|
||||
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"`
|
||||
|
||||
IncompleteResults bool `json:"incomplete_results,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) parseGithubResponseWithRetry(getRequest string) GhResponseInfo {
|
||||
resp := gcl.parseGithubResponse(getRequest)
|
||||
retries := 0
|
||||
for resp.Parsed.IncompleteResults {
|
||||
resp = gcl.parseGithubResponse(getRequest)
|
||||
retries++
|
||||
}
|
||||
log.Printf("The result of query(%s) is complete after %d retries", getRequest, retries)
|
||||
return resp
|
||||
}
|
||||
|
||||
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 CloseResponseBody(resp)
|
||||
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))
|
||||
|
||||
// gcl.client.Do: a non-2xx status code doesn't cause an error.
|
||||
// See https://golang.org/pkg/net/http/#Client.Do for more info.
|
||||
resp, err := gcl.client.Do(req)
|
||||
if resp != nil && resp.StatusCode != http.StatusOK {
|
||||
err = fmt.Errorf("GhClient.Do(%s) failed with response code: %d",
|
||||
query, resp.StatusCode)
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (gcl GhClient) getWithRetry(
|
||||
query string) (resp *http.Response, err error) {
|
||||
|
||||
resp, err = gcl.Do(query)
|
||||
|
||||
retryCount := gcl.retryCount
|
||||
|
||||
for resp != nil && resp.StatusCode == http.StatusForbidden && retryCount > 0 {
|
||||
retryTime := resp.Header.Get("Retry-After")
|
||||
i, errAtoi := strconv.Atoi(retryTime)
|
||||
if errAtoi != 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
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user