mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-14 18:40:55 +00:00
Compare commits
1969 Commits
release-ap
...
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 | ||
|
|
3d17503329 | ||
|
|
7ac37867dc | ||
|
|
d8f2d2256d | ||
|
|
463b776486 | ||
|
|
d28ce28130 | ||
|
|
4f72faeecb | ||
|
|
29814b556b | ||
|
|
d94ed369fa | ||
|
|
0cf18987d7 | ||
|
|
a6374db2cb | ||
|
|
e98eada736 | ||
|
|
8a65ece956 | ||
|
|
4cdc3b0bad | ||
|
|
40bf89abcd | ||
|
|
7f548eddd0 | ||
|
|
86e9983bb7 | ||
|
|
cbbcfde99d | ||
|
|
304a9e57ee | ||
|
|
f23f26aa05 | ||
|
|
720857623f | ||
|
|
065c2b861a | ||
|
|
2a16af80bf | ||
|
|
81d324c68c | ||
|
|
b8702561ef | ||
|
|
ea039b36bc | ||
|
|
561cef1d5c | ||
|
|
62c5e424a6 | ||
|
|
45b1bf17d3 | ||
|
|
11dce34407 | ||
|
|
550a89295a | ||
|
|
8083b3607f | ||
|
|
cb42142161 | ||
|
|
cb59e0ef5f | ||
|
|
1a4a9fcdaf | ||
|
|
eb8dc5e20a | ||
|
|
0fb30a1010 | ||
|
|
fdfdfa9e4d | ||
|
|
6042aca7a4 | ||
|
|
94962c8bac | ||
|
|
f6ddea435c | ||
|
|
a9d4b7615f | ||
|
|
822cac26f9 | ||
|
|
97eedc8a43 | ||
|
|
2cb972de3b | ||
|
|
79d0d6b5e1 | ||
|
|
fabaf35c72 | ||
|
|
e13f8803eb | ||
|
|
64ffbcb15d | ||
|
|
b41df2293b | ||
|
|
e3fcec122a | ||
|
|
1edf9b630c | ||
|
|
7c6bf2e21d | ||
|
|
b3fc306f6a | ||
|
|
e92d048af2 | ||
|
|
f76059b824 | ||
|
|
bb41d018b5 | ||
|
|
cf8815b0a0 | ||
|
|
64beee22e9 | ||
|
|
79afd219a5 | ||
|
|
c68cf40d75 | ||
|
|
c7337a7d87 | ||
|
|
875e265e5d | ||
|
|
bdbfb28139 | ||
|
|
d54bc674f2 | ||
|
|
bd4580d73a | ||
|
|
ea5d08bac5 | ||
|
|
14a1a0e4a8 | ||
|
|
497e8038a3 | ||
|
|
44b5acad51 | ||
|
|
e5e19f7c09 | ||
|
|
a03843dfc7 | ||
|
|
b7cce27d40 | ||
|
|
126f5481f3 | ||
|
|
30dcf38609 | ||
|
|
1a2779b2c3 | ||
|
|
658b62c6f1 | ||
|
|
cf0bb49610 | ||
|
|
c2fbb709da | ||
|
|
1a002005c1 | ||
|
|
4f468fcc90 | ||
|
|
769f65d6c4 | ||
|
|
378eaedc82 | ||
|
|
6f2f401f6b | ||
|
|
614e853db3 | ||
|
|
33be04db45 | ||
|
|
8c6a9f6495 | ||
|
|
03b2fff0ee | ||
|
|
69cade143f | ||
|
|
90f45651d1 | ||
|
|
1b740034f7 | ||
|
|
a2d8e686de | ||
|
|
ce2ab487a5 | ||
|
|
7439f1809e | ||
|
|
6977c83a83 | ||
|
|
7b9eb05058 | ||
|
|
e2806a09fd | ||
|
|
f30fea4c07 | ||
|
|
8732671919 | ||
|
|
07cada36fa | ||
|
|
0d6b232b49 | ||
|
|
61455fe489 | ||
|
|
c63ed033ad | ||
|
|
455bd0c563 | ||
|
|
9ad4b1ddca | ||
|
|
d529eb8777 | ||
|
|
f7b2f0c067 | ||
|
|
ff6b337ebe | ||
|
|
76f05f3a40 | ||
|
|
d181a73f56 | ||
|
|
979fe3b457 | ||
|
|
bec45093e2 | ||
|
|
c113c41c9c | ||
|
|
99c9edfc3d | ||
|
|
cde6a5741e | ||
|
|
a315eb56ec | ||
|
|
90654b39bf | ||
|
|
24c4c66403 | ||
|
|
2b5029952c | ||
|
|
6a3bb5df44 | ||
|
|
d9623ab307 | ||
|
|
dd1df5a30e | ||
|
|
0e13a9c02b | ||
|
|
eb26d79fa0 | ||
|
|
7f8da385c0 | ||
|
|
1426137883 | ||
|
|
d90d77cdaf | ||
|
|
1ffd790cfb | ||
|
|
454906d093 | ||
|
|
8b97274af3 | ||
|
|
b1056b43cb | ||
|
|
e411942a74 | ||
|
|
6b30b72ebc | ||
|
|
9ddf0fe304 | ||
|
|
8a952a1b26 | ||
|
|
fff484e98b | ||
|
|
e819a2ba9d | ||
|
|
4908654c09 | ||
|
|
586515ebc4 | ||
|
|
735befef19 | ||
|
|
35087ed0cc | ||
|
|
f84f8f28fd | ||
|
|
45e118458c | ||
|
|
1102153ae3 | ||
|
|
338910d36c | ||
|
|
38e9770f40 | ||
|
|
20a4153893 | ||
|
|
51fba009b3 | ||
|
|
002215d719 | ||
|
|
f9d1e800e4 | ||
|
|
82db6cd73d | ||
|
|
3c25584658 | ||
|
|
c32a809dbd | ||
|
|
02be687778 | ||
|
|
c0a754e7b0 | ||
|
|
63e441a673 | ||
|
|
bd27d5f8bb | ||
|
|
0aa250c6e2 | ||
|
|
e269ad4a80 | ||
|
|
9db6b37b88 | ||
|
|
2361b70967 | ||
|
|
d016326877 | ||
|
|
67e445c10b | ||
|
|
76970a6b05 | ||
|
|
66db1df79a | ||
|
|
6442047e52 | ||
|
|
8ac6954de1 | ||
|
|
494977b9d0 | ||
|
|
0a0a6e1018 | ||
|
|
fa69d4ba9d | ||
|
|
36bdcca735 | ||
|
|
c71a4534a0 | ||
|
|
97402f1136 | ||
|
|
1329afa3ca | ||
|
|
60c8f4c594 | ||
|
|
428e25b856 | ||
|
|
8dd6f2b185 | ||
|
|
a3bf3ba608 | ||
|
|
b97e59c57e | ||
|
|
bae3228557 | ||
|
|
cc43a2d732 | ||
|
|
d25e1effb7 | ||
|
|
485b8121d3 | ||
|
|
401118728a | ||
|
|
0f45bd9583 | ||
|
|
97e4353755 | ||
|
|
826b5d9792 | ||
|
|
8c9da15156 | ||
|
|
e18d619c9e | ||
|
|
f8a3c04286 | ||
|
|
1a4c82241a | ||
|
|
81ca271e62 | ||
|
|
72262c5e71 | ||
|
|
125762d94d | ||
|
|
0ecbd5905b | ||
|
|
cb2b376065 | ||
|
|
595e59b919 | ||
|
|
6bbc829593 | ||
|
|
3dd9c6c0b5 | ||
|
|
c04c13a12a | ||
|
|
05eccab823 | ||
|
|
dd946e1343 | ||
|
|
b4ad4b6984 | ||
|
|
13dee68d3b | ||
|
|
e849b160bc | ||
|
|
87987d3382 | ||
|
|
575b4efc18 | ||
|
|
b1a460985c | ||
|
|
e1fd74bb61 | ||
|
|
8a673b82bd | ||
|
|
d2e995b3e2 | ||
|
|
f2e025ea53 | ||
|
|
dade85e40e | ||
|
|
51ba54ad82 | ||
|
|
4144775a3b | ||
|
|
ff9b215ae7 | ||
|
|
cd5ae17335 | ||
|
|
e3d022325b | ||
|
|
e6dc03bea4 | ||
|
|
d08b9c30ee | ||
|
|
f6e5eedee2 | ||
|
|
212b2cff12 | ||
|
|
f66e5bb923 | ||
|
|
23bc91f233 | ||
|
|
9c421c7410 | ||
|
|
c63dfd6772 | ||
|
|
1a5aa63d54 | ||
|
|
1583cef8d9 | ||
|
|
a7c91c37e9 | ||
|
|
c1dca7fdb5 | ||
|
|
724bbe9452 | ||
|
|
deb2b21cbe | ||
|
|
709f499b44 | ||
|
|
dbaa2d6092 | ||
|
|
6445e03d1a | ||
|
|
19ff1b307e | ||
|
|
55f44a29c6 | ||
|
|
1f1873a6ed | ||
|
|
6e3c4ecc72 | ||
|
|
c37c7b6b2c | ||
|
|
42acdcc1d0 | ||
|
|
67db23b24b | ||
|
|
35a19fb8a9 | ||
|
|
a6c2e982f9 | ||
|
|
a053ff6907 | ||
|
|
64201c8352 | ||
|
|
d5ce26e423 | ||
|
|
09497f0830 | ||
|
|
32dd194aca | ||
|
|
4122787bd8 | ||
|
|
485cb3831e | ||
|
|
2ccb73a2a3 | ||
|
|
83377cf597 | ||
|
|
2bf73c60c3 | ||
|
|
4a55a07c14 | ||
|
|
5279ad904b | ||
|
|
736f110f04 | ||
|
|
c9ab1270fa | ||
|
|
bb5fc9086b | ||
|
|
e8c85456cc | ||
|
|
aa9a397808 | ||
|
|
60ea8de5f1 | ||
|
|
56c8df7b85 | ||
|
|
a51e4234c4 | ||
|
|
50cd01e5f7 | ||
|
|
42b082a6b1 | ||
|
|
c3cdd15769 | ||
|
|
c3a8b6f359 | ||
|
|
4af4483e12 | ||
|
|
8f7bcb9b16 | ||
|
|
f54d904766 | ||
|
|
ba91b6f79f | ||
|
|
851acafe32 | ||
|
|
14eac6020f | ||
|
|
e45d667b4d | ||
|
|
cbdf4a0e43 | ||
|
|
aed7e5aaf9 | ||
|
|
ec64ef705b | ||
|
|
1e0eb58bc9 | ||
|
|
fd2fe35863 | ||
|
|
ebdbd81ed8 | ||
|
|
c9d2ae6b49 | ||
|
|
e8c212a10f | ||
|
|
1eb378254a | ||
|
|
0d030e3095 | ||
|
|
f2706dce68 | ||
|
|
f8194bd3c2 | ||
|
|
eb5227680e | ||
|
|
27e89f271f | ||
|
|
626f2f9d1d | ||
|
|
ffda124ca5 | ||
|
|
fd677f635e | ||
|
|
14b1388c6a | ||
|
|
5248fd0cd9 | ||
|
|
b6ae9f80d3 | ||
|
|
2ad502d5a8 | ||
|
|
a5f3d5c823 | ||
|
|
c65875cacc | ||
|
|
82b2d83ede | ||
|
|
7e01aec5a4 | ||
|
|
9e8a84c5ff | ||
|
|
849d5c4a8d | ||
|
|
00352cbc58 | ||
|
|
b88e770b1d | ||
|
|
eda7a9f8d6 | ||
|
|
114b3f4b00 | ||
|
|
6cb339142d | ||
|
|
052a6b4e96 | ||
|
|
4add7eccd0 | ||
|
|
a0a89e1adc | ||
|
|
10fd4b4bbe | ||
|
|
e15ede037d | ||
|
|
32a2f5ffa9 | ||
|
|
4f74203f6c | ||
|
|
5b0cbcb5fb | ||
|
|
2f4c35347e | ||
|
|
be5db09db1 | ||
|
|
a6833bc4c0 | ||
|
|
2ae323bb26 | ||
|
|
7e74271071 | ||
|
|
260e093547 | ||
|
|
47b12fa3dc | ||
|
|
38801cd966 | ||
|
|
c49e177b9c | ||
|
|
486be07e22 | ||
|
|
a12db91a10 | ||
|
|
f7613631d1 | ||
|
|
8a77494c7d | ||
|
|
af25469c8b | ||
|
|
8c4841c28f | ||
|
|
b6a4dd446a | ||
|
|
606654756d | ||
|
|
97c18518ea | ||
|
|
2dca4ab987 | ||
|
|
712276176c | ||
|
|
c610e3a364 | ||
|
|
02e7589323 | ||
|
|
2ae180ca23 | ||
|
|
98900c43f7 | ||
|
|
02b14d919b | ||
|
|
bd0a699ff6 | ||
|
|
02b4b56c60 | ||
|
|
0cac05448b | ||
|
|
e1c3caeba6 | ||
|
|
a25429ae3b | ||
|
|
e557677fed | ||
|
|
59e0b593cf | ||
|
|
3fa906505c | ||
|
|
a823f3043f | ||
|
|
b2ba82a0bd | ||
|
|
a4f22cb84f | ||
|
|
22f41c789a | ||
|
|
966d0a054c | ||
|
|
97f3ac2da1 | ||
|
|
536b69e5dc | ||
|
|
c7aaa18d0c | ||
|
|
a45523bb95 | ||
|
|
376f59f0f6 | ||
|
|
34863346b0 | ||
|
|
9569ca93d9 | ||
|
|
09377ac19c | ||
|
|
a25300dfd6 | ||
|
|
50e9e90b17 | ||
|
|
e3ad472933 | ||
|
|
154f894774 | ||
|
|
4d7600d4ef | ||
|
|
0a0a0ccddc | ||
|
|
2278e01b7f | ||
|
|
abc88c56c4 | ||
|
|
eddd872eca | ||
|
|
febede115e | ||
|
|
a2087f07d4 | ||
|
|
8b9d8a266d | ||
|
|
5557e1ff3c | ||
|
|
1c009ca217 | ||
|
|
bdb59d2cd2 | ||
|
|
b61a115e76 | ||
|
|
66221d17d4 | ||
|
|
c83f256dbe | ||
|
|
fa3caaacee | ||
|
|
0a0a2ed586 | ||
|
|
aff1db13e0 | ||
|
|
ad092cc7a9 | ||
|
|
0a9fc6c8cb | ||
|
|
881d33ac5c | ||
|
|
842e4f5dc5 | ||
|
|
ef612286e4 | ||
|
|
636c9fcddf | ||
|
|
333945d361 | ||
|
|
afff3ce5ab | ||
|
|
71b763888c | ||
|
|
c5cd539b01 | ||
|
|
4b89c2afa2 | ||
|
|
630fc9b973 | ||
|
|
a468743b81 | ||
|
|
e9a74b87e3 | ||
|
|
64f8d2ae38 | ||
|
|
5ab320c216 | ||
|
|
6131f86d23 | ||
|
|
e7609559ce | ||
|
|
c2bdac7a6b | ||
|
|
4cc2c4f623 | ||
|
|
155c42679c | ||
|
|
88239445ce | ||
|
|
d66fc462ec | ||
|
|
6788af083b | ||
|
|
df0576a270 | ||
|
|
f4d8ebb1da | ||
|
|
0acac39640 | ||
|
|
65db82df0c | ||
|
|
68951bb37e | ||
|
|
b18910aa6d | ||
|
|
f780f7a3c2 | ||
|
|
7966386615 | ||
|
|
2130ba72cc | ||
|
|
94d26ba53a | ||
|
|
c53f31ca4f | ||
|
|
b58075cbc3 | ||
|
|
b3e82a2fe7 | ||
|
|
78c26f55b5 | ||
|
|
ec2a6e4e4b | ||
|
|
886f73aa0f | ||
|
|
73d91dda6e | ||
|
|
9f06376ab2 | ||
|
|
e785bab474 | ||
|
|
8f80a898b6 | ||
|
|
fe84d119d6 | ||
|
|
03b847a749 | ||
|
|
9d2f257acf | ||
|
|
129b25ceff | ||
|
|
57f4ea5354 | ||
|
|
ec2cc2d421 | ||
|
|
712eb6d276 | ||
|
|
a04e3a575c | ||
|
|
03e2fed925 | ||
|
|
c37b3b2525 | ||
|
|
ceeba8764f | ||
|
|
04d133a66f | ||
|
|
99aaa80e1d | ||
|
|
1f806b0aa2 | ||
|
|
1f697e3792 | ||
|
|
28cdcc2e46 | ||
|
|
c0ecd1d1ad | ||
|
|
3923c63182 | ||
|
|
9943e74187 | ||
|
|
3b504fa3e5 | ||
|
|
9fb25fc5a7 | ||
|
|
4d99217a7c | ||
|
|
0834e152b2 | ||
|
|
ff276af317 | ||
|
|
3b79944190 | ||
|
|
d8d57eae29 | ||
|
|
c803ca83a4 | ||
|
|
6bed275234 | ||
|
|
8e5df26e4c | ||
|
|
3fed68b694 | ||
|
|
0e59c36d03 | ||
|
|
00fdf71dc3 | ||
|
|
be327e7443 | ||
|
|
be8d2fe016 | ||
|
|
072ae36fe6 | ||
|
|
b5d8b8d258 | ||
|
|
e6b21174f1 | ||
|
|
49094cf999 | ||
|
|
d2c7db6ca0 | ||
|
|
d141f9b973 | ||
|
|
877da8da6d | ||
|
|
8596e63203 | ||
|
|
b2df55e9d7 | ||
|
|
6daf8f8820 | ||
|
|
e75d4fc87d | ||
|
|
9ae07634f2 | ||
|
|
981959ffcf | ||
|
|
dc31321b05 | ||
|
|
64dc3e14ff | ||
|
|
da0893bac0 | ||
|
|
c1747439cd | ||
|
|
f68986827b | ||
|
|
b736b81167 | ||
|
|
0a04b1bb78 | ||
|
|
f7ebaae39e | ||
|
|
08099f0cea | ||
|
|
6fd04dd253 | ||
|
|
9ac97ef91f | ||
|
|
cfbf426174 | ||
|
|
9aafc61c5b | ||
|
|
cd2ebd3046 | ||
|
|
b20e5d7f84 | ||
|
|
13c9a2873e | ||
|
|
fc06283905 | ||
|
|
119d7cadf5 | ||
|
|
cdc6d1fc28 | ||
|
|
49dced2e01 | ||
|
|
76a8f034cb | ||
|
|
52060ac480 | ||
|
|
719532e4df | ||
|
|
70dcc79bf4 | ||
|
|
55b4448862 | ||
|
|
bcaac6f8c1 | ||
|
|
ba4b44db6b | ||
|
|
fd280d0c0b | ||
|
|
1dbf490146 | ||
|
|
62e4df72d3 | ||
|
|
bb77a7c86d | ||
|
|
41abeb85be | ||
|
|
3c86d37148 | ||
|
|
287b38cc87 | ||
|
|
d8d727b1ca | ||
|
|
a81a3d40ce | ||
|
|
52e682489c | ||
|
|
8714ca5a58 | ||
|
|
0490ca163f | ||
|
|
b4947fe8a0 | ||
|
|
9c7b4fddf9 | ||
|
|
5d5b1c2c38 | ||
|
|
cf1aafb121 | ||
|
|
2d4e406a86 | ||
|
|
2a8edd2859 | ||
|
|
a3bc13847c | ||
|
|
944b19ff7c | ||
|
|
f75274bae7 | ||
|
|
62a8a8c57d | ||
|
|
9514f9cd3a | ||
|
|
c1c2725360 | ||
|
|
e9ff26bb1b | ||
|
|
14dc3dfb81 | ||
|
|
44619d5ca2 | ||
|
|
027b7d61ea | ||
|
|
a458ed84f9 | ||
|
|
108f44377d | ||
|
|
dc8439fbfa | ||
|
|
f5353fafa1 | ||
|
|
3d1376bbbc | ||
|
|
b1ea25e86a | ||
|
|
495f6df973 | ||
|
|
a4f1f0841e | ||
|
|
9d0fba81f0 | ||
|
|
92826c6a1e | ||
|
|
7e04be9ec6 | ||
|
|
d954c39ef7 | ||
|
|
176ac5b4fa | ||
|
|
dd696b5cb4 | ||
|
|
501404e403 | ||
|
|
ddf94175ee | ||
|
|
232da9e12b | ||
|
|
8b9ce8eacb | ||
|
|
ee9a4f2526 | ||
|
|
006ce72b2d | ||
|
|
a80bd15bda | ||
|
|
6c63bb2727 | ||
|
|
a7ba93b1d8 | ||
|
|
4a78cd6579 | ||
|
|
b2b8c12203 | ||
|
|
8cc281fad6 | ||
|
|
7346813b8d | ||
|
|
52f3aca22d | ||
|
|
a6a061215f | ||
|
|
7464d8ac8f | ||
|
|
64fda38e2d | ||
|
|
4cefb62d41 | ||
|
|
ccca424234 | ||
|
|
ca45907af0 | ||
|
|
dcf43c7f2b | ||
|
|
1386ec3850 | ||
|
|
de8e16df15 | ||
|
|
222b2d4485 | ||
|
|
e107020bd2 | ||
|
|
430665e984 | ||
|
|
017d5673ba | ||
|
|
f346b9803e | ||
|
|
58092bf66d | ||
|
|
747323efce | ||
|
|
7428e08f93 | ||
|
|
3c8e6d7151 | ||
|
|
43bd2f4cdb | ||
|
|
1dfc9a88a8 | ||
|
|
01beba8697 | ||
|
|
b1e01b238b | ||
|
|
1f595da9ad | ||
|
|
1cf876927d | ||
|
|
a422c935d8 | ||
|
|
1971816663 | ||
|
|
3d1eab872b | ||
|
|
10c1b0c5fa | ||
|
|
4d95cd3630 | ||
|
|
0c169e96e5 | ||
|
|
5baea8400f | ||
|
|
4052cd4fd8 | ||
|
|
21ac400d49 | ||
|
|
351a4a48e4 | ||
|
|
b3cf475024 | ||
|
|
ded25075b1 | ||
|
|
05b8671d17 | ||
|
|
1bfcc81f08 | ||
|
|
3d058830b9 | ||
|
|
bdb53fca9e | ||
|
|
b7265440f8 | ||
|
|
72e1a27177 | ||
|
|
0a1fde1e41 | ||
|
|
ae4618d327 | ||
|
|
aee6ccb05c | ||
|
|
3cd2a0a2f7 | ||
|
|
c96fa7c347 | ||
|
|
ad01d8d34e | ||
|
|
b214fa7d5a | ||
|
|
68f67c183e | ||
|
|
fe5c3a291f | ||
|
|
f38cc4446b | ||
|
|
e695b0534d | ||
|
|
51ecca8f2f | ||
|
|
460c54064c | ||
|
|
50c0200429 | ||
|
|
b1b5a95466 | ||
|
|
3f71c671df | ||
|
|
2ce9c02ada | ||
|
|
3ffc13dd6e | ||
|
|
2fb8603b2a | ||
|
|
1d4b3fa36c | ||
|
|
03ea8f3615 | ||
|
|
74d0d7960e | ||
|
|
ffed4c95b3 | ||
|
|
c59b393fa4 | ||
|
|
fafe24c9df | ||
|
|
d62d8dcade | ||
|
|
ae7f984c71 | ||
|
|
7c8c827a88 | ||
|
|
ff927fd11a | ||
|
|
a4f6fee6c8 | ||
|
|
dd8edb1b01 | ||
|
|
bb42d8aa1b | ||
|
|
bb60c29672 | ||
|
|
c93274c224 | ||
|
|
f5feffbd23 | ||
|
|
e17bab7e55 | ||
|
|
bd534441ce | ||
|
|
85f79edc97 | ||
|
|
4c48a4ff83 | ||
|
|
eb6c715bc3 | ||
|
|
1320e0c3dc | ||
|
|
df0022c985 | ||
|
|
15fc341a13 | ||
|
|
dd90c41f85 | ||
|
|
3a5951563d | ||
|
|
acdfd9a920 | ||
|
|
52016b22dd | ||
|
|
11049fa0bb | ||
|
|
db6c825c05 | ||
|
|
cb7974cf45 | ||
|
|
00111846d3 | ||
|
|
eafa37810b | ||
|
|
0c0cb9aaba | ||
|
|
b51e09d5fe | ||
|
|
c8cd5e55fc | ||
|
|
103d1461a1 | ||
|
|
f02af7a48b | ||
|
|
eba9edd7a6 | ||
|
|
cf38166bd6 | ||
|
|
03498b46b8 | ||
|
|
9fa9c6c30c | ||
|
|
884e35b4c8 | ||
|
|
28787396b2 | ||
|
|
e3cf8987e1 | ||
|
|
551841b789 | ||
|
|
66740dfad6 | ||
|
|
537ff024dd | ||
|
|
cfab28a5ff | ||
|
|
471d5ccf84 | ||
|
|
6a9e75ee0d | ||
|
|
7500764cbf | ||
|
|
544fc60bfe | ||
|
|
0850eae0b9 | ||
|
|
3818cebe33 | ||
|
|
31b395a33f | ||
|
|
ce0dba9217 | ||
|
|
9abd0119e1 | ||
|
|
b6c6cfa7ac | ||
|
|
0c5fc5e694 | ||
|
|
64cbfbe56d | ||
|
|
6a94eb873f | ||
|
|
0bfec6b39b | ||
|
|
9002c338cb | ||
|
|
f86cb6479e | ||
|
|
8285af8cf1 | ||
|
|
5fa1282dcb | ||
|
|
5a0abc8b12 | ||
|
|
3f2508fa94 | ||
|
|
9d992aae68 | ||
|
|
8c906b804f | ||
|
|
4ff4940fa7 | ||
|
|
09aec5694a | ||
|
|
1f917c0499 | ||
|
|
225ffc7cd8 | ||
|
|
eb638cc312 | ||
|
|
7dfb96425e | ||
|
|
6d4c6127c8 | ||
|
|
6aa72b66ef | ||
|
|
f03fdc09cb | ||
|
|
30b6eeb460 | ||
|
|
bf67fcb6d6 | ||
|
|
4ae420cce1 | ||
|
|
87d2187436 | ||
|
|
f1dabbd4fc | ||
|
|
747e05f2a4 | ||
|
|
3514317b3d | ||
|
|
9299821571 | ||
|
|
d91f313137 | ||
|
|
161af9d99c | ||
|
|
b115c95ea1 | ||
|
|
4c75dac10a | ||
|
|
2f8a376ae4 | ||
|
|
20cd4bfef9 | ||
|
|
b314ca185f | ||
|
|
f6c06b58ef | ||
|
|
c45e05b7bd | ||
|
|
76bae738a0 | ||
|
|
98c88805c3 | ||
|
|
0770661b2a | ||
|
|
67d5871e87 | ||
|
|
f98c683915 | ||
|
|
ffe9c9d947 | ||
|
|
4d42ffc7f8 | ||
|
|
d7dc7d911e | ||
|
|
04404ff61b | ||
|
|
f864e15c68 | ||
|
|
29a444fffc | ||
|
|
327035a43a | ||
|
|
ad7fed061e | ||
|
|
cea2986574 | ||
|
|
00f0fd7109 | ||
|
|
1c6481d011 | ||
|
|
f0bc926640 | ||
|
|
11d9ff5690 | ||
|
|
6a0a909e73 | ||
|
|
bd8f0c88e5 | ||
|
|
e5809e49cb | ||
|
|
880009b648 | ||
|
|
d083c7f1d0 | ||
|
|
684ce141de | ||
|
|
5c8c7a043a | ||
|
|
0fe7f65ef2 | ||
|
|
950c1de46d | ||
|
|
fc690f14a8 | ||
|
|
a6e03e4d11 | ||
|
|
60428be5fb | ||
|
|
4d402d4875 | ||
|
|
fbddd264be | ||
|
|
d3c46d3f7c | ||
|
|
dda3984a8f | ||
|
|
f889ca8885 | ||
|
|
341bacb9a2 | ||
|
|
badc1177d9 | ||
|
|
1680cc72c0 | ||
|
|
2f89de86f8 | ||
|
|
ab4e9c718b | ||
|
|
288c03ddca | ||
|
|
5c60285f25 | ||
|
|
6df0a45368 | ||
|
|
8206987580 | ||
|
|
6189ca9798 | ||
|
|
b8c1601a93 | ||
|
|
34d610a38d | ||
|
|
8e4c8464e7 | ||
|
|
43ab7a8e71 | ||
|
|
d2f23a4b8b | ||
|
|
0dc36a4f7c | ||
|
|
678ae12115 | ||
|
|
c4d937322f | ||
|
|
a2adb835b6 | ||
|
|
01b5c4e9da | ||
|
|
5a4e2c2898 | ||
|
|
51719d8089 | ||
|
|
eb4c5dc035 | ||
|
|
e976386931 | ||
|
|
bae9986422 | ||
|
|
e7970d82a8 | ||
|
|
9bdd489c96 | ||
|
|
0f49fef5ed | ||
|
|
8d74b8c3b5 | ||
|
|
39a8798a87 | ||
|
|
980f407552 | ||
|
|
9ca8f4602d | ||
|
|
ba0f583ee5 | ||
|
|
f432f4d75e | ||
|
|
17793abacd | ||
|
|
64cd4ec1d5 | ||
|
|
fb822984e3 | ||
|
|
6d2a737c29 | ||
|
|
6e7713281e | ||
|
|
6a7bb9e33e | ||
|
|
0e9428c8b0 | ||
|
|
c838962432 | ||
|
|
548d10ef08 | ||
|
|
2db8487f02 | ||
|
|
b42f71a20f | ||
|
|
e9824aa749 | ||
|
|
92cc9fc5e1 | ||
|
|
e53b4c9884 | ||
|
|
d4503dfd1e | ||
|
|
e2973f6ecc | ||
|
|
2bf9fc816d | ||
|
|
ff55856c63 | ||
|
|
ceef219eec | ||
|
|
b21699a277 | ||
|
|
2ab85d2f63 | ||
|
|
320545884c | ||
|
|
16bbc2d67e | ||
|
|
6d860e8ace | ||
|
|
80c8a6df61 | ||
|
|
90bc96d9d8 | ||
|
|
2be59aefec | ||
|
|
91b779269f | ||
|
|
e6ea4ad260 | ||
|
|
f5cab0f6e1 | ||
|
|
e39afc9f68 | ||
|
|
3801a29d9b | ||
|
|
39cf4af638 | ||
|
|
9d65dd0786 | ||
|
|
3dced70850 | ||
|
|
4356043582 | ||
|
|
c202be0338 | ||
|
|
9359155418 | ||
|
|
ba22bbe19e | ||
|
|
3e5989ae18 | ||
|
|
fbebd990a4 | ||
|
|
257707d839 | ||
|
|
c1cd872df6 | ||
|
|
646e0b4f61 | ||
|
|
0f67692265 | ||
|
|
6a7afd8694 | ||
|
|
46194b3385 | ||
|
|
904a9dea08 | ||
|
|
30b58e90a3 | ||
|
|
5bdd8657a5 | ||
|
|
893c99da1c | ||
|
|
43980f8586 | ||
|
|
0c8e033c96 | ||
|
|
fa15242719 | ||
|
|
a2e080bf6c | ||
|
|
e91cdb5eba | ||
|
|
ef54f9be5a | ||
|
|
f051acb83c | ||
|
|
bbb046081b | ||
|
|
77b28a986f | ||
|
|
97bc34eb37 | ||
|
|
719380f523 | ||
|
|
640ae9521b | ||
|
|
1dffc7577b | ||
|
|
a0b7288329 | ||
|
|
cc5617c048 | ||
|
|
a77d7e5164 | ||
|
|
40dc90b3b1 | ||
|
|
16229095b3 | ||
|
|
1d91401772 | ||
|
|
007a5327d7 | ||
|
|
24beeb429d | ||
|
|
9b4d4c9d46 | ||
|
|
d5f868c5c7 | ||
|
|
ff3f39d84b | ||
|
|
451c5c32c9 | ||
|
|
faef5714bf | ||
|
|
7833c6edcf | ||
|
|
a1cd23c91d | ||
|
|
e39a5adc00 | ||
|
|
b6900ead22 | ||
|
|
d03cf061e8 | ||
|
|
8293f3002d | ||
|
|
edced4b3f6 | ||
|
|
501684a9c6 | ||
|
|
4e42e1a058 | ||
|
|
b450b624e8 | ||
|
|
0be4a61f64 | ||
|
|
596c39b7bc | ||
|
|
037ac3b134 | ||
|
|
0ff4e53046 | ||
|
|
660f7f9435 | ||
|
|
e6ee03e3e3 | ||
|
|
ca04c874f2 | ||
|
|
cbfef858a0 | ||
|
|
62fbfdfa21 | ||
|
|
5d72fbc6c9 | ||
|
|
bc37ec9d88 | ||
|
|
8619c9aa13 | ||
|
|
5d8722a786 | ||
|
|
0d5552fca6 | ||
|
|
8a8e35f3bb | ||
|
|
25dbe1eaa8 | ||
|
|
2289e7d2e9 | ||
|
|
eb0f484e3d | ||
|
|
d438271263 | ||
|
|
c4c8decb74 | ||
|
|
0590b225c7 | ||
|
|
45131a6d62 | ||
|
|
4dfe3c6296 | ||
|
|
881f358228 | ||
|
|
86c93b9fb7 | ||
|
|
25e30de2d6 | ||
|
|
a8160356bd | ||
|
|
32de6de313 | ||
|
|
501ec38777 | ||
|
|
1b2a966c62 | ||
|
|
bcdbb1a282 | ||
|
|
01f28e6779 | ||
|
|
3f8e3686e2 | ||
|
|
b47e34ea5e | ||
|
|
762e587471 | ||
|
|
6f782ac8c3 | ||
|
|
e5bc644653 | ||
|
|
1bc9225302 | ||
|
|
99d7ad6dc9 | ||
|
|
345dbc83e3 | ||
|
|
8ddf2297e8 | ||
|
|
b407675fc0 | ||
|
|
fd5eeb1645 | ||
|
|
ff5051711f | ||
|
|
51268a5f06 | ||
|
|
1366e0344a | ||
|
|
9be38e815e | ||
|
|
88c318bf46 | ||
|
|
b71b36a213 | ||
|
|
e5a78710aa | ||
|
|
7ee75c33a9 | ||
|
|
2b328eeb36 | ||
|
|
4da40461d3 | ||
|
|
c96a4f3d73 | ||
|
|
45893b2588 | ||
|
|
2f3c89e73f | ||
|
|
eee581462c | ||
|
|
616363ee73 | ||
|
|
0e13eadd7a | ||
|
|
0c37388135 | ||
|
|
5d1352882b | ||
|
|
c469e80cad | ||
|
|
5559601ecb | ||
|
|
01b34c8ea0 | ||
|
|
eba0ffdde2 | ||
|
|
31c59bd7f2 | ||
|
|
bd7d0f864b | ||
|
|
2da8959198 | ||
|
|
dc591f0a10 | ||
|
|
c94f164e66 | ||
|
|
fb216d8af8 | ||
|
|
72207bfa04 | ||
|
|
fe3321d710 | ||
|
|
35b5890e46 | ||
|
|
ca807019f0 | ||
|
|
6420fc4911 | ||
|
|
bd8262630e | ||
|
|
cf5b26db8a | ||
|
|
4b4049e646 | ||
|
|
f211841035 | ||
|
|
efa4587f92 | ||
|
|
873c8c1d17 | ||
|
|
686e97f2fe | ||
|
|
669ae59982 | ||
|
|
868a226e4e | ||
|
|
a2693d0249 | ||
|
|
b7d913b58c | ||
|
|
f199b747e9 | ||
|
|
5a9fbf7da3 | ||
|
|
c18c803d3f | ||
|
|
d59d0401f4 | ||
|
|
83a70f7830 | ||
|
|
6afabf26ae | ||
|
|
2bcece5f1e | ||
|
|
e605391895 | ||
|
|
9de2c6b58e | ||
|
|
b3f147d012 | ||
|
|
fc70e3181f | ||
|
|
8cd7c13fad | ||
|
|
740ec39dd8 | ||
|
|
e6927a2fdf | ||
|
|
083dccfe91 | ||
|
|
b61553e584 | ||
|
|
8cdc97a0dd | ||
|
|
f205641498 | ||
|
|
bfaca2122a | ||
|
|
9482c571f0 | ||
|
|
1ad49de087 | ||
|
|
5e89565930 | ||
|
|
ef713e33ce | ||
|
|
2f7241f4c3 | ||
|
|
34e0ade3e7 | ||
|
|
436c688bd0 | ||
|
|
9d8fbd9f04 | ||
|
|
bb6fb703a0 | ||
|
|
c99bc47c8d | ||
|
|
60422c8090 | ||
|
|
fc83477ec8 | ||
|
|
c9e8631399 | ||
|
|
974e3847dd | ||
|
|
8f4e7e8072 | ||
|
|
4e74947731 | ||
|
|
43b0f2d925 | ||
|
|
efd5f414a8 | ||
|
|
9b8232533f | ||
|
|
bc5859d44b | ||
|
|
5c433ead5e | ||
|
|
feeaa994b7 | ||
|
|
17f935452f | ||
|
|
6faff2d031 | ||
|
|
18a86bd7d6 | ||
|
|
7eac250cf4 | ||
|
|
52083c6e49 | ||
|
|
6f63cf7238 | ||
|
|
de0c8dedc4 | ||
|
|
c23004df79 | ||
|
|
1a44c3c543 | ||
|
|
a9d1182322 | ||
|
|
a3d5628133 | ||
|
|
a563169461 | ||
|
|
729544b9f4 | ||
|
|
69d497ccdd | ||
|
|
c58c142849 | ||
|
|
9ba04e3f7d | ||
|
|
3f842e5e92 | ||
|
|
9fdb3e1e9e | ||
|
|
462dbcb999 | ||
|
|
3e0448f1b7 | ||
|
|
3c3f9a26f6 | ||
|
|
7d7c889285 | ||
|
|
4fe2f9dd5b | ||
|
|
0cb852b98a | ||
|
|
7f5ce3e6f0 | ||
|
|
bf5656b02b | ||
|
|
0ec901a9a9 | ||
|
|
af057a95c5 | ||
|
|
c48e584d1a | ||
|
|
2ee4eec791 | ||
|
|
f06a64e9cc | ||
|
|
51f9a84358 | ||
|
|
07c25eb458 | ||
|
|
ba57cdbd99 | ||
|
|
8a9dc011f4 | ||
|
|
fd196f5d70 | ||
|
|
4c577f6667 | ||
|
|
65fd7c3e6e | ||
|
|
1dff481883 | ||
|
|
d437f67035 | ||
|
|
ee57e9db12 | ||
|
|
a0fdcfe2e3 | ||
|
|
d9fe98a289 | ||
|
|
8b9829f222 | ||
|
|
2114b97969 | ||
|
|
508d193e7a | ||
|
|
e0eb79adcc | ||
|
|
dcab3cbb5f | ||
|
|
8225ca45a8 | ||
|
|
ef924a5c9c | ||
|
|
33b03fce89 | ||
|
|
3907643880 | ||
|
|
b7f7536cfa | ||
|
|
15bc399d5a | ||
|
|
46a6bf0bb4 | ||
|
|
6717bbd36b | ||
|
|
6fccb7fd48 | ||
|
|
370a3d2e74 | ||
|
|
eb7beba8ad | ||
|
|
1e3bc51645 | ||
|
|
92e1d452b7 | ||
|
|
7abedcf87b | ||
|
|
aa991956ef | ||
|
|
c6524f984c | ||
|
|
a70c6b3496 | ||
|
|
166c7f3167 | ||
|
|
10371aa1b5 | ||
|
|
95fb639fa8 | ||
|
|
0f7aae38e3 | ||
|
|
c660fd33ae | ||
|
|
108195185f | ||
|
|
4fbe565b36 | ||
|
|
e9bc2c00c1 | ||
|
|
45eed23b26 | ||
|
|
27b2c7f294 | ||
|
|
03d6229c0b | ||
|
|
71b7b00bd8 | ||
|
|
e9396dca2c | ||
|
|
bc581b70bf | ||
|
|
ac1c31c82b | ||
|
|
c878957d0b | ||
|
|
f9ee578aed | ||
|
|
0b359d0ef0 | ||
|
|
22ee7cbd49 | ||
|
|
7bf9c7002f | ||
|
|
155411f229 | ||
|
|
e894756003 |
8
.dockerignore
Normal file
8
.dockerignore
Normal file
@@ -0,0 +1,8 @@
|
||||
.github
|
||||
docs
|
||||
examples
|
||||
functions
|
||||
hack
|
||||
site
|
||||
travis
|
||||
*.md
|
||||
68
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
68
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ""
|
||||
labels:
|
||||
- kind/bug
|
||||
assignees: ""
|
||||
---
|
||||
|
||||
<!--
|
||||
Please read this page: https://kubernetes-sigs.github.io/kustomize/contributing/bugs/ before
|
||||
filing a bug
|
||||
-->
|
||||
|
||||
<!-- Feel free to skip the sections if they are not applicable. -->
|
||||
|
||||
**Describe the bug**
|
||||
|
||||
<!-- A clear and concise description of what the bug is. -->
|
||||
|
||||
**Files that can reproduce the issue**
|
||||
|
||||
<!--
|
||||
We cannot figure out or fix the issue if we don't know how to reproduce. Please
|
||||
provide a minimum set of files that can reproduce the issue. You can paste the
|
||||
file contents here or provide a link to a tarball or git repo.
|
||||
|
||||
Example:
|
||||
|
||||
kustomization.yaml
|
||||
|
||||
```
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
...
|
||||
```
|
||||
|
||||
resources.yaml
|
||||
|
||||
```
|
||||
apiVersion: v1
|
||||
kind: Deployment
|
||||
...
|
||||
```
|
||||
|
||||
...
|
||||
-->
|
||||
|
||||
**Expected output**
|
||||
|
||||
<!-- What's the expected output? -->
|
||||
|
||||
**Actual output**
|
||||
|
||||
<!-- What's the actual output? -->
|
||||
|
||||
**Kustomize version**
|
||||
|
||||
<!-- Please use the latest version when it's possible. -->
|
||||
|
||||
**Platform**
|
||||
|
||||
<!-- Linux/macOS/Windows? -->
|
||||
|
||||
**Additional context**
|
||||
|
||||
<!-- Add any other context about the problem here. -->
|
||||
1
.github/ISSUE_TEMPLATE/config.yaml
vendored
Normal file
1
.github/ISSUE_TEMPLATE/config.yaml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
blank_issues_enabled: true
|
||||
26
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
26
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ""
|
||||
labels:
|
||||
- kind/feature
|
||||
assignees: ""
|
||||
---
|
||||
|
||||
<!-- Feel free to skip the sections if they are not applicable. -->
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
|
||||
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
|
||||
|
||||
**Describe the solution you'd like**
|
||||
|
||||
<!-- A clear and concise description of what you want to happen. -->
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
|
||||
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
|
||||
|
||||
**Additional context**
|
||||
|
||||
<!-- Add any other context or screenshots about the feature request here. -->
|
||||
9
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
9
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
---
|
||||
name: Question
|
||||
about: Ask a question about the kustomize
|
||||
title: "[Question]"
|
||||
labels: ""
|
||||
assignees: ""
|
||||
---
|
||||
|
||||
<!-- Please describe your question here -->
|
||||
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: ./travis/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]
|
||||
|
||||
204
Makefile
204
Makefile
@@ -3,77 +3,93 @@
|
||||
#
|
||||
# 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.
|
||||
ifndef REPO_OWNER
|
||||
REPO_OWNER := "kubernetes-sigs"
|
||||
endif
|
||||
|
||||
ifndef REPO_NAME
|
||||
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.7.0
|
||||
test-examples-kustomize-against-HEAD \
|
||||
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-examples-kustomize-against-HEAD \
|
||||
test-unit-cmd-all \
|
||||
test-go-mod
|
||||
# test-examples-kustomize-against-3.7.0 \
|
||||
test-go-mod \
|
||||
test-examples-kustomize-against-HEAD \
|
||||
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
|
||||
|
||||
# Version pinned by api/go.mod
|
||||
$(MYGOBIN)/mdrip:
|
||||
cd api; \
|
||||
go install github.com/monopole/mdrip
|
||||
go install github.com/monopole/mdrip@v1.0.2
|
||||
|
||||
# Version pinned by api/go.mod
|
||||
$(MYGOBIN)/stringer:
|
||||
cd api; \
|
||||
go install golang.org/x/tools/cmd/stringer
|
||||
go get golang.org/x/tools/cmd/stringer
|
||||
|
||||
# Version pinned by api/go.mod
|
||||
$(MYGOBIN)/goimports:
|
||||
cd api; \
|
||||
go install golang.org/x/tools/cmd/goimports
|
||||
go get golang.org/x/tools/cmd/goimports
|
||||
|
||||
# Install resource from whatever is checked out.
|
||||
$(MYGOBIN)/resource:
|
||||
cd cmd/resource; \
|
||||
# Build from local source.
|
||||
$(MYGOBIN)/gorepomod:
|
||||
cd cmd/gorepomod; \
|
||||
go install .
|
||||
|
||||
# To pin pluginator, use this recipe instead:
|
||||
# cd api;
|
||||
# go install sigs.k8s.io/kustomize/pluginator/v2
|
||||
# Build from local source.
|
||||
$(MYGOBIN)/k8scopy:
|
||||
cd cmd/k8scopy; \
|
||||
go install .
|
||||
|
||||
# Build from local source.
|
||||
$(MYGOBIN)/pluginator:
|
||||
cd pluginator; \
|
||||
cd cmd/pluginator; \
|
||||
go install .
|
||||
|
||||
# Install kustomize from whatever is checked out.
|
||||
$(MYGOBIN)/kustomize:
|
||||
# Build from local source.
|
||||
$(MYGOBIN)/prchecker:
|
||||
cd cmd/prchecker; \
|
||||
go install .
|
||||
|
||||
# Build from local source.
|
||||
$(MYGOBIN)/kustomize: build-kustomize-api
|
||||
cd kustomize; \
|
||||
go install .
|
||||
|
||||
@@ -81,8 +97,12 @@ $(MYGOBIN)/kustomize:
|
||||
install-tools: \
|
||||
$(MYGOBIN)/goimports \
|
||||
$(MYGOBIN)/golangci-lint-kustomize \
|
||||
$(MYGOBIN)/gorepomod \
|
||||
$(MYGOBIN)/helmV3 \
|
||||
$(MYGOBIN)/k8scopy \
|
||||
$(MYGOBIN)/mdrip \
|
||||
$(MYGOBIN)/pluginator \
|
||||
$(MYGOBIN)/prchecker \
|
||||
$(MYGOBIN)/stringer
|
||||
|
||||
### Begin kustomize plugin rules.
|
||||
@@ -114,6 +134,7 @@ pSrc=plugin/builtin
|
||||
_builtinplugins = \
|
||||
AnnotationsTransformer.go \
|
||||
ConfigMapGenerator.go \
|
||||
IAMPolicyGenerator.go \
|
||||
HashTransformer.go \
|
||||
ImageTagTransformer.go \
|
||||
LabelTransformer.go \
|
||||
@@ -123,9 +144,11 @@ _builtinplugins = \
|
||||
PatchStrategicMergeTransformer.go \
|
||||
PatchTransformer.go \
|
||||
PrefixSuffixTransformer.go \
|
||||
ReplacementTransformer.go \
|
||||
ReplicaCountTransformer.go \
|
||||
SecretGenerator.go \
|
||||
ValueAddTransformer.go
|
||||
ValueAddTransformer.go \
|
||||
HelmChartInflationGenerator.go
|
||||
|
||||
# Maintaining this explicit list of generated files, and
|
||||
# adding it as a dependency to a few targets, to assure
|
||||
@@ -139,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
|
||||
@@ -148,9 +172,11 @@ $(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
|
||||
$(pGen)/HelmChartInflationGenerator.go: $(pSrc)/helmchartinflationgenerator/HelmChartInflationGenerator.go
|
||||
|
||||
# The (verbose but portable) Makefile way to convert to lowercase.
|
||||
toLowerCase = $(subst A,a,$(subst B,b,$(subst C,c,$(subst D,d,$(subst E,e,$(subst F,f,$(subst G,g,$(subst H,h,$(subst I,i,$(subst J,j,$(subst K,k,$(subst L,l,$(subst M,m,$(subst N,n,$(subst O,o,$(subst P,p,$(subst Q,q,$(subst R,r,$(subst S,s,$(subst T,t,$(subst U,u,$(subst V,v,$(subst W,w,$(subst X,x,$(subst Y,y,$(subst Z,z,$1))))))))))))))))))))))))))
|
||||
@@ -169,24 +195,27 @@ $(pGen)/%.go: $(MYGOBIN)/pluginator
|
||||
.PHONY: generate-kustomize-builtin-plugins
|
||||
generate-kustomize-builtin-plugins: $(builtinplugins)
|
||||
|
||||
.PHONY: kustomize-external-go-plugin-build
|
||||
kustomize-external-go-plugin-build:
|
||||
.PHONY: build-kustomize-external-go-plugin
|
||||
build-kustomize-external-go-plugin:
|
||||
./hack/buildExternalGoPlugins.sh ./plugin
|
||||
|
||||
.PHONY: kustomize-external-go-plugin-clean
|
||||
kustomize-external-go-plugin-clean:
|
||||
.PHONY: clean-kustomize-external-go-plugin
|
||||
clean-kustomize-external-go-plugin:
|
||||
./hack/buildExternalGoPlugins.sh ./plugin clean
|
||||
|
||||
### End kustomize plugin rules.
|
||||
|
||||
.PHONY: lint-kustomize
|
||||
lint-kustomize: install-tools $(builtinplugins)
|
||||
cd api; \
|
||||
$(MYGOBIN)/golangci-lint-kustomize -c ../.golangci-kustomize.yml run ./...
|
||||
cd kustomize; \
|
||||
$(MYGOBIN)/golangci-lint-kustomize -c ../.golangci-kustomize.yml run ./...
|
||||
cd pluginator; \
|
||||
$(MYGOBIN)/golangci-lint-kustomize -c ../.golangci-kustomize.yml run ./...
|
||||
lint-kustomize: $(MYGOBIN)/golangci-lint-kustomize $(builtinplugins)
|
||||
cd api; $(MYGOBIN)/golangci-lint-kustomize \
|
||||
-c ../.golangci-kustomize.yml \
|
||||
run ./...
|
||||
cd kustomize; $(MYGOBIN)/golangci-lint-kustomize \
|
||||
-c ../.golangci-kustomize.yml \
|
||||
run ./...
|
||||
cd cmd/pluginator; $(MYGOBIN)/golangci-lint-kustomize \
|
||||
-c ../../.golangci-kustomize.yml \
|
||||
run ./...
|
||||
|
||||
# Used to add non-default compilation flags when experimenting with
|
||||
# plugin-to-api compatibility checks.
|
||||
@@ -194,9 +223,14 @@ lint-kustomize: install-tools $(builtinplugins)
|
||||
build-kustomize-api: $(builtinplugins)
|
||||
cd api; go build ./...
|
||||
|
||||
.PHONY: 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:
|
||||
@@ -213,10 +247,23 @@ test-unit-kustomize-all: \
|
||||
test-unit-kustomize-plugins
|
||||
|
||||
test-unit-cmd-all:
|
||||
./travis/kyaml-pre-commit.sh
|
||||
./hack/kyaml-pre-commit.sh
|
||||
|
||||
test-go-mod:
|
||||
./travis/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
|
||||
.PHONY: test-multi-module
|
||||
test-multi-module: $(MYGOBIN)/prchecker
|
||||
( \
|
||||
export MYGOBIN=$(MYGOBIN); \
|
||||
export REPO_OWNER=$(REPO_OWNER); \
|
||||
export REPO_NAME=$(REPO_NAME); \
|
||||
export PULL_NUMBER=$(PULL_NUMBER); \
|
||||
export MODULES=$(MODULES); \
|
||||
./hack/check-multi-module.sh; \
|
||||
)
|
||||
|
||||
.PHONY:
|
||||
test-examples-e2e-kustomize: $(MYGOBIN)/mdrip $(MYGOBIN)/kind
|
||||
@@ -233,17 +280,8 @@ test-examples-kustomize-against-HEAD: $(MYGOBIN)/kustomize $(MYGOBIN)/mdrip
|
||||
./hack/testExamplesAgainstKustomize.sh HEAD
|
||||
|
||||
.PHONY:
|
||||
test-examples-kustomize-against-3.7.0: $(MYGOBIN)/mdrip
|
||||
( \
|
||||
set -e; \
|
||||
tag=v3.7.0; \
|
||||
/bin/rm -f $(MYGOBIN)/kustomize; \
|
||||
echo "Installing kustomize $$tag."; \
|
||||
GO111MODULE=on go get sigs.k8s.io/kustomize/kustomize/v3@$${tag}; \
|
||||
./hack/testExamplesAgainstKustomize.sh $$tag; \
|
||||
echo "Reinstalling kustomize from HEAD."; \
|
||||
cd kustomize; go install .; \
|
||||
)
|
||||
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
|
||||
@@ -255,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; \
|
||||
)
|
||||
@@ -270,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 \
|
||||
)
|
||||
|
||||
@@ -283,34 +321,48 @@ $(MYGOBIN)/helmV3:
|
||||
( \
|
||||
set -e; \
|
||||
d=$(shell mktemp -d); cd $$d; \
|
||||
tgzFile=helm-v3.2.0-rc.1-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 v2 for the time being.
|
||||
$(MYGOBIN)/helm: $(MYGOBIN)/helmV2
|
||||
ln -s $(MYGOBIN)/helmV2 $(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; \
|
||||
)
|
||||
|
||||
# linux only.
|
||||
$(MYGOBIN)/gh:
|
||||
( \
|
||||
set -e; \
|
||||
d=$(shell mktemp -d); cd $$d; \
|
||||
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_$(GOOS)_$(GOARCH)/bin/gh $(MYGOBIN)/gh; \
|
||||
rm -rf $$d \
|
||||
)
|
||||
|
||||
.PHONY: clean
|
||||
clean: kustomize-external-go-plugin-clean
|
||||
clean: clean-kustomize-external-go-plugin
|
||||
go clean --cache
|
||||
rm -f $(builtinplugins)
|
||||
rm -f $(MYGOBIN)/pluginator
|
||||
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
|
||||
|
||||
# Nuke the site from orbit. It's the only way to be sure.
|
||||
.PHONY: nuke
|
||||
|
||||
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,13 +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
|
||||
|
||||
43
README.md
43
README.md
@@ -9,29 +9,38 @@ patch [kubernetes style] API objects. It's like
|
||||
[`make`], in that what it does is declared in a file,
|
||||
and it's like [`sed`], in that it emits edited text.
|
||||
|
||||
This tool is sponsored by [sig-cli] ([KEP]), and
|
||||
inspired by [DAM].
|
||||
This tool is sponsored by [sig-cli] ([KEP]).
|
||||
|
||||
- [Installation instructions](https://kubernetes-sigs.github.io/kustomize/installation)
|
||||
- [General documentation](https://kubernetes-sigs.github.io/kustomize)
|
||||
- [Examples](examples)
|
||||
|
||||
[](https://prow.k8s.io/job-history/kubernetes-jenkins/pr-logs/directory/kustomize-presubmit-master)
|
||||
[](https://goreportcard.com/report/github.com/kubernetes-sigs/kustomize)
|
||||
|
||||
Download a binary from the [release page], or see
|
||||
these [instructions](https://kubernetes-sigs.github.io/kustomize/installation/).
|
||||
|
||||
Browse the [docs](https://kubernetes-sigs.github.io/kustomize/) or jump right into the
|
||||
tested [examples](examples).
|
||||
|
||||
## kubectl integration
|
||||
|
||||
Since [v1.14][kubectl announcement] the kustomize build system has been included in kubectl.
|
||||
The kustomize build flow at [v2.0.3] was added
|
||||
to [kubectl v1.14][kubectl announcement]. The kustomize
|
||||
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.16.0 | [v2.0.3](/../../tree/v2.0.3) |
|
||||
| v1.15.x | [v2.0.3](/../../tree/v2.0.3) |
|
||||
| v1.14.x | [v2.0.3](/../../tree/v2.0.3) |
|
||||
| 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 |
|
||||
|
||||
For examples and guides for using the kubectl integration please see the [kubectl book] or the [kubernetes documentation].
|
||||
[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].
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -143,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
|
||||
@@ -151,7 +160,7 @@ is governed by the [Kubernetes Code of Conduct].
|
||||
[imageBase]: docs/images/base.jpg
|
||||
[imageOverlay]: docs/images/overlay.jpg
|
||||
[kubectl announcement]: https://kubernetes.io/blog/2019/03/25/kubernetes-1-14-release-announcement
|
||||
[kubectl book]: https://kubectl.docs.kubernetes.io/pages/app_customization/introduction.html
|
||||
[kubectl book]: https://kubectl.docs.kubernetes.io/guides/introduction/kustomize/
|
||||
[kubernetes documentation]: https://kubernetes.io/docs/tasks/manage-kubernetes-objects/kustomization/
|
||||
[kubernetes style]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#kubernetes-style-object
|
||||
[kustomization]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#kustomization
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"sigs.k8s.io/kustomize/api/filters/annotations"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/filtersutil"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
@@ -25,16 +24,13 @@ func (p *AnnotationsTransformerPlugin) Config(
|
||||
}
|
||||
|
||||
func (p *AnnotationsTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
for _, r := range m.Resources() {
|
||||
err := filtersutil.ApplyToJSON(annotations.Filter{
|
||||
Annotations: p.Annotations,
|
||||
FsSlice: p.FieldSpecs,
|
||||
}, r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(p.Annotations) == 0 {
|
||||
return nil
|
||||
}
|
||||
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,10 +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.StorePreviousId()
|
||||
res.SetName(fmt.Sprintf("%s-%s", res.GetName(), h))
|
||||
}
|
||||
}
|
||||
|
||||
339
api/builtins/HelmChartInflationGenerator.go
Normal file
339
api/builtins/HelmChartInflationGenerator.go
Normal file
@@ -0,0 +1,339 @@
|
||||
// Code generated by pluginator on HelmChartInflationGenerator; DO NOT EDIT.
|
||||
// pluginator {unknown 1970-01-01T00:00:00Z }
|
||||
|
||||
package builtins
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/imdario/mergo"
|
||||
"github.com/pkg/errors"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
// HelmChartInflationGeneratorPlugin is a plugin to generate resources
|
||||
// from a remote or local helm chart.
|
||||
type HelmChartInflationGeneratorPlugin struct {
|
||||
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) (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
|
||||
if err = yaml.Unmarshal(config, p); err != nil {
|
||||
return
|
||||
}
|
||||
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, 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 = "charts"
|
||||
}
|
||||
|
||||
// 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 err = p.errIfIllegalValuesMerge(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 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 == "" {
|
||||
// Use the default.
|
||||
p.ValuesMerge = valuesMergeOptionOverride
|
||||
return nil
|
||||
}
|
||||
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(),
|
||||
)
|
||||
}
|
||||
return stdout.Bytes(), 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
|
||||
}
|
||||
}
|
||||
var b []byte
|
||||
b, err = yaml.Marshal(p.ValuesInline)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return p.writeValuesBytes(b)
|
||||
}
|
||||
|
||||
func (p *HelmChartInflationGeneratorPlugin) replaceValuesInline() error {
|
||||
pValues, err := p.h.Loader().Load(p.ValuesFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chValues := make(map[string]interface{})
|
||||
if err = yaml.Unmarshal(pValues, &chValues); err != nil {
|
||||
return err
|
||||
}
|
||||
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() (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
|
||||
}
|
||||
var stdout []byte
|
||||
stdout, err = p.runHelmCommand(p.templateCommand())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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) templateCommand() []string {
|
||||
args := []string{"template"}
|
||||
if p.ReleaseName != "" {
|
||||
args = append(args, p.ReleaseName)
|
||||
}
|
||||
if p.Namespace != "" {
|
||||
args = append(args, "--namespace", p.Namespace)
|
||||
}
|
||||
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")
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
// chartExistsLocally will return true if the chart does exist in
|
||||
// local chart home.
|
||||
func (p *HelmChartInflationGeneratorPlugin) chartExistsLocally() (string, bool) {
|
||||
path := filepath.Join(p.absChartHome(), p.Name)
|
||||
s, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
return path, s.IsDir()
|
||||
}
|
||||
|
||||
// checkHelmVersion will return an error if the helm version is not V3
|
||||
func (p *HelmChartInflationGeneratorPlugin) checkHelmVersion() error {
|
||||
stdout, err := p.runHelmCommand([]string{"version", "-c", "--short"})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r, err := regexp.Compile(`v?\d+(\.\d+)+`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewHelmChartInflationGeneratorPlugin() resmap.GeneratorPlugin {
|
||||
return &HelmChartInflationGeneratorPlugin{}
|
||||
}
|
||||
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,14 +4,9 @@
|
||||
package builtins
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/transform"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/filters/imagetag"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
@@ -30,156 +25,15 @@ func (p *ImageTagTransformerPlugin) Config(
|
||||
}
|
||||
|
||||
func (p *ImageTagTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
for _, r := range m.Resources() {
|
||||
for _, path := range p.FieldSpecs {
|
||||
if !r.OrgId().IsSelected(&path.Gvk) {
|
||||
continue
|
||||
}
|
||||
err := transform.MutateField(
|
||||
r.Map(), path.PathSlice(), false, p.mutateImage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Kept for backward compatibility
|
||||
if err := p.findAndReplaceImage(r.Map()); err != nil && r.OrgId().Kind != `CustomResourceDefinition` {
|
||||
return err
|
||||
}
|
||||
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 {
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"sigs.k8s.io/kustomize/api/filters/labels"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/filtersutil"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
@@ -25,16 +24,13 @@ func (p *LabelTransformerPlugin) Config(
|
||||
}
|
||||
|
||||
func (p *LabelTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
for _, r := range m.Resources() {
|
||||
err := filtersutil.ApplyToJSON(labels.Filter{
|
||||
Labels: p.Labels,
|
||||
FsSlice: p.FieldSpecs,
|
||||
}, r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(p.Labels) == 0 {
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
return m.ApplyFilter(labels.Filter{
|
||||
Labels: p.Labels,
|
||||
FsSlice: p.FieldSpecs,
|
||||
})
|
||||
}
|
||||
|
||||
func NewLabelTransformerPlugin() resmap.TransformerPlugin {
|
||||
|
||||
@@ -7,11 +7,8 @@ import (
|
||||
"fmt"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/filters/namespace"
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/resource"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/filtersutil"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
@@ -33,15 +30,15 @@ func (p *NamespaceTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
return nil
|
||||
}
|
||||
for _, r := range m.Resources() {
|
||||
if len(r.Map()) == 0 {
|
||||
if r.IsNilOrEmpty() {
|
||||
// Don't mutate empty objects?
|
||||
continue
|
||||
}
|
||||
err := filtersutil.ApplyToJSON(namespace.Filter{
|
||||
r.StorePreviousId()
|
||||
if err := r.ApplyFilter(namespace.Filter{
|
||||
Namespace: p.Namespace,
|
||||
FsSlice: p.FieldSpecs,
|
||||
}, r)
|
||||
if err != nil {
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
matches := m.GetMatchingResourcesByCurrentId(r.CurId().Equals)
|
||||
@@ -53,74 +50,6 @@ func (p *NamespaceTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Special casing metadata.namespace since
|
||||
// all objects have it, even "ClusterKind" objects
|
||||
// that don't exist in a namespace (the Namespace
|
||||
// object itself doesn't live in a namespace).
|
||||
func (p *NamespaceTransformerPlugin) applicableFieldSpecs(id resid.ResId) []types.FieldSpec {
|
||||
var res []types.FieldSpec
|
||||
for _, fs := range p.FieldSpecs {
|
||||
if id.IsSelected(&fs.Gvk) &&
|
||||
(fs.Path != types.MetadataNamespacePath ||
|
||||
(fs.Path == types.MetadataNamespacePath && id.IsNamespaceableKind())) {
|
||||
res = append(res, fs)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (p *NamespaceTransformerPlugin) changeNamespace(
|
||||
_ *resource.Resource) func(in interface{}) (interface{}, error) {
|
||||
return func(in interface{}) (interface{}, error) {
|
||||
switch in.(type) {
|
||||
case string:
|
||||
// will happen when the metadata/namespace
|
||||
// value is replaced
|
||||
return p.Namespace, nil
|
||||
case []interface{}:
|
||||
l, _ := in.([]interface{})
|
||||
for idx, item := range l {
|
||||
switch item.(type) {
|
||||
case map[string]interface{}:
|
||||
// Will happen when mutating the subjects
|
||||
// field of ClusterRoleBinding and RoleBinding
|
||||
inMap, _ := item.(map[string]interface{})
|
||||
if _, ok := inMap["name"]; !ok {
|
||||
continue
|
||||
}
|
||||
name, ok := inMap["name"].(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
// The only case we need to force the namespace
|
||||
// if for the "service account". "default" is
|
||||
// kind of hardcoded here for right now.
|
||||
if name != "default" {
|
||||
continue
|
||||
}
|
||||
inMap["namespace"] = p.Namespace
|
||||
l[idx] = inMap
|
||||
default:
|
||||
// nothing to do for right now
|
||||
}
|
||||
}
|
||||
return in, nil
|
||||
case map[string]interface{}:
|
||||
// Will happen if the createField=true
|
||||
// when the namespace is added to the
|
||||
// object
|
||||
inMap := in.(map[string]interface{})
|
||||
if len(inMap) == 0 {
|
||||
return p.Namespace, nil
|
||||
} else {
|
||||
return in, nil
|
||||
}
|
||||
default:
|
||||
return in, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func NewNamespaceTransformerPlugin() resmap.TransformerPlugin {
|
||||
return &NamespaceTransformerPlugin{}
|
||||
}
|
||||
|
||||
@@ -10,19 +10,18 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"sigs.k8s.io/kustomize/api/filters/patchjson6902"
|
||||
"sigs.k8s.io/kustomize/api/ifc"
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/filtersutil"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
type PatchJson6902TransformerPlugin struct {
|
||||
ldr ifc.Loader
|
||||
decodedPatch jsonpatch.Patch
|
||||
Target types.PatchTarget `json:"target,omitempty" yaml:"target,omitempty"`
|
||||
Path string `json:"path,omitempty" yaml:"path,omitempty"`
|
||||
JsonOp string `json:"jsonOp,omitempty" yaml:"jsonOp,omitempty"`
|
||||
Target *types.Selector `json:"target,omitempty" yaml:"target,omitempty"`
|
||||
Path string `json:"path,omitempty" yaml:"path,omitempty"`
|
||||
JsonOp string `json:"jsonOp,omitempty" yaml:"jsonOp,omitempty"`
|
||||
}
|
||||
|
||||
func (p *PatchJson6902TransformerPlugin) Config(
|
||||
@@ -72,22 +71,33 @@ func (p *PatchJson6902TransformerPlugin) Config(
|
||||
}
|
||||
|
||||
func (p *PatchJson6902TransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
id := resid.NewResIdWithNamespace(
|
||||
resid.Gvk{
|
||||
Group: p.Target.Group,
|
||||
Version: p.Target.Version,
|
||||
Kind: p.Target.Kind,
|
||||
},
|
||||
p.Target.Name,
|
||||
p.Target.Namespace,
|
||||
)
|
||||
obj, err := m.GetById(id)
|
||||
if p.Target == nil {
|
||||
return fmt.Errorf("must specify a target for patch %s", p.JsonOp)
|
||||
}
|
||||
resources, err := m.Select(*p.Target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return filtersutil.ApplyToJSON(patchjson6902.Filter{
|
||||
Patch: p.JsonOp,
|
||||
}, obj)
|
||||
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
|
||||
}
|
||||
|
||||
func NewPatchJson6902TransformerPlugin() resmap.TransformerPlugin {
|
||||
|
||||
@@ -5,19 +5,14 @@ package builtins
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"sigs.k8s.io/kustomize/api/filters/patchstrategicmerge"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/resource"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/filtersutil"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
type PatchStrategicMergeTransformerPlugin struct {
|
||||
h *resmap.PluginHelpers
|
||||
loadedPatches []*resource.Resource
|
||||
Paths []types.PatchStrategicMerge `json:"paths,omitempty" yaml:"paths,omitempty"`
|
||||
Patches string `json:"patches,omitempty" yaml:"patches,omitempty"`
|
||||
@@ -25,7 +20,6 @@ type PatchStrategicMergeTransformerPlugin struct {
|
||||
|
||||
func (p *PatchStrategicMergeTransformerPlugin) Config(
|
||||
h *resmap.PluginHelpers, c []byte) (err error) {
|
||||
p.h = h
|
||||
err = yaml.Unmarshal(c, p)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -34,83 +28,58 @@ func (p *PatchStrategicMergeTransformerPlugin) Config(
|
||||
return fmt.Errorf("empty file path and empty patch content")
|
||||
}
|
||||
if len(p.Paths) != 0 {
|
||||
for _, onePath := range p.Paths {
|
||||
res, err := p.h.ResmapFactory().RF().SliceFromBytes([]byte(onePath))
|
||||
if err == nil {
|
||||
p.loadedPatches = append(p.loadedPatches, res...)
|
||||
continue
|
||||
}
|
||||
res, err = p.h.ResmapFactory().RF().SliceFromPatches(
|
||||
p.h.Loader(), []types.PatchStrategicMerge{onePath})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.loadedPatches = append(p.loadedPatches, res...)
|
||||
}
|
||||
}
|
||||
if p.Patches != "" {
|
||||
res, err := p.h.ResmapFactory().RF().SliceFromBytes([]byte(p.Patches))
|
||||
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)
|
||||
}
|
||||
return err
|
||||
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 {
|
||||
patches, err := p.h.ResmapFactory().MergePatches(p.loadedPatches)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, patch := range patches.Resources() {
|
||||
for _, patch := range p.loadedPatches {
|
||||
target, err := m.GetById(patch.OrgId())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
patchCopy := patch.DeepCopy()
|
||||
patchCopy.SetName(target.GetName())
|
||||
patchCopy.SetNamespace(target.GetNamespace())
|
||||
patchCopy.SetGvk(target.GetGvk())
|
||||
node, err := filtersutil.GetRNode(patchCopy)
|
||||
if err != nil {
|
||||
if err = m.ApplySmPatch(
|
||||
resource.MakeIdSet([]*resource.Resource{target}), patch); err != nil {
|
||||
return err
|
||||
}
|
||||
err = filtersutil.ApplyToJSON(patchstrategicmerge.Filter{
|
||||
Patch: node,
|
||||
}, target)
|
||||
if err != nil {
|
||||
// Check for an error string from UnmarshalJSON that's indicative
|
||||
// of an object that's missing basic KRM fields, and thus may have been
|
||||
// entirely deleted (an acceptable outcome). This error handling should
|
||||
// be deleted along with use of ResMap and apimachinery functions like
|
||||
// UnmarshalJSON.
|
||||
if !strings.Contains(err.Error(), "Object 'Kind' is missing") {
|
||||
// Some unknown error, let it through.
|
||||
return err
|
||||
}
|
||||
if len(target.Map()) != 0 {
|
||||
return errors.Wrapf(
|
||||
err, "with unexpectedly non-empty object map of size %d",
|
||||
len(target.Map()))
|
||||
}
|
||||
// Fall through to handle deleted object.
|
||||
}
|
||||
if len(target.Map()) == 0 {
|
||||
// This means all fields have been removed from the object.
|
||||
// This can happen if a patch required deletion of the
|
||||
// entire resource (not just a part of it). This means
|
||||
// the overall resmap must shrink by one.
|
||||
err = m.Remove(target.CurId())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -9,11 +9,10 @@ import (
|
||||
|
||||
jsonpatch "github.com/evanphx/json-patch"
|
||||
"sigs.k8s.io/kustomize/api/filters/patchjson6902"
|
||||
"sigs.k8s.io/kustomize/api/filters/patchstrategicmerge"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/resource"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/filtersutil"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
@@ -23,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(
|
||||
@@ -40,7 +40,6 @@ func (p *PatchTransformerPlugin) Config(
|
||||
return fmt.Errorf(
|
||||
"patch and path can't be set at the same time\n%s", string(c))
|
||||
}
|
||||
|
||||
if p.Path != "" {
|
||||
loaded, loadErr := h.Loader().Load(p.Path)
|
||||
if loadErr != nil {
|
||||
@@ -63,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
|
||||
}
|
||||
@@ -72,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
|
||||
@@ -87,36 +91,13 @@ func (p *PatchTransformerPlugin) transformStrategicMerge(m resmap.ResMap, patch
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return p.applySMPatch(target, patch)
|
||||
return target.ApplySmPatch(patch)
|
||||
}
|
||||
|
||||
resources, err := m.Select(*p.Target)
|
||||
selected, err := m.Select(*p.Target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, res := range resources {
|
||||
patchCopy := patch.DeepCopy()
|
||||
patchCopy.SetName(res.GetName())
|
||||
patchCopy.SetNamespace(res.GetNamespace())
|
||||
patchCopy.SetGvk(res.GetGvk())
|
||||
err := p.applySMPatch(res, patchCopy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// applySMPatch applies the provided strategic merge patch to the
|
||||
// given resource.
|
||||
func (p *PatchTransformerPlugin) applySMPatch(resource, patch *resource.Resource) error {
|
||||
node, err := filtersutil.GetRNode(patch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return filtersutil.ApplyToJSON(patchstrategicmerge.Filter{
|
||||
Patch: node,
|
||||
}, resource)
|
||||
return m.ApplySmPatch(resource.MakeIdSet(selected), patch)
|
||||
}
|
||||
|
||||
// transformJson6902 applies the provided json6902 patch
|
||||
@@ -130,12 +111,20 @@ func (p *PatchTransformerPlugin) transformJson6902(m resmap.ResMap, patch jsonpa
|
||||
return err
|
||||
}
|
||||
for _, res := range resources {
|
||||
err = filtersutil.ApplyToJSON(patchjson6902.Filter{
|
||||
res.StorePreviousId()
|
||||
internalAnnotations := kioutil.GetInternalAnnotations(&res.RNode)
|
||||
err = res.ApplyFilter(patchjson6902.Filter{
|
||||
Patch: p.Patch,
|
||||
}, res)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
annotations := res.GetAnnotations()
|
||||
for key, value := range internalAnnotations {
|
||||
annotations[key] = value
|
||||
}
|
||||
err = res.SetAnnotations(annotations)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -5,31 +5,27 @@ package builtins
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/transform"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/filters/prefixsuffix"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/resid"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
// Add the given prefix and suffix to the field.
|
||||
type PrefixSuffixTransformerPlugin struct {
|
||||
Prefix string `json:"prefix,omitempty" yaml:"prefix,omitempty"`
|
||||
Suffix string `json:"suffix,omitempty" yaml:"suffix,omitempty"`
|
||||
FieldSpecs []types.FieldSpec `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
|
||||
Prefix string `json:"prefix,omitempty" yaml:"prefix,omitempty"`
|
||||
Suffix string `json:"suffix,omitempty" yaml:"suffix,omitempty"`
|
||||
FieldSpecs types.FsSlice `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
|
||||
}
|
||||
|
||||
// Not placed in a file yet due to lack of demand.
|
||||
var prefixSuffixFieldSpecsToSkip = []types.FieldSpec{
|
||||
{
|
||||
Gvk: resid.Gvk{Kind: "CustomResourceDefinition"},
|
||||
},
|
||||
{
|
||||
Gvk: resid.Gvk{Group: "apiregistration.k8s.io", Kind: "APIService"},
|
||||
},
|
||||
// A Gvk skip list for prefix/suffix modification.
|
||||
// hard coded for now - eventually should be part of config.
|
||||
var prefixSuffixFieldSpecsToSkip = types.FsSlice{
|
||||
{Gvk: resid.Gvk{Kind: "CustomResourceDefinition"}},
|
||||
{Gvk: resid.Gvk{Group: "apiregistration.k8s.io", Kind: "APIService"}},
|
||||
{Gvk: resid.Gvk{Kind: "Namespace"}},
|
||||
}
|
||||
|
||||
func (p *PrefixSuffixTransformerPlugin) Config(
|
||||
@@ -48,46 +44,40 @@ func (p *PrefixSuffixTransformerPlugin) Config(
|
||||
}
|
||||
|
||||
func (p *PrefixSuffixTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
|
||||
// Even if both the Prefix and Suffix are empty we want
|
||||
// to proceed with the transformation. This allows to add contextual
|
||||
// information to the resources (AddNamePrefix and AddNameSuffix).
|
||||
|
||||
for _, r := range m.Resources() {
|
||||
// TODO: move this test into the filter (i.e. make a better filter)
|
||||
if p.shouldSkip(r.OrgId()) {
|
||||
// Don't change the actual definition
|
||||
// of a CRD.
|
||||
continue
|
||||
}
|
||||
id := r.OrgId()
|
||||
// current default configuration contains
|
||||
// only one entry: "metadata/name" with no GVK
|
||||
for _, path := range p.FieldSpecs {
|
||||
if !id.IsSelected(&path.Gvk) {
|
||||
// With the currrent default configuration,
|
||||
// because no Gvk is specified, so a wild
|
||||
// card
|
||||
for _, fs := range p.FieldSpecs {
|
||||
// TODO: this is redundant to filter (but needed for now)
|
||||
if !id.IsSelected(&fs.Gvk) {
|
||||
continue
|
||||
}
|
||||
|
||||
if smellsLikeANameChange(&path) {
|
||||
// TODO: move this test into the filter.
|
||||
if smellsLikeANameChange(&fs) {
|
||||
// "metadata/name" is the only field.
|
||||
// this will add a prefix and a suffix
|
||||
// to the resource even if those are
|
||||
// empty
|
||||
|
||||
r.AddNamePrefix(p.Prefix)
|
||||
r.AddNameSuffix(p.Suffix)
|
||||
if p.Prefix != "" || p.Suffix != "" {
|
||||
r.StorePreviousId()
|
||||
}
|
||||
}
|
||||
|
||||
// the addPrefixSuffix method will not
|
||||
// change the name if both the prefix and suffix
|
||||
// are empty.
|
||||
err := transform.MutateField(
|
||||
r.Map(),
|
||||
path.PathSlice(),
|
||||
path.CreateIfNotPresent,
|
||||
p.addPrefixSuffix)
|
||||
if err != nil {
|
||||
if err := r.ApplyFilter(prefixsuffix.Filter{
|
||||
Prefix: p.Prefix,
|
||||
Suffix: p.Suffix,
|
||||
FieldSpec: fs,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -99,8 +89,7 @@ func smellsLikeANameChange(fs *types.FieldSpec) bool {
|
||||
return fs.Path == "metadata/name"
|
||||
}
|
||||
|
||||
func (p *PrefixSuffixTransformerPlugin) shouldSkip(
|
||||
id resid.ResId) bool {
|
||||
func (p *PrefixSuffixTransformerPlugin) shouldSkip(id resid.ResId) bool {
|
||||
for _, path := range prefixSuffixFieldSpecsToSkip {
|
||||
if id.IsSelected(&path.Gvk) {
|
||||
return true
|
||||
@@ -109,15 +98,6 @@ func (p *PrefixSuffixTransformerPlugin) shouldSkip(
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *PrefixSuffixTransformerPlugin) addPrefixSuffix(
|
||||
in interface{}) (interface{}, error) {
|
||||
s, ok := in.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%#v is expected to be %T", in, s)
|
||||
}
|
||||
return fmt.Sprintf("%s%s%s", p.Prefix, s, p.Suffix), nil
|
||||
}
|
||||
|
||||
func NewPrefixSuffixTransformerPlugin() resmap.TransformerPlugin {
|
||||
return &PrefixSuffixTransformerPlugin{}
|
||||
}
|
||||
|
||||
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{}
|
||||
}
|
||||
@@ -6,11 +6,10 @@ package builtins
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/transform"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/filters/replicacount"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/resid"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
@@ -30,18 +29,22 @@ func (p *ReplicaCountTransformerPlugin) Config(
|
||||
|
||||
func (p *ReplicaCountTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
found := false
|
||||
for i, replicaSpec := range p.FieldSpecs {
|
||||
matcher := p.createMatcher(i)
|
||||
matchOriginal := m.GetMatchingResourcesByOriginalId(matcher)
|
||||
matchCurrent := m.GetMatchingResourcesByCurrentId(matcher)
|
||||
|
||||
for _, res := range append(matchOriginal, matchCurrent...) {
|
||||
for _, fs := range p.FieldSpecs {
|
||||
matcher := p.createMatcher(fs)
|
||||
resList := m.GetMatchingResourcesByAnyId(matcher)
|
||||
if len(resList) > 0 {
|
||||
found = true
|
||||
err := transform.MutateField(
|
||||
res.Map(), replicaSpec.PathSlice(),
|
||||
replicaSpec.CreateIfNotPresent, p.addReplicas)
|
||||
if err != nil {
|
||||
return err
|
||||
for _, r := range resList {
|
||||
// There are redundant checks in the filter
|
||||
// that we'll live with until resolution of
|
||||
// https://github.com/kubernetes-sigs/kustomize/issues/2506
|
||||
err := r.ApplyFilter(replicacount.Filter{
|
||||
Replica: p.Replica,
|
||||
FieldSpec: fs,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,30 +62,12 @@ func (p *ReplicaCountTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
}
|
||||
|
||||
// Match Replica.Name and FieldSpec
|
||||
func (p *ReplicaCountTransformerPlugin) createMatcher(i int) resmap.IdMatcher {
|
||||
func (p *ReplicaCountTransformerPlugin) createMatcher(fs types.FieldSpec) resmap.IdMatcher {
|
||||
return func(r resid.ResId) bool {
|
||||
return r.Name == p.Replica.Name &&
|
||||
r.Gvk.IsSelected(&p.FieldSpecs[i].Gvk)
|
||||
return r.Name == p.Replica.Name && r.Gvk.IsSelected(&fs.Gvk)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ReplicaCountTransformerPlugin) addReplicas(in interface{}) (interface{}, error) {
|
||||
switch m := in.(type) {
|
||||
case int64:
|
||||
// Was already in the field.
|
||||
case map[string]interface{}:
|
||||
if len(m) != 0 {
|
||||
// A map was already in the replicas field, don't want to
|
||||
// discard this data silently.
|
||||
return nil, fmt.Errorf("%#v is expected to be %T", in, m)
|
||||
}
|
||||
// Just got added, default type is map, but we can return anything.
|
||||
default:
|
||||
return nil, fmt.Errorf("%#v is expected to be %T", in, m)
|
||||
}
|
||||
return p.Replica.Count, nil
|
||||
}
|
||||
|
||||
func NewReplicaCountTransformerPlugin() resmap.TransformerPlugin {
|
||||
return &ReplicaCountTransformerPlugin{}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/resource"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/filtersutil"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
@@ -119,15 +118,15 @@ func (p *ValueAddTransformerPlugin) Transform(m resmap.ResMap) (err error) {
|
||||
// TODO: consider t.NotSelector if implemented
|
||||
for _, res := range resources {
|
||||
if t.FieldPath == types.MetadataNamespacePath {
|
||||
err = filtersutil.ApplyToJSON(namespace.Filter{
|
||||
err = res.ApplyFilter(namespace.Filter{
|
||||
Namespace: p.Value,
|
||||
}, res)
|
||||
})
|
||||
} else {
|
||||
err = filtersutil.ApplyToJSON(valueadd.Filter{
|
||||
err = res.ApplyFilter(valueadd.Filter{
|
||||
Value: p.Value,
|
||||
FieldPath: t.FieldPath,
|
||||
FilePathPosition: t.FilePathPosition,
|
||||
}, res)
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
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
|
||||
}
|
||||
@@ -24,15 +24,16 @@ type Filter struct {
|
||||
var _ kio.Filter = Filter{}
|
||||
|
||||
func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||
keys := filtersutil.SortedMapKeys(f.Annotations)
|
||||
keys := yaml.SortedMapKeys(f.Annotations)
|
||||
_, err := kio.FilterAll(yaml.FilterFunc(
|
||||
func(node *yaml.RNode) (*yaml.RNode, error) {
|
||||
for _, k := range keys {
|
||||
if err := node.PipeE(fsslice.Filter{
|
||||
FsSlice: f.FsSlice,
|
||||
SetValue: fsslice.SetEntry(k, f.Annotations[k], yaml.StringTag),
|
||||
FsSlice: f.FsSlice,
|
||||
SetValue: filtersutil.SetEntry(
|
||||
k, f.Annotations[k], yaml.NodeTagString),
|
||||
CreateKind: yaml.MappingNode, // Annotations are MappingNodes.
|
||||
CreateTag: "!!map",
|
||||
CreateTag: yaml.NodeTagMap,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -28,7 +28,9 @@ metadata:
|
||||
`)}},
|
||||
Filters: []kio.Filter{Filter{
|
||||
Annotations: map[string]string{
|
||||
"foo": "bar",
|
||||
"foo": "bar",
|
||||
"booleanValue": "true",
|
||||
"numberValue": "42",
|
||||
},
|
||||
FsSlice: fss,
|
||||
}},
|
||||
@@ -44,12 +46,16 @@ metadata:
|
||||
// metadata:
|
||||
// name: instance
|
||||
// annotations:
|
||||
// booleanValue: "true"
|
||||
// foo: bar
|
||||
// numberValue: "42"
|
||||
// ---
|
||||
// apiVersion: example.com/v1
|
||||
// kind: Bar
|
||||
// metadata:
|
||||
// name: instance
|
||||
// annotations:
|
||||
// booleanValue: "true"
|
||||
// foo: bar
|
||||
// numberValue: "42"
|
||||
}
|
||||
|
||||
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.
|
||||
6
api/filters/fieldspec/doc.go
Normal file
6
api/filters/fieldspec/doc.go
Normal file
@@ -0,0 +1,6 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package fieldspec contains a yaml.Filter to modify a resource
|
||||
// that matches the FieldSpec.
|
||||
package fieldspec
|
||||
61
api/filters/fieldspec/example_test.go
Normal file
61
api/filters/fieldspec/example_test.go
Normal file
@@ -0,0 +1,61 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package fieldspec_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
. "sigs.k8s.io/kustomize/api/filters/fieldspec"
|
||||
"sigs.k8s.io/kustomize/api/filters/filtersutil"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
func ExampleFilter() {
|
||||
in := &kio.ByteReader{
|
||||
Reader: bytes.NewBufferString(`
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
`),
|
||||
}
|
||||
fltr := Filter{
|
||||
CreateKind: yaml.ScalarNode,
|
||||
SetValue: filtersutil.SetScalar("green"),
|
||||
FieldSpec: types.FieldSpec{Path: "a/b", CreateIfNotPresent: true},
|
||||
}
|
||||
|
||||
err := kio.Pipeline{
|
||||
Inputs: []kio.Reader{in},
|
||||
Filters: []kio.Filter{kio.FilterAll(fltr)},
|
||||
Outputs: []kio.Writer{kio.ByteWriter{Writer: os.Stdout}},
|
||||
}.Execute()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// apiVersion: example.com/v1
|
||||
// kind: Foo
|
||||
// metadata:
|
||||
// name: instance
|
||||
// a:
|
||||
// b: green
|
||||
// ---
|
||||
// apiVersion: example.com/v1
|
||||
// kind: Bar
|
||||
// metadata:
|
||||
// name: instance
|
||||
// a:
|
||||
// b: green
|
||||
}
|
||||
181
api/filters/fieldspec/fieldspec.go
Normal file
181
api/filters/fieldspec/fieldspec.go
Normal file
@@ -0,0 +1,181 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
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 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.
|
||||
FieldSpec types.FieldSpec `yaml:"fieldSpec"`
|
||||
|
||||
// Set the field using this function
|
||||
SetValue filtersutil.SetFn
|
||||
|
||||
// CreateKind defines the type of node to create if the field is not found
|
||||
CreateKind yaml.Kind
|
||||
|
||||
CreateTag string
|
||||
|
||||
// path keeps internal state about the current path
|
||||
path []string
|
||||
}
|
||||
|
||||
func (fltr Filter) Filter(obj *yaml.RNode) (*yaml.RNode, error) {
|
||||
// check if the FieldSpec applies to the object
|
||||
if match := isMatchGVK(fltr.FieldSpec, obj); !match {
|
||||
return obj, nil
|
||||
}
|
||||
fltr.path = utils.PathSplitter(fltr.FieldSpec.Path, "/")
|
||||
if err := fltr.filter(obj); err != nil {
|
||||
s, _ := obj.String()
|
||||
return nil, errors.WrapPrefixf(err,
|
||||
"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() || obj.IsNil() {
|
||||
return nil
|
||||
}
|
||||
switch obj.YNode().Kind {
|
||||
case yaml.SequenceNode:
|
||||
return fltr.handleSequence(obj)
|
||||
case yaml.MappingNode:
|
||||
return fltr.handleMap(obj)
|
||||
case yaml.AliasNode:
|
||||
return fltr.filter(yaml.NewRNode(obj.YNode().Alias))
|
||||
default:
|
||||
return errors.Errorf("expected sequence or mapping node")
|
||||
}
|
||||
}
|
||||
|
||||
// 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 operation yaml.Filter
|
||||
var kind yaml.Kind
|
||||
tag := yaml.NodeTagEmpty
|
||||
switch {
|
||||
case !fltr.FieldSpec.CreateIfNotPresent || fltr.CreateKind == 0 || isSeq:
|
||||
// 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.
|
||||
kind = yaml.SequenceNode
|
||||
}
|
||||
case len(fltr.path) <= 1:
|
||||
// create the field if it is missing: use the provided node kind
|
||||
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
|
||||
operation = yaml.LookupCreate(yaml.MappingNode, fieldName)
|
||||
kind = yaml.MappingNode
|
||||
tag = yaml.NodeTagMap
|
||||
}
|
||||
|
||||
// locate (or maybe create) the field
|
||||
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
|
||||
// TODO: update yaml.LookupCreate to support this
|
||||
if field.YNode().Tag == yaml.NodeTagNull && yaml.IsCreate(kind) {
|
||||
field.YNode().Kind = kind
|
||||
field.YNode().Tag = tag
|
||||
}
|
||||
|
||||
// copy the current fltr and change the path on the copy
|
||||
var next = fltr
|
||||
// call filter for the next path element on the matching field
|
||||
next.path = fltr.path[1:]
|
||||
return next.filter(field)
|
||||
}
|
||||
|
||||
// seq calls filter on all sequence elements
|
||||
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
|
||||
// and less likely to have side effects
|
||||
// keep the entire path -- it does not contain parts for sequences
|
||||
return fltr.filter(node)
|
||||
}); err != nil {
|
||||
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) {
|
||||
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 {
|
||||
if kind := obj.GetKind(); fs.Kind != "" && fs.Kind != kind {
|
||||
// kind doesn't match
|
||||
return false
|
||||
}
|
||||
|
||||
// parse the group and version from the apiVersion field
|
||||
group, version := resid.ParseGroupVersion(obj.GetApiVersion())
|
||||
|
||||
if fs.Group != "" && fs.Group != group {
|
||||
// group doesn't match
|
||||
return false
|
||||
}
|
||||
|
||||
if fs.Version != "" && fs.Version != version {
|
||||
// version doesn't match
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
568
api/filters/fieldspec/fieldspec_test.go
Normal file
568
api/filters/fieldspec/fieldspec_test.go
Normal file
@@ -0,0 +1,568 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package fieldspec_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"sigs.k8s.io/kustomize/api/filters/fieldspec"
|
||||
"sigs.k8s.io/kustomize/api/filters/filtersutil"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
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: `
|
||||
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: `
|
||||
apiVersion: foo/v1beta1
|
||||
kind: Bar
|
||||
a:
|
||||
b: e
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
},
|
||||
},
|
||||
|
||||
"update-kind-not-match": {
|
||||
fieldSpec: `
|
||||
path: a/b
|
||||
group: foo
|
||||
kind: Bar1
|
||||
`,
|
||||
input: `
|
||||
apiVersion: foo/v1beta1
|
||||
kind: Bar2
|
||||
a:
|
||||
b: c
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: foo/v1beta1
|
||||
kind: Bar2
|
||||
a:
|
||||
b: c
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
},
|
||||
},
|
||||
|
||||
"update-group-not-match": {
|
||||
fieldSpec: `
|
||||
path: a/b
|
||||
group: foo1
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
apiVersion: foo2/v1beta1
|
||||
kind: Bar
|
||||
a:
|
||||
b: c
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: foo2/v1beta1
|
||||
kind: Bar
|
||||
a:
|
||||
b: c
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
},
|
||||
},
|
||||
|
||||
"update-version-not-match": {
|
||||
fieldSpec: `
|
||||
path: a/b
|
||||
group: foo
|
||||
version: v1beta1
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
apiVersion: foo/v1beta2
|
||||
kind: Bar
|
||||
a:
|
||||
b: c
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: foo/v1beta2
|
||||
kind: Bar
|
||||
a:
|
||||
b: c
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
},
|
||||
},
|
||||
|
||||
"bad-version": {
|
||||
fieldSpec: `
|
||||
path: a/b
|
||||
group: foo
|
||||
version: v1beta1
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
apiVersion: foo/v1beta2/something
|
||||
kind: Bar
|
||||
a:
|
||||
b: c
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: foo/v1beta2/something
|
||||
kind: Bar
|
||||
a:
|
||||
b: c
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
},
|
||||
},
|
||||
|
||||
"bad-meta": {
|
||||
fieldSpec: `
|
||||
path: a/b
|
||||
group: foo
|
||||
version: v1beta1
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
a:
|
||||
b: c
|
||||
`,
|
||||
expected: `
|
||||
a:
|
||||
b: c
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
},
|
||||
},
|
||||
|
||||
"miss-match-type": {
|
||||
fieldSpec: `
|
||||
path: a/b/c
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
kind: Bar
|
||||
a:
|
||||
b: a
|
||||
`,
|
||||
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"),
|
||||
},
|
||||
},
|
||||
|
||||
"add": {
|
||||
fieldSpec: `
|
||||
path: a/b/c/d
|
||||
group: foo
|
||||
create: true
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
apiVersion: foo/v1beta1
|
||||
kind: Bar
|
||||
a: {}
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: foo/v1beta1
|
||||
kind: Bar
|
||||
a: {b: {c: {d: e}}}
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
},
|
||||
},
|
||||
|
||||
"update-in-sequence": {
|
||||
fieldSpec: `
|
||||
path: a/b[]/c/d
|
||||
group: foo
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
apiVersion: foo/v1beta1
|
||||
kind: Bar
|
||||
a:
|
||||
b:
|
||||
- c:
|
||||
d: a
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: foo/v1beta1
|
||||
kind: Bar
|
||||
a:
|
||||
b:
|
||||
- c:
|
||||
d: e
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
},
|
||||
},
|
||||
|
||||
// Don't create a sequence
|
||||
"empty-sequence-no-create": {
|
||||
fieldSpec: `
|
||||
path: a/b[]/c/d
|
||||
group: foo
|
||||
create: true
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
apiVersion: foo/v1beta1
|
||||
kind: Bar
|
||||
a: {}
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: foo/v1beta1
|
||||
kind: Bar
|
||||
a: {}
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
},
|
||||
},
|
||||
|
||||
// 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: `
|
||||
apiVersion: foo/v1beta1
|
||||
kind: Bar
|
||||
a:
|
||||
b:
|
||||
- c: {}
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: foo/v1beta1
|
||||
kind: Bar
|
||||
a:
|
||||
b:
|
||||
- c: {d: e}
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
},
|
||||
},
|
||||
|
||||
"group v1": {
|
||||
fieldSpec: `
|
||||
path: a/b
|
||||
group: v1
|
||||
create: true
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
},
|
||||
},
|
||||
|
||||
"version v1": {
|
||||
fieldSpec: `
|
||||
path: a/b
|
||||
version: v1
|
||||
create: true
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
a:
|
||||
b: e
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
},
|
||||
},
|
||||
|
||||
"successfully set field on array entry no sequence hint": {
|
||||
fieldSpec: `
|
||||
path: spec/containers/image
|
||||
version: v1
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
spec:
|
||||
containers:
|
||||
- image: foo
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
spec:
|
||||
containers:
|
||||
- image: bar
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("bar"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
},
|
||||
},
|
||||
|
||||
"successfully set field on array entry with sequence hint": {
|
||||
fieldSpec: `
|
||||
path: spec/containers[]/image
|
||||
version: v1
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
spec:
|
||||
containers:
|
||||
- image: foo
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
spec:
|
||||
containers:
|
||||
- image: bar
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("bar"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
},
|
||||
},
|
||||
"failure to set field on array entry with sequence hint in path": {
|
||||
fieldSpec: `
|
||||
path: spec/containers[]/image
|
||||
version: v1
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
spec:
|
||||
containers:
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
spec:
|
||||
containers: []
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("bar"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
},
|
||||
},
|
||||
|
||||
"failure to set field on array entry, no sequence hint in path": {
|
||||
fieldSpec: `
|
||||
path: spec/containers/image
|
||||
version: v1
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
spec:
|
||||
containers:
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
spec:
|
||||
containers:
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("bar"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
},
|
||||
},
|
||||
"fieldname with slash '/'": {
|
||||
fieldSpec: `
|
||||
path: a/b\/c/d
|
||||
version: v1
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
a:
|
||||
b/c:
|
||||
d: foo
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
a:
|
||||
b/c:
|
||||
d: bar
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("bar"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
},
|
||||
},
|
||||
"fieldname with multiple '/'": {
|
||||
fieldSpec: `
|
||||
path: a/b\/c/d\/e/f
|
||||
version: v1
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
a:
|
||||
b/c:
|
||||
d/e:
|
||||
f: foo
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
a:
|
||||
b/c:
|
||||
d/e:
|
||||
f: bar
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("bar"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
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(tc.input),
|
||||
Writer: out,
|
||||
OmitReaderAnnotations: true,
|
||||
}
|
||||
|
||||
// run the filter
|
||||
err = kio.Pipeline{
|
||||
Inputs: []kio.Reader{rw},
|
||||
Filters: []kio.Filter{kio.FilterAll(tc.filter)},
|
||||
Outputs: []kio.Writer{rw},
|
||||
}.Execute()
|
||||
if tc.error != "" {
|
||||
if !assert.EqualError(t, err, tc.error) {
|
||||
t.FailNow()
|
||||
}
|
||||
// stop rest of test
|
||||
return
|
||||
}
|
||||
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
// check results
|
||||
if !assert.Equal(t,
|
||||
strings.TrimSpace(tc.expected),
|
||||
strings.TrimSpace(out.String())) {
|
||||
t.FailNow()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package filtersutil
|
||||
|
||||
import (
|
||||
"sort"
|
||||
)
|
||||
|
||||
// SortedMapKeys returns a sorted slice of keys to the given map.
|
||||
// Writing this function never gets old.
|
||||
func SortedMapKeys(m map[string]string) []string {
|
||||
keys := make([]string, len(m))
|
||||
i := 0
|
||||
for k := range m {
|
||||
keys[i] = k
|
||||
i++
|
||||
}
|
||||
sort.Strings(keys)
|
||||
return keys
|
||||
}
|
||||
33
api/filters/filtersutil/setters.go
Normal file
33
api/filters/filtersutil/setters.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package filtersutil
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
// SetFn is a function that accepts an RNode to possibly modify.
|
||||
type SetFn func(*yaml.RNode) error
|
||||
|
||||
// SetScalar returns a SetFn to set a scalar value
|
||||
func SetScalar(value string) SetFn {
|
||||
return func(node *yaml.RNode) error {
|
||||
return node.PipeE(yaml.FieldSetter{StringValue: value})
|
||||
}
|
||||
}
|
||||
|
||||
// SetEntry returns a SetFn to set an entry in a map
|
||||
func SetEntry(key, value, tag string) SetFn {
|
||||
n := &yaml.Node{
|
||||
Kind: yaml.ScalarNode,
|
||||
Value: value,
|
||||
Tag: tag,
|
||||
}
|
||||
if tag == yaml.NodeTagString && yaml.IsYaml1_1NonString(n) {
|
||||
n.Style = yaml.DoubleQuotedStyle
|
||||
}
|
||||
return func(node *yaml.RNode) error {
|
||||
return node.PipeE(yaml.FieldSetter{
|
||||
Name: key,
|
||||
Value: yaml.NewRNode(n),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package fsslice contains a yaml.Filter to modify a resource using an
|
||||
// FsSlice to identify fields to be updated within the resource.
|
||||
// Package fsslice contains a yaml.Filter to modify a resource if
|
||||
// it matches one or more FieldSpec entries.
|
||||
package fsslice
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"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"
|
||||
@@ -30,7 +31,7 @@ metadata:
|
||||
}
|
||||
fltr := fsslice.Filter{
|
||||
CreateKind: yaml.ScalarNode,
|
||||
SetValue: fsslice.SetScalar("green"),
|
||||
SetValue: filtersutil.SetScalar("green"),
|
||||
FsSlice: []types.FieldSpec{
|
||||
{Path: "a/b", CreateIfNotPresent: true},
|
||||
},
|
||||
|
||||
@@ -1,155 +0,0 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package fsslice
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/errors"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
// fieldSpecFilter applies a single fieldSpec to a single object
|
||||
// fieldSpecFilter stores internal state and should not be reused
|
||||
type fieldSpecFilter struct {
|
||||
// FieldSpec contains the path to the value to set.
|
||||
FieldSpec types.FieldSpec `yaml:"fieldSpec"`
|
||||
|
||||
// Set the field using this function
|
||||
SetValue SetFn
|
||||
|
||||
// CreateKind defines the type of node to create if the field is not found
|
||||
CreateKind yaml.Kind
|
||||
|
||||
CreateTag string
|
||||
|
||||
// path keeps internal state about the current path
|
||||
path []string
|
||||
}
|
||||
|
||||
func (fltr fieldSpecFilter) 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)
|
||||
}
|
||||
fltr.path = strings.Split(fltr.FieldSpec.Path, "/")
|
||||
if err := fltr.filter(obj); err != nil {
|
||||
s, _ := obj.String()
|
||||
return nil, errors.WrapPrefixf(err,
|
||||
"obj %v at path %v", s, fltr.FieldSpec.Path)
|
||||
}
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
func (fltr fieldSpecFilter) filter(obj *yaml.RNode) error {
|
||||
if len(fltr.path) == 0 {
|
||||
// found the field -- set its value
|
||||
return fltr.SetValue(obj)
|
||||
}
|
||||
switch obj.YNode().Kind {
|
||||
case yaml.SequenceNode:
|
||||
return fltr.seq(obj)
|
||||
case yaml.MappingNode:
|
||||
return fltr.field(obj)
|
||||
}
|
||||
// not found -- this might be an error since the type doesn't match
|
||||
|
||||
return errors.Errorf("unsupported yaml node")
|
||||
}
|
||||
|
||||
// field calls filter on the field matching the next path element
|
||||
func (fltr fieldSpecFilter) field(obj *yaml.RNode) error {
|
||||
fieldName, isSeq := isSequenceField(fltr.path[0])
|
||||
|
||||
// lookup the field matching the next path element
|
||||
var lookupField yaml.Filter
|
||||
var kind yaml.Kind
|
||||
var tag string
|
||||
switch {
|
||||
case !fltr.FieldSpec.CreateIfNotPresent || fltr.CreateKind == 0 || isSeq:
|
||||
// dont' create the field if we don't find it
|
||||
lookupField = yaml.Lookup(fieldName)
|
||||
case len(fltr.path) <= 1:
|
||||
// create the field if it is missing: use the provided node kind
|
||||
lookupField = 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)
|
||||
kind = yaml.MappingNode
|
||||
tag = "!!map"
|
||||
}
|
||||
|
||||
// locate (or maybe create) the field
|
||||
field, err := obj.Pipe(lookupField)
|
||||
if err != nil || field == nil {
|
||||
return errors.WrapPrefixf(err, "fieldName: %s", fieldName)
|
||||
}
|
||||
|
||||
// if the value exists, but is null, then change it to the creation type
|
||||
// TODO: update yaml.LookupCreate to support this
|
||||
if field.YNode().Tag == "!!null" {
|
||||
field.YNode().Kind = kind
|
||||
field.YNode().Tag = tag
|
||||
}
|
||||
|
||||
// copy the current fltr and change the path on the copy
|
||||
var next = fltr
|
||||
// call filter for the next path element on the matching field
|
||||
next.path = fltr.path[1:]
|
||||
return next.filter(field)
|
||||
}
|
||||
|
||||
// seq calls filter on all sequence elements
|
||||
func (fltr fieldSpecFilter) seq(obj *yaml.RNode) error {
|
||||
if err := obj.VisitElements(func(node *yaml.RNode) error {
|
||||
// recurse on each element -- re-allocating a fieldSpecFilter is
|
||||
// not strictly required, but is more consistent with field
|
||||
// and less likely to have side effects
|
||||
// keep the entire path -- it does not contain parts for sequences
|
||||
return fltr.filter(node)
|
||||
}); err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// kind doesn't match
|
||||
return false, err
|
||||
}
|
||||
|
||||
// parse the group and version from the apiVersion field
|
||||
group, version := parseGV(meta.APIVersion)
|
||||
|
||||
if fs.Group != "" && fs.Group != group {
|
||||
// group doesn't match
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if fs.Version != "" && fs.Version != version {
|
||||
// version doesn't match
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
@@ -4,48 +4,22 @@
|
||||
package fsslice
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/api/filters/fieldspec"
|
||||
"sigs.k8s.io/kustomize/api/filters/filtersutil"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
// SetFn sets a value
|
||||
type SetFn func(*yaml.RNode) error
|
||||
|
||||
// SetScalar returns a SetFn to set a scalar value
|
||||
func SetScalar(value string) SetFn {
|
||||
return func(node *yaml.RNode) error {
|
||||
return node.PipeE(yaml.FieldSetter{StringValue: value})
|
||||
}
|
||||
}
|
||||
|
||||
// SetEntry returns a SetFn to set an entry in a map
|
||||
func SetEntry(key, value, tag string) SetFn {
|
||||
n := &yaml.Node{
|
||||
Kind: yaml.ScalarNode,
|
||||
Value: value,
|
||||
Tag: tag,
|
||||
}
|
||||
if tag == yaml.StringTag && yaml.IsYaml1_1NonString(n) {
|
||||
n.Style = yaml.DoubleQuotedStyle
|
||||
}
|
||||
return func(node *yaml.RNode) error {
|
||||
return node.PipeE(yaml.FieldSetter{
|
||||
Name: key,
|
||||
Value: yaml.NewRNode(n),
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var _ yaml.Filter = Filter{}
|
||||
|
||||
// Filter uses an FsSlice to modify fields on a single object
|
||||
// Filter ranges over an FsSlice to modify fields on a single object.
|
||||
// An FsSlice is a range of FieldSpecs. A FieldSpec is a GVK plus a path.
|
||||
type Filter struct {
|
||||
// FieldSpecList list of FieldSpecs to set
|
||||
FsSlice types.FsSlice `yaml:"fsSlice"`
|
||||
|
||||
// SetValue is called on each field that matches one of the FieldSpecs
|
||||
SetValue SetFn
|
||||
SetValue filtersutil.SetFn
|
||||
|
||||
// CreateKind is used to create fields that do not exist
|
||||
CreateKind yaml.Kind
|
||||
@@ -59,7 +33,7 @@ func (fltr Filter) Filter(obj *yaml.RNode) (*yaml.RNode, error) {
|
||||
// apply this FieldSpec
|
||||
// create a new filter for each iteration because they
|
||||
// store internal state about the field paths
|
||||
_, err := (&fieldSpecFilter{
|
||||
_, err := (&fieldspec.Filter{
|
||||
FieldSpec: fltr.FsSlice[i],
|
||||
SetValue: fltr.SetValue,
|
||||
CreateKind: fltr.CreateKind,
|
||||
|
||||
@@ -9,335 +9,77 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"sigs.k8s.io/kustomize/api/filters/fsslice"
|
||||
"sigs.k8s.io/kustomize/api/filters/filtersutil"
|
||||
. "sigs.k8s.io/kustomize/api/filters/fsslice"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
type TestCase struct {
|
||||
name string
|
||||
input string
|
||||
expected string
|
||||
filter fsslice.Filter
|
||||
filter Filter
|
||||
fsSlice string
|
||||
error string
|
||||
}
|
||||
|
||||
var tests = []TestCase{
|
||||
{
|
||||
name: "update",
|
||||
var tests = map[string]TestCase{
|
||||
"empty": {
|
||||
fsSlice: `
|
||||
- path: a/b
|
||||
group: foo
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
apiVersion: foo/v1beta1
|
||||
apiVersion: foo/v1
|
||||
kind: Bar
|
||||
a:
|
||||
b: c
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: foo/v1beta1
|
||||
apiVersion: foo/v1
|
||||
kind: Bar
|
||||
a:
|
||||
b: e
|
||||
`,
|
||||
filter: fsslice.Filter{
|
||||
SetValue: fsslice.SetScalar("e"),
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "update-kind-not-match",
|
||||
fsSlice: `
|
||||
- path: a/b
|
||||
group: foo
|
||||
kind: Bar1
|
||||
`,
|
||||
input: `
|
||||
apiVersion: foo/v1beta1
|
||||
kind: Bar2
|
||||
a:
|
||||
b: c
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: foo/v1beta1
|
||||
kind: Bar2
|
||||
a:
|
||||
b: c
|
||||
`,
|
||||
filter: fsslice.Filter{
|
||||
SetValue: fsslice.SetScalar("e"),
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "update-group-not-match",
|
||||
fsSlice: `
|
||||
- path: a/b
|
||||
group: foo1
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
apiVersion: foo2/v1beta1
|
||||
kind: Bar
|
||||
a:
|
||||
b: c
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: foo2/v1beta1
|
||||
kind: Bar
|
||||
a:
|
||||
b: c
|
||||
`,
|
||||
filter: fsslice.Filter{
|
||||
SetValue: fsslice.SetScalar("e"),
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "update-version-not-match",
|
||||
fsSlice: `
|
||||
- path: a/b
|
||||
group: foo
|
||||
version: v1beta1
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
apiVersion: foo/v1beta2
|
||||
kind: Bar
|
||||
a:
|
||||
b: c
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: foo/v1beta2
|
||||
kind: Bar
|
||||
a:
|
||||
b: c
|
||||
`,
|
||||
filter: fsslice.Filter{
|
||||
SetValue: fsslice.SetScalar("e"),
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "bad-version",
|
||||
fsSlice: `
|
||||
- path: a/b
|
||||
group: foo
|
||||
version: v1beta1
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
apiVersion: foo/v1beta2/something
|
||||
kind: Bar
|
||||
a:
|
||||
b: c
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: foo/v1beta2/something
|
||||
kind: Bar
|
||||
a:
|
||||
b: c
|
||||
`,
|
||||
filter: fsslice.Filter{
|
||||
SetValue: fsslice.SetScalar("e"),
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "bad-meta",
|
||||
fsSlice: `
|
||||
- path: a/b
|
||||
group: foo
|
||||
version: v1beta1
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
a:
|
||||
b: c
|
||||
`,
|
||||
filter: fsslice.Filter{
|
||||
SetValue: fsslice.SetScalar("e"),
|
||||
},
|
||||
error: "missing Resource metadata",
|
||||
},
|
||||
|
||||
{
|
||||
name: "miss-match-type",
|
||||
fsSlice: `
|
||||
- path: a/b/c
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
kind: Bar
|
||||
a:
|
||||
b: a
|
||||
`,
|
||||
error: "obj kind: Bar\na:\n b: a\n at path a/b/c: unsupported yaml node",
|
||||
filter: fsslice.Filter{
|
||||
SetValue: fsslice.SetScalar("e"),
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "add",
|
||||
fsSlice: `
|
||||
- path: a/b/c/d
|
||||
group: foo
|
||||
create: true
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
apiVersion: foo/v1beta1
|
||||
kind: Bar
|
||||
a: {}
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: foo/v1beta1
|
||||
kind: Bar
|
||||
a: {b: {c: {d: e}}}
|
||||
`,
|
||||
filter: fsslice.Filter{
|
||||
SetValue: fsslice.SetScalar("e"),
|
||||
filter: Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "update-in-sequence",
|
||||
fsSlice: `
|
||||
- path: a/b[]/c/d
|
||||
group: foo
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
apiVersion: foo/v1beta1
|
||||
kind: Bar
|
||||
a:
|
||||
b:
|
||||
- c:
|
||||
d: a
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: foo/v1beta1
|
||||
kind: Bar
|
||||
a:
|
||||
b:
|
||||
- c:
|
||||
d: e
|
||||
`,
|
||||
filter: fsslice.Filter{
|
||||
SetValue: fsslice.SetScalar("e"),
|
||||
},
|
||||
},
|
||||
|
||||
// Don't create a sequence
|
||||
{
|
||||
name: "empty-sequence-no-create",
|
||||
fsSlice: `
|
||||
- path: a/b[]/c/d
|
||||
group: foo
|
||||
create: true
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
apiVersion: foo/v1beta1
|
||||
kind: Bar
|
||||
a: {}
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: foo/v1beta1
|
||||
kind: Bar
|
||||
a: {}
|
||||
`,
|
||||
filter: fsslice.Filter{
|
||||
SetValue: fsslice.SetScalar("e"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
},
|
||||
},
|
||||
|
||||
// Create a new field for an element in a sequence
|
||||
{
|
||||
name: "empty-sequence-create",
|
||||
fsSlice: `
|
||||
- path: a/b[]/c/d
|
||||
group: foo
|
||||
create: true
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
apiVersion: foo/v1beta1
|
||||
kind: Bar
|
||||
a:
|
||||
b:
|
||||
- c: {}
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: foo/v1beta1
|
||||
kind: Bar
|
||||
a:
|
||||
b:
|
||||
- c: {d: e}
|
||||
`,
|
||||
filter: fsslice.Filter{
|
||||
SetValue: fsslice.SetScalar("e"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "group v1",
|
||||
"two": {
|
||||
fsSlice: `
|
||||
- path: a/b
|
||||
group: v1
|
||||
group: foo
|
||||
version: v1
|
||||
create: true
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
`,
|
||||
filter: fsslice.Filter{
|
||||
SetValue: fsslice.SetScalar("e"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "version v1",
|
||||
fsSlice: `
|
||||
- path: a/b
|
||||
- path: q/r[]/s/t
|
||||
group: foo
|
||||
version: v1
|
||||
create: true
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
apiVersion: v1
|
||||
apiVersion: foo/v1
|
||||
kind: Bar
|
||||
q:
|
||||
r:
|
||||
- s: {}
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: v1
|
||||
apiVersion: foo/v1
|
||||
kind: Bar
|
||||
q:
|
||||
r:
|
||||
- s: {t: e}
|
||||
a:
|
||||
b: e
|
||||
`,
|
||||
filter: fsslice.Filter{
|
||||
SetValue: fsslice.SetScalar("e"),
|
||||
filter: Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestFilter_Filter(t *testing.T) {
|
||||
for i := range tests {
|
||||
test := tests[i]
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
func TestFilter(t *testing.T) {
|
||||
for name := range tests {
|
||||
test := tests[name]
|
||||
t.Run(name, func(t *testing.T) {
|
||||
err := yaml.Unmarshal([]byte(test.fsSlice), &test.filter.FsSlice)
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
package fsslice
|
||||
|
||||
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 fsslice
|
||||
|
||||
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,17 +4,24 @@
|
||||
package imagetag
|
||||
|
||||
import (
|
||||
"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/yaml"
|
||||
)
|
||||
|
||||
// Filter modifies an "image tag", the value used to specify the
|
||||
// name, tag, version digest etc. of (docker) container images
|
||||
// used by a pod template.
|
||||
type Filter struct {
|
||||
// imageTag is the tag we want to apply to the inputs
|
||||
// The name of the image is used as a key, and other fields
|
||||
// can specify a new name, tag, etc.
|
||||
ImageTag types.Image `json:"imageTag,omitempty" yaml:"imageTag,omitempty"`
|
||||
|
||||
// FsSlice contains the FieldSpecs to locate the namespace field
|
||||
// FsSlice contains the FieldSpecs to locate an image field,
|
||||
// e.g. Path: "spec/myContainers[]/image"
|
||||
FsSlice types.FsSlice `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
|
||||
}
|
||||
|
||||
@@ -26,6 +33,12 @@ func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||
}
|
||||
|
||||
func (f Filter) filter(node *yaml.RNode) (*yaml.RNode, error) {
|
||||
// FsSlice is an allowlist, not a denyList, so to deny
|
||||
// something via configuration a new config mechanism is
|
||||
// needed. Until then, hardcode it.
|
||||
if f.isOnDenyList(node) {
|
||||
return node, nil
|
||||
}
|
||||
if err := node.PipeE(fsslice.Filter{
|
||||
FsSlice: f.FsSlice,
|
||||
SetValue: updateImageTagFn(f.ImageTag),
|
||||
@@ -35,7 +48,19 @@ func (f Filter) filter(node *yaml.RNode) (*yaml.RNode, error) {
|
||||
return node, nil
|
||||
}
|
||||
|
||||
func updateImageTagFn(imageTag types.Image) fsslice.SetFn {
|
||||
func (f Filter) isOnDenyList(node *yaml.RNode) bool {
|
||||
meta, err := node.GetMeta()
|
||||
if err != nil {
|
||||
// A missing 'meta' field will cause problems elsewhere;
|
||||
// ignore it here to keep the signature simple.
|
||||
return false
|
||||
}
|
||||
// Ignore CRDs
|
||||
// https://github.com/kubernetes-sigs/kustomize/issues/890
|
||||
return meta.Kind == `CustomResourceDefinition`
|
||||
}
|
||||
|
||||
func updateImageTagFn(imageTag types.Image) filtersutil.SetFn {
|
||||
return func(node *yaml.RNode) error {
|
||||
return node.PipeE(imageTagUpdater{
|
||||
ImageTag: imageTag,
|
||||
|
||||
@@ -19,6 +19,162 @@ func TestImageTagUpdater_Filter(t *testing.T) {
|
||||
filter Filter
|
||||
fsSlice types.FsSlice
|
||||
}{
|
||||
"ignore CustomResourceDefinition": {
|
||||
input: `
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: whatever
|
||||
spec:
|
||||
containers:
|
||||
- image: whatever
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: whatever
|
||||
spec:
|
||||
containers:
|
||||
- image: whatever
|
||||
`,
|
||||
filter: Filter{
|
||||
ImageTag: types.Image{
|
||||
Name: "whatever",
|
||||
NewName: "theImageShouldNotChangeInACrd",
|
||||
},
|
||||
},
|
||||
fsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "spec/containers/image",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
"legacy multiple images in containers": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx:1.2.1
|
||||
- image: nginx:2.1.2
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
spec:
|
||||
containers:
|
||||
- image: apache@12345
|
||||
- image: apache@12345
|
||||
`,
|
||||
filter: Filter{
|
||||
ImageTag: types.Image{
|
||||
Name: "nginx",
|
||||
NewName: "apache",
|
||||
Digest: "12345",
|
||||
},
|
||||
},
|
||||
fsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "spec/containers/image",
|
||||
},
|
||||
},
|
||||
},
|
||||
"legacy both containers and initContainers": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx:1.2.1
|
||||
- image: tomcat:1.2.3
|
||||
initContainers:
|
||||
- image: nginx:1.2.1
|
||||
- image: apache:1.2.3
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
spec:
|
||||
containers:
|
||||
- image: apache:3.2.1
|
||||
- image: tomcat:1.2.3
|
||||
initContainers:
|
||||
- image: apache:3.2.1
|
||||
- image: apache:1.2.3
|
||||
`,
|
||||
filter: Filter{
|
||||
ImageTag: types.Image{
|
||||
Name: "nginx",
|
||||
NewName: "apache",
|
||||
NewTag: "3.2.1",
|
||||
},
|
||||
},
|
||||
fsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "spec/containers/image",
|
||||
},
|
||||
{
|
||||
Path: "spec/initContainers/image",
|
||||
},
|
||||
},
|
||||
},
|
||||
"legacy updates at multiple depths": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx:1.2.1
|
||||
- image: tomcat:1.2.3
|
||||
template:
|
||||
spec:
|
||||
initContainers:
|
||||
- image: nginx:1.2.1
|
||||
- image: apache:1.2.3
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
spec:
|
||||
containers:
|
||||
- image: apache:3.2.1
|
||||
- image: tomcat:1.2.3
|
||||
template:
|
||||
spec:
|
||||
initContainers:
|
||||
- image: apache:3.2.1
|
||||
- image: apache:1.2.3
|
||||
`,
|
||||
filter: Filter{
|
||||
ImageTag: types.Image{
|
||||
Name: "nginx",
|
||||
NewName: "apache",
|
||||
NewTag: "3.2.1",
|
||||
},
|
||||
},
|
||||
fsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "spec/containers/image",
|
||||
},
|
||||
{
|
||||
Path: "spec/template/spec/initContainers/image",
|
||||
},
|
||||
},
|
||||
},
|
||||
"update with digest": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
@@ -49,6 +205,7 @@ spec:
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
"multiple matches in sequence": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
@@ -85,6 +242,422 @@ spec:
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
"new Tag": {
|
||||
input: `
|
||||
group: apps
|
||||
apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy1
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx:1.7.9
|
||||
name: nginx-tagged
|
||||
- image: nginx:latest
|
||||
name: nginx-latest
|
||||
- image: foobar:1
|
||||
name: replaced-with-digest
|
||||
- image: postgres:1.8.0
|
||||
name: postgresdb
|
||||
initContainers:
|
||||
- image: nginx
|
||||
name: nginx-notag
|
||||
- image: nginx@sha256:111111111111111111
|
||||
name: nginx-sha256
|
||||
- image: alpine:1.8.0
|
||||
name: init-alpine`,
|
||||
expectedOutput: `
|
||||
group: apps
|
||||
apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy1
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx:v2
|
||||
name: nginx-tagged
|
||||
- image: nginx:v2
|
||||
name: nginx-latest
|
||||
- image: foobar:1
|
||||
name: replaced-with-digest
|
||||
- image: postgres:1.8.0
|
||||
name: postgresdb
|
||||
initContainers:
|
||||
- image: nginx:v2
|
||||
name: nginx-notag
|
||||
- image: nginx:v2
|
||||
name: nginx-sha256
|
||||
- image: alpine:1.8.0
|
||||
name: init-alpine
|
||||
`,
|
||||
filter: Filter{
|
||||
ImageTag: types.Image{
|
||||
Name: "nginx",
|
||||
NewTag: "v2",
|
||||
},
|
||||
},
|
||||
fsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "spec/template/spec/containers[]/image",
|
||||
},
|
||||
{
|
||||
Path: "spec/template/spec/initContainers[]/image",
|
||||
},
|
||||
},
|
||||
},
|
||||
"newImage": {
|
||||
input: `
|
||||
group: apps
|
||||
apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy1
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx:1.7.9
|
||||
name: nginx-tagged
|
||||
- image: nginx:latest
|
||||
name: nginx-latest
|
||||
- image: foobar:1
|
||||
name: replaced-with-digest
|
||||
- image: postgres:1.8.0
|
||||
name: postgresdb
|
||||
initContainers:
|
||||
- image: nginx
|
||||
name: nginx-notag
|
||||
- image: nginx@sha256:111111111111111111
|
||||
name: nginx-sha256
|
||||
- image: alpine:1.8.0
|
||||
name: init-alpine
|
||||
`,
|
||||
expectedOutput: `
|
||||
group: apps
|
||||
apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy1
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: busybox:1.7.9
|
||||
name: nginx-tagged
|
||||
- image: busybox:latest
|
||||
name: nginx-latest
|
||||
- image: foobar:1
|
||||
name: replaced-with-digest
|
||||
- image: postgres:1.8.0
|
||||
name: postgresdb
|
||||
initContainers:
|
||||
- image: busybox
|
||||
name: nginx-notag
|
||||
- image: busybox@sha256:111111111111111111
|
||||
name: nginx-sha256
|
||||
- image: alpine:1.8.0
|
||||
name: init-alpine
|
||||
`,
|
||||
filter: Filter{
|
||||
ImageTag: types.Image{
|
||||
Name: "nginx",
|
||||
NewName: "busybox",
|
||||
},
|
||||
},
|
||||
fsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "spec/template/spec/containers[]/image",
|
||||
},
|
||||
{
|
||||
Path: "spec/template/spec/initContainers[]/image",
|
||||
},
|
||||
},
|
||||
},
|
||||
"newImageAndTag": {
|
||||
input: `
|
||||
group: apps
|
||||
apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy1
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx:1.7.9
|
||||
name: nginx-tagged
|
||||
- image: nginx:latest
|
||||
name: nginx-latest
|
||||
- image: foobar:1
|
||||
name: replaced-with-digest
|
||||
- image: postgres:1.8.0
|
||||
name: postgresdb
|
||||
initContainers:
|
||||
- image: nginx
|
||||
name: nginx-notag
|
||||
- image: nginx@sha256:111111111111111111
|
||||
name: nginx-sha256
|
||||
- image: alpine:1.8.0
|
||||
name: init-alpine
|
||||
`,
|
||||
expectedOutput: `
|
||||
group: apps
|
||||
apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy1
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: busybox:v3
|
||||
name: nginx-tagged
|
||||
- image: busybox:v3
|
||||
name: nginx-latest
|
||||
- image: foobar:1
|
||||
name: replaced-with-digest
|
||||
- image: postgres:1.8.0
|
||||
name: postgresdb
|
||||
initContainers:
|
||||
- image: busybox:v3
|
||||
name: nginx-notag
|
||||
- image: busybox:v3
|
||||
name: nginx-sha256
|
||||
- image: alpine:1.8.0
|
||||
name: init-alpine
|
||||
`,
|
||||
filter: Filter{
|
||||
ImageTag: types.Image{
|
||||
Name: "nginx",
|
||||
NewName: "busybox",
|
||||
NewTag: "v3",
|
||||
},
|
||||
},
|
||||
fsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "spec/template/spec/containers[]/image",
|
||||
},
|
||||
{
|
||||
Path: "spec/template/spec/initContainers[]/image",
|
||||
},
|
||||
},
|
||||
},
|
||||
"newDigest": {
|
||||
input: `
|
||||
group: apps
|
||||
apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy1
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx:1.7.9
|
||||
name: nginx-tagged
|
||||
- image: nginx:latest
|
||||
name: nginx-latest
|
||||
- image: foobar:1
|
||||
name: replaced-with-digest
|
||||
- image: postgres:1.8.0
|
||||
name: postgresdb
|
||||
initContainers:
|
||||
- image: nginx
|
||||
name: nginx-notag
|
||||
- image: nginx@sha256:111111111111111111
|
||||
name: nginx-sha256
|
||||
- image: alpine:1.8.0
|
||||
name: init-alpine
|
||||
`,
|
||||
expectedOutput: `
|
||||
group: apps
|
||||
apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy1
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx@sha256:222222222222222222
|
||||
name: nginx-tagged
|
||||
- image: nginx@sha256:222222222222222222
|
||||
name: nginx-latest
|
||||
- image: foobar:1
|
||||
name: replaced-with-digest
|
||||
- image: postgres:1.8.0
|
||||
name: postgresdb
|
||||
initContainers:
|
||||
- image: nginx@sha256:222222222222222222
|
||||
name: nginx-notag
|
||||
- image: nginx@sha256:222222222222222222
|
||||
name: nginx-sha256
|
||||
- image: alpine:1.8.0
|
||||
name: init-alpine
|
||||
`,
|
||||
filter: Filter{
|
||||
ImageTag: types.Image{
|
||||
Name: "nginx",
|
||||
Digest: "sha256:222222222222222222",
|
||||
},
|
||||
},
|
||||
fsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "spec/template/spec/containers/image",
|
||||
},
|
||||
{
|
||||
Path: "spec/template/spec/initContainers/image",
|
||||
},
|
||||
},
|
||||
},
|
||||
"newImageAndDigest": {
|
||||
input: `
|
||||
group: apps
|
||||
apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy1
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx:1.7.9
|
||||
name: nginx-tagged
|
||||
- image: nginx:latest
|
||||
name: nginx-latest
|
||||
- image: foobar:1
|
||||
name: replaced-with-digest
|
||||
- image: postgres:1.8.0
|
||||
name: postgresdb
|
||||
initContainers:
|
||||
- image: nginx
|
||||
name: nginx-notag
|
||||
- image: nginx@sha256:111111111111111111
|
||||
name: nginx-sha256
|
||||
- image: alpine:1.8.0
|
||||
name: init-alpine
|
||||
`,
|
||||
expectedOutput: `
|
||||
group: apps
|
||||
apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy1
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: busybox@sha256:222222222222222222
|
||||
name: nginx-tagged
|
||||
- image: busybox@sha256:222222222222222222
|
||||
name: nginx-latest
|
||||
- image: foobar:1
|
||||
name: replaced-with-digest
|
||||
- image: postgres:1.8.0
|
||||
name: postgresdb
|
||||
initContainers:
|
||||
- image: busybox@sha256:222222222222222222
|
||||
name: nginx-notag
|
||||
- image: busybox@sha256:222222222222222222
|
||||
name: nginx-sha256
|
||||
- image: alpine:1.8.0
|
||||
name: init-alpine
|
||||
`,
|
||||
filter: Filter{
|
||||
ImageTag: types.Image{
|
||||
Name: "nginx",
|
||||
NewName: "busybox",
|
||||
Digest: "sha256:222222222222222222",
|
||||
},
|
||||
},
|
||||
fsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "spec/template/spec/containers[]/image",
|
||||
},
|
||||
{
|
||||
Path: "spec/template/spec/initContainers[]/image",
|
||||
},
|
||||
},
|
||||
},
|
||||
"emptyContainers": {
|
||||
input: `
|
||||
group: apps
|
||||
apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy1
|
||||
spec:
|
||||
containers:
|
||||
`,
|
||||
expectedOutput: `
|
||||
group: apps
|
||||
apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy1
|
||||
spec:
|
||||
containers: []
|
||||
`,
|
||||
filter: Filter{
|
||||
ImageTag: types.Image{
|
||||
Name: "nginx",
|
||||
NewTag: "v2",
|
||||
},
|
||||
},
|
||||
fsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "spec/containers[]/image",
|
||||
// CreateIfNotPresent: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
"tagWithBraces": {
|
||||
input: `
|
||||
group: apps
|
||||
apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy1
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: some.registry.io/my-image:{GENERATED_TAG}
|
||||
name: my-image
|
||||
`,
|
||||
expectedOutput: `
|
||||
group: apps
|
||||
apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy1
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: some.registry.io/my-image:my-fixed-tag
|
||||
name: my-image
|
||||
`,
|
||||
filter: Filter{
|
||||
ImageTag: types.Image{
|
||||
Name: "some.registry.io/my-image",
|
||||
NewTag: "my-fixed-tag",
|
||||
},
|
||||
},
|
||||
fsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "spec/template/spec/containers[]/image",
|
||||
},
|
||||
{
|
||||
Path: "spec/template/spec/initContainers[]/image",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for tn, tc := range testCases {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -25,15 +25,16 @@ type Filter struct {
|
||||
var _ kio.Filter = Filter{}
|
||||
|
||||
func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||
keys := filtersutil.SortedMapKeys(f.Labels)
|
||||
keys := yaml.SortedMapKeys(f.Labels)
|
||||
_, err := kio.FilterAll(yaml.FilterFunc(
|
||||
func(node *yaml.RNode) (*yaml.RNode, error) {
|
||||
for _, k := range keys {
|
||||
if err := node.PipeE(fsslice.Filter{
|
||||
FsSlice: f.FsSlice,
|
||||
SetValue: fsslice.SetEntry(k, f.Labels[k], yaml.StringTag),
|
||||
FsSlice: f.FsSlice,
|
||||
SetValue: filtersutil.SetEntry(
|
||||
k, f.Labels[k], yaml.NodeTagString),
|
||||
CreateKind: yaml.MappingNode, // Labels are MappingNodes.
|
||||
CreateTag: "!!map",
|
||||
CreateTag: yaml.NodeTagMap,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
3
api/filters/nameref/doc.go
Normal file
3
api/filters/nameref/doc.go
Normal file
@@ -0,0 +1,3 @@
|
||||
// Package nameref contains a kio.Filter implementation of the kustomize
|
||||
// name reference transformer.
|
||||
package nameref
|
||||
396
api/filters/nameref/nameref.go
Normal file
396
api/filters/nameref/nameref.go
Normal file
@@ -0,0 +1,396 @@
|
||||
package nameref
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"sigs.k8s.io/kustomize/api/filters/fieldspec"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/resource"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"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 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.
|
||||
// This is the field to write.
|
||||
NameFieldToUpdate types.FieldSpec
|
||||
|
||||
// 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 scan to find the ReferralTarget.
|
||||
ReferralCandidates resmap.ResMap
|
||||
}
|
||||
|
||||
// At time of writing, in practice this is called with a slice with only
|
||||
// one entry, the node also referred to be the resource in the Referrer field.
|
||||
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;
|
||||
// that's how the referrer's name field is updated.
|
||||
// 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) {
|
||||
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,
|
||||
}); 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
|
||||
}
|
||||
switch node.YNode().Kind {
|
||||
case yaml.ScalarNode:
|
||||
return f.setScalar(node)
|
||||
case yaml.MappingNode:
|
||||
return f.setMapping(node)
|
||||
case yaml.SequenceNode:
|
||||
return applyFilterToSeq(seqFilter{
|
||||
setScalarFn: f.setScalar,
|
||||
setMappingFn: f.setMapping,
|
||||
}, node)
|
||||
default:
|
||||
return fmt.Errorf("node must be a scalar, sequence or map")
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return errors.Wrap(err, "trying to match 'name' field")
|
||||
}
|
||||
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")
|
||||
}
|
||||
candidates, err := f.filterMapCandidatesByNamespace(node)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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
|
||||
}
|
||||
if err = node.PipeE(yaml.FieldSetter{
|
||||
Name: "name",
|
||||
StringValue: referral.GetName(),
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
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 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 {
|
||||
referral, err := f.selectReferral(
|
||||
node.YNode().Value, f.ReferralCandidates.Resources())
|
||||
if err != nil || referral == nil {
|
||||
// Nil referral means nothing to do.
|
||||
return err
|
||||
}
|
||||
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: referral.GetName()})
|
||||
}
|
||||
|
||||
// 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(n *resource.Resource) (*resid.Gvk, error) {
|
||||
roleRef, err := n.Pipe(yaml.Lookup("roleRef"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if roleRef.IsNil() {
|
||||
return nil, fmt.Errorf("roleRef cannot be found in %s", n.MustString())
|
||||
}
|
||||
apiGroup, err := roleRef.Pipe(yaml.Lookup("apiGroup"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if apiGroup.IsNil() {
|
||||
return nil, fmt.Errorf(
|
||||
"apiGroup cannot be found in roleRef %s", roleRef.MustString())
|
||||
}
|
||||
kind, err := roleRef.Pipe(yaml.Lookup("kind"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if kind.IsNil() {
|
||||
return nil, fmt.Errorf(
|
||||
"kind cannot be found in roleRef %s", roleRef.MustString())
|
||||
}
|
||||
return &resid.Gvk{
|
||||
Group: apiGroup.YNode().Value,
|
||||
Kind: kind.YNode().Value,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
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,
|
||||
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
|
||||
}
|
||||
candidates = doSieve(candidates, prefixSuffixEquals(f.Referrer))
|
||||
if len(candidates) == 1 {
|
||||
return candidates[0], 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 (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())
|
||||
}
|
||||
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
|
||||
}
|
||||
789
api/filters/nameref/nameref_test.go
Normal file
789
api/filters/nameref/nameref_test.go
Normal file
@@ -0,0 +1,789 @@
|
||||
package nameref
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"sigs.k8s.io/kustomize/api/provider"
|
||||
"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 {
|
||||
referrerOriginal string
|
||||
candidates string
|
||||
referrerFinal string
|
||||
filter Filter
|
||||
originalNames []string
|
||||
}{
|
||||
"simple scalar": {
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
ref:
|
||||
name: oldName
|
||||
`,
|
||||
candidates: `
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: NotSecret
|
||||
metadata:
|
||||
name: newName2
|
||||
`,
|
||||
originalNames: []string{"oldName", "newName2"},
|
||||
referrerFinal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
ref:
|
||||
name: newName
|
||||
`,
|
||||
filter: Filter{
|
||||
NameFieldToUpdate: types.FieldSpec{Path: "ref/name"},
|
||||
ReferralTarget: resid.Gvk{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Secret",
|
||||
},
|
||||
},
|
||||
},
|
||||
"sequence": {
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
seq:
|
||||
- oldName1
|
||||
- oldName2
|
||||
`,
|
||||
candidates: `
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: NotSecret
|
||||
metadata:
|
||||
name: newName2
|
||||
`,
|
||||
originalNames: []string{"oldName1", "newName2"},
|
||||
referrerFinal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
seq:
|
||||
- newName
|
||||
- oldName2
|
||||
`,
|
||||
filter: Filter{
|
||||
NameFieldToUpdate: types.FieldSpec{Path: "seq"},
|
||||
ReferralTarget: resid.Gvk{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Secret",
|
||||
},
|
||||
},
|
||||
},
|
||||
"mapping": {
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
map:
|
||||
name: oldName
|
||||
`,
|
||||
candidates: `
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: NotSecret
|
||||
metadata:
|
||||
name: newName2
|
||||
`,
|
||||
originalNames: []string{"oldName", "newName2"},
|
||||
referrerFinal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
map:
|
||||
name: newName
|
||||
`,
|
||||
filter: Filter{
|
||||
NameFieldToUpdate: types.FieldSpec{Path: "map"},
|
||||
ReferralTarget: resid.Gvk{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Secret",
|
||||
},
|
||||
},
|
||||
},
|
||||
"mapping with namespace": {
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
namespace: someNs
|
||||
map:
|
||||
name: oldName
|
||||
namespace: someNs
|
||||
`,
|
||||
candidates: `
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName
|
||||
namespace: someNs
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: NotSecret
|
||||
metadata:
|
||||
name: newName2
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: thirdName
|
||||
`,
|
||||
originalNames: []string{"oldName", "oldName", "oldName"},
|
||||
referrerFinal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
namespace: someNs
|
||||
map:
|
||||
name: newName
|
||||
namespace: someNs
|
||||
`,
|
||||
filter: Filter{
|
||||
NameFieldToUpdate: types.FieldSpec{Path: "map"},
|
||||
ReferralTarget: resid.Gvk{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Secret",
|
||||
},
|
||||
},
|
||||
},
|
||||
"null value": {
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
map:
|
||||
name: null
|
||||
`,
|
||||
candidates: `
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: NotSecret
|
||||
metadata:
|
||||
name: newName2
|
||||
`,
|
||||
originalNames: []string{"oldName", "newName2"},
|
||||
referrerFinal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
map:
|
||||
name: null
|
||||
`,
|
||||
filter: Filter{
|
||||
NameFieldToUpdate: types.FieldSpec{Path: "map"},
|
||||
ReferralTarget: resid.Gvk{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Secret",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for tn, tc := range testCases {
|
||||
t.Run(tn, func(t *testing.T) {
|
||||
factory := provider.NewDefaultDepProvider().GetResourceFactory()
|
||||
referrer, err := factory.FromBytes([]byte(tc.referrerOriginal))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tc.filter.Referrer = referrer
|
||||
|
||||
resMapFactory := resmap.NewFactory(factory)
|
||||
candidatesRes, err := factory.SliceFromBytesWithNames(
|
||||
tc.originalNames, []byte(tc.candidates))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
candidates := resMapFactory.FromResourceSlice(candidatesRes)
|
||||
tc.filter.ReferralCandidates = candidates
|
||||
|
||||
result := filtertest_test.RunFilter(t, tc.referrerOriginal, tc.filter)
|
||||
if !assert.Equal(t,
|
||||
strings.TrimSpace(tc.referrerFinal),
|
||||
strings.TrimSpace(result)) {
|
||||
t.FailNow()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNamerefFilterUnhappy(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
referrerOriginal string
|
||||
candidates string
|
||||
referrerFinal string
|
||||
filter Filter
|
||||
originalNames []string
|
||||
}{
|
||||
"multiple match": {
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
ref:
|
||||
name: oldName
|
||||
`,
|
||||
candidates: `
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName2
|
||||
`,
|
||||
originalNames: []string{"oldName", "oldName"},
|
||||
referrerFinal: "",
|
||||
filter: Filter{
|
||||
NameFieldToUpdate: types.FieldSpec{Path: "ref/name"},
|
||||
ReferralTarget: resid.Gvk{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Secret",
|
||||
},
|
||||
},
|
||||
},
|
||||
"no name": {
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
ref:
|
||||
notName: oldName
|
||||
`,
|
||||
candidates: `
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName2
|
||||
`,
|
||||
originalNames: []string{"oldName", "oldName"},
|
||||
referrerFinal: "",
|
||||
filter: Filter{
|
||||
NameFieldToUpdate: types.FieldSpec{Path: "ref"},
|
||||
ReferralTarget: resid.Gvk{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Secret",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for tn, tc := range testCases {
|
||||
t.Run(tn, func(t *testing.T) {
|
||||
factory := provider.NewDefaultDepProvider().GetResourceFactory()
|
||||
referrer, err := factory.FromBytes([]byte(tc.referrerOriginal))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tc.filter.Referrer = referrer
|
||||
|
||||
resMapFactory := resmap.NewFactory(factory)
|
||||
candidatesRes, err := factory.SliceFromBytesWithNames(
|
||||
tc.originalNames, []byte(tc.candidates))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
candidates := resMapFactory.FromResourceSlice(candidatesRes)
|
||||
tc.filter.ReferralCandidates = candidates
|
||||
|
||||
_, err = filtertest_test.RunFilterE(t, tc.referrerOriginal, tc.filter)
|
||||
if err == nil {
|
||||
t.Fatalf("expect an error")
|
||||
}
|
||||
if tc.referrerFinal != "" && !assert.EqualError(t, err, tc.referrerFinal) {
|
||||
t.FailNow()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCandidatesWithDifferentPrefixSuffix(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
referrerOriginal string
|
||||
candidates string
|
||||
referrerFinal string
|
||||
filter Filter
|
||||
originalNames []string
|
||||
prefix []string
|
||||
suffix []string
|
||||
inputPrefix string
|
||||
inputSuffix string
|
||||
err bool
|
||||
}{
|
||||
"prefix match": {
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
ref:
|
||||
name: oldName
|
||||
`,
|
||||
candidates: `
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName2
|
||||
`,
|
||||
originalNames: []string{"oldName", "oldName"},
|
||||
prefix: []string{"prefix1", "prefix2"},
|
||||
suffix: []string{"", "suffix2"},
|
||||
inputPrefix: "prefix1",
|
||||
inputSuffix: "",
|
||||
referrerFinal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
ref:
|
||||
name: newName
|
||||
`,
|
||||
filter: Filter{
|
||||
NameFieldToUpdate: types.FieldSpec{Path: "ref/name"},
|
||||
ReferralTarget: resid.Gvk{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Secret",
|
||||
},
|
||||
},
|
||||
err: false,
|
||||
},
|
||||
"suffix match": {
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
ref:
|
||||
name: oldName
|
||||
`,
|
||||
candidates: `
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName2
|
||||
`,
|
||||
originalNames: []string{"oldName", "oldName"},
|
||||
prefix: []string{"", "prefix2"},
|
||||
suffix: []string{"suffix1", "suffix2"},
|
||||
inputPrefix: "",
|
||||
inputSuffix: "suffix1",
|
||||
referrerFinal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
ref:
|
||||
name: newName
|
||||
`,
|
||||
filter: Filter{
|
||||
NameFieldToUpdate: types.FieldSpec{Path: "ref/name"},
|
||||
ReferralTarget: resid.Gvk{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Secret",
|
||||
},
|
||||
},
|
||||
err: false,
|
||||
},
|
||||
"prefix suffix both match": {
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
ref:
|
||||
name: oldName
|
||||
`,
|
||||
candidates: `
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName2
|
||||
`,
|
||||
originalNames: []string{"oldName", "oldName"},
|
||||
prefix: []string{"prefix1", "prefix2"},
|
||||
suffix: []string{"suffix1", "suffix2"},
|
||||
inputPrefix: "prefix1",
|
||||
inputSuffix: "suffix1",
|
||||
referrerFinal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
ref:
|
||||
name: newName
|
||||
`,
|
||||
filter: Filter{
|
||||
NameFieldToUpdate: types.FieldSpec{Path: "ref/name"},
|
||||
ReferralTarget: resid.Gvk{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Secret",
|
||||
},
|
||||
},
|
||||
err: false,
|
||||
},
|
||||
"multiple match: both": {
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
ref:
|
||||
name: oldName
|
||||
`,
|
||||
candidates: `
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName2
|
||||
`,
|
||||
originalNames: []string{"oldName", "oldName"},
|
||||
prefix: []string{"prefix", "prefix"},
|
||||
suffix: []string{"suffix", "suffix"},
|
||||
inputPrefix: "prefix",
|
||||
inputSuffix: "suffix",
|
||||
referrerFinal: "",
|
||||
filter: Filter{
|
||||
NameFieldToUpdate: types.FieldSpec{Path: "ref/name"},
|
||||
ReferralTarget: resid.Gvk{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Secret",
|
||||
},
|
||||
},
|
||||
err: true,
|
||||
},
|
||||
"multiple match: only prefix": {
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
ref:
|
||||
name: oldName
|
||||
`,
|
||||
candidates: `
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName2
|
||||
`,
|
||||
originalNames: []string{"oldName", "oldName"},
|
||||
prefix: []string{"prefix", "prefix"},
|
||||
suffix: []string{"", ""},
|
||||
inputPrefix: "prefix",
|
||||
inputSuffix: "",
|
||||
referrerFinal: "",
|
||||
filter: Filter{
|
||||
NameFieldToUpdate: types.FieldSpec{Path: "ref/name"},
|
||||
ReferralTarget: resid.Gvk{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Secret",
|
||||
},
|
||||
},
|
||||
err: true,
|
||||
},
|
||||
"multiple match: only suffix": {
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
ref:
|
||||
name: oldName
|
||||
`,
|
||||
candidates: `
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName2
|
||||
`,
|
||||
originalNames: []string{"oldName", "oldName"},
|
||||
prefix: []string{"", ""},
|
||||
suffix: []string{"suffix", "suffix"},
|
||||
inputPrefix: "",
|
||||
inputSuffix: "suffix",
|
||||
referrerFinal: "",
|
||||
filter: Filter{
|
||||
NameFieldToUpdate: types.FieldSpec{Path: "ref/name"},
|
||||
ReferralTarget: resid.Gvk{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Secret",
|
||||
},
|
||||
},
|
||||
err: true,
|
||||
},
|
||||
"no match: neither match": {
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
ref:
|
||||
name: oldName
|
||||
`,
|
||||
candidates: `
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName2
|
||||
`,
|
||||
originalNames: []string{"oldName", "oldName"},
|
||||
prefix: []string{"prefix1", "prefix2"},
|
||||
suffix: []string{"suffix1", "suffix2"},
|
||||
inputPrefix: "prefix",
|
||||
inputSuffix: "suffix",
|
||||
referrerFinal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
ref:
|
||||
name: oldName
|
||||
`,
|
||||
filter: Filter{
|
||||
NameFieldToUpdate: types.FieldSpec{Path: "ref/name"},
|
||||
ReferralTarget: resid.Gvk{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Secret",
|
||||
},
|
||||
},
|
||||
err: false,
|
||||
},
|
||||
"no match: prefix doesn't match": {
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
ref:
|
||||
name: oldName
|
||||
`,
|
||||
candidates: `
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName2
|
||||
`,
|
||||
originalNames: []string{"oldName", "oldName"},
|
||||
prefix: []string{"prefix1", "prefix2"},
|
||||
suffix: []string{"suffix", "suffix"},
|
||||
inputPrefix: "prefix",
|
||||
inputSuffix: "suffix",
|
||||
referrerFinal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
ref:
|
||||
name: oldName
|
||||
`,
|
||||
filter: Filter{
|
||||
NameFieldToUpdate: types.FieldSpec{Path: "ref/name"},
|
||||
ReferralTarget: resid.Gvk{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Secret",
|
||||
},
|
||||
},
|
||||
err: false,
|
||||
},
|
||||
"no match: suffix doesn't match": {
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
ref:
|
||||
name: oldName
|
||||
`,
|
||||
candidates: `
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName2
|
||||
`,
|
||||
originalNames: []string{"oldName", "oldName"},
|
||||
prefix: []string{"prefix", "prefix"},
|
||||
suffix: []string{"suffix1", "suffix2"},
|
||||
inputPrefix: "prefix",
|
||||
inputSuffix: "suffix",
|
||||
referrerFinal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
ref:
|
||||
name: oldName
|
||||
`,
|
||||
filter: Filter{
|
||||
NameFieldToUpdate: types.FieldSpec{Path: "ref/name"},
|
||||
ReferralTarget: resid.Gvk{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Secret",
|
||||
},
|
||||
},
|
||||
err: false,
|
||||
},
|
||||
}
|
||||
|
||||
for tn, tc := range testCases {
|
||||
t.Run(tn, func(t *testing.T) {
|
||||
factory := provider.NewDefaultDepProvider().GetResourceFactory()
|
||||
referrer, err := factory.FromBytes([]byte(tc.referrerOriginal))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if tc.inputPrefix != "" {
|
||||
referrer.AddNamePrefix(tc.inputPrefix)
|
||||
}
|
||||
if tc.inputSuffix != "" {
|
||||
referrer.AddNameSuffix(tc.inputSuffix)
|
||||
}
|
||||
tc.filter.Referrer = referrer
|
||||
|
||||
resMapFactory := resmap.NewFactory(factory)
|
||||
candidatesRes, err := factory.SliceFromBytesWithNames(
|
||||
tc.originalNames, []byte(tc.candidates))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for i := range candidatesRes {
|
||||
if tc.prefix[i] != "" {
|
||||
candidatesRes[i].AddNamePrefix(tc.prefix[i])
|
||||
}
|
||||
if tc.suffix[i] != "" {
|
||||
candidatesRes[i].AddNameSuffix(tc.suffix[i])
|
||||
}
|
||||
}
|
||||
|
||||
candidates := resMapFactory.FromResourceSlice(candidatesRes)
|
||||
tc.filter.ReferralCandidates = candidates
|
||||
|
||||
if !tc.err {
|
||||
if !assert.Equal(t,
|
||||
strings.TrimSpace(tc.referrerFinal),
|
||||
strings.TrimSpace(
|
||||
filtertest_test.RunFilter(
|
||||
t, tc.referrerOriginal, tc.filter))) {
|
||||
t.FailNow()
|
||||
}
|
||||
} else {
|
||||
_, err := filtertest_test.RunFilterE(
|
||||
t, tc.referrerOriginal, tc.filter)
|
||||
if err == nil {
|
||||
t.Fatalf("an error is expected")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
57
api/filters/nameref/seqfilter.go
Normal file
57
api/filters/nameref/seqfilter.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package nameref
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
type setFn func(*yaml.RNode) error
|
||||
|
||||
type seqFilter struct {
|
||||
setScalarFn setFn
|
||||
setMappingFn setFn
|
||||
}
|
||||
|
||||
func (sf seqFilter) Filter(node *yaml.RNode) (*yaml.RNode, error) {
|
||||
if yaml.IsMissingOrNull(node) {
|
||||
return node, nil
|
||||
}
|
||||
switch node.YNode().Kind {
|
||||
case yaml.ScalarNode:
|
||||
// Kind: Role/ClusterRole
|
||||
// FieldSpec is rules.resourceNames
|
||||
err := sf.setScalarFn(node)
|
||||
return node, err
|
||||
case yaml.MappingNode:
|
||||
// Kind: RoleBinding/ClusterRoleBinding
|
||||
// FieldSpec is subjects
|
||||
// Note: The corresponding fieldSpec had been changed from
|
||||
// from path: subjects/name to just path: subjects. This is
|
||||
// what get mutatefield to request the mapping of the whole
|
||||
// map containing namespace and name instead of just a simple
|
||||
// string field containing the name
|
||||
err := sf.setMappingFn(node)
|
||||
return node, err
|
||||
default:
|
||||
return node, fmt.Errorf(
|
||||
"%#v is expected to be either a string or a map of string", node)
|
||||
}
|
||||
}
|
||||
|
||||
// applyFilterToSeq will apply the filter to each element in the sequence node
|
||||
func applyFilterToSeq(filter yaml.Filter, node *yaml.RNode) error {
|
||||
if node.YNode().Kind != yaml.SequenceNode {
|
||||
return fmt.Errorf("expect a sequence node but got %v", node.YNode().Kind)
|
||||
}
|
||||
|
||||
for _, elem := range node.Content() {
|
||||
rnode := yaml.NewRNode(elem)
|
||||
err := rnode.PipeE(filter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
80
api/filters/nameref/seqfilter_test.go
Normal file
80
api/filters/nameref/seqfilter_test.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package nameref
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
func SeqFilter(node *yaml.RNode) (*yaml.RNode, error) {
|
||||
if node.YNode().Value == "aaa" {
|
||||
node.YNode().SetString("ccc")
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
func TestApplyFilterToSeq(t *testing.T) {
|
||||
fltr := yaml.FilterFunc(SeqFilter)
|
||||
|
||||
testCases := map[string]struct {
|
||||
input string
|
||||
expect string
|
||||
}{
|
||||
"replace in seq": {
|
||||
input: `
|
||||
- aaa
|
||||
- bbb`,
|
||||
expect: `
|
||||
- ccc
|
||||
- bbb`,
|
||||
},
|
||||
}
|
||||
|
||||
for tn, tc := range testCases {
|
||||
t.Run(tn, func(t *testing.T) {
|
||||
node, err := yaml.Parse(tc.input)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = applyFilterToSeq(fltr, node)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !assert.Equal(t,
|
||||
strings.TrimSpace(tc.expect),
|
||||
strings.TrimSpace(node.MustString())) {
|
||||
t.Fatalf("expect:\n%s\nactual:\n%s",
|
||||
strings.TrimSpace(tc.expect),
|
||||
strings.TrimSpace(node.MustString()))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyFilterToSeqUnhappy(t *testing.T) {
|
||||
fltr := yaml.FilterFunc(SeqFilter)
|
||||
|
||||
testCases := map[string]struct {
|
||||
input string
|
||||
}{
|
||||
"replace in seq": {
|
||||
input: `
|
||||
aaa`,
|
||||
},
|
||||
}
|
||||
|
||||
for tn, tc := range testCases {
|
||||
t.Run(tn, func(t *testing.T) {
|
||||
node, err := yaml.Parse(tc.input)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = applyFilterToSeq(fltr, node)
|
||||
if err == nil {
|
||||
t.Fatalf("expect an error")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,11 @@
|
||||
package namespace
|
||||
|
||||
import (
|
||||
"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"
|
||||
)
|
||||
|
||||
@@ -42,9 +44,9 @@ func (ns Filter) run(node *yaml.RNode) (*yaml.RNode, error) {
|
||||
// transformations based on data -- :)
|
||||
err := node.PipeE(fsslice.Filter{
|
||||
FsSlice: ns.FsSlice,
|
||||
SetValue: fsslice.SetScalar(ns.Namespace),
|
||||
SetValue: filtersutil.SetScalar(ns.Namespace),
|
||||
CreateKind: yaml.ScalarNode, // Namespace is a ScalarNode
|
||||
CreateTag: yaml.StringTag,
|
||||
CreateTag: yaml.NodeTagString,
|
||||
})
|
||||
return node, err
|
||||
}
|
||||
@@ -52,36 +54,30 @@ 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
|
||||
// for the metadata.namespace field on namespace scoped resources.
|
||||
// namespace scoped resources are determined by NOT being present
|
||||
// in a blacklist of cluster-scoped resource types (by apiVersion and kind).
|
||||
// in a hard-coded list of cluster-scoped resource types (by apiVersion and kind).
|
||||
//
|
||||
// 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 := fsslice.GetGVK(meta)
|
||||
if !gvk.IsNamespaceableKind() {
|
||||
func (ns Filter) metaNamespaceHack(obj *yaml.RNode, gvk resid.Gvk) error {
|
||||
if gvk.IsClusterScoped() {
|
||||
return nil
|
||||
}
|
||||
f := fsslice.Filter{
|
||||
FsSlice: []types.FieldSpec{
|
||||
{Path: types.MetadataNamespacePath, CreateIfNotPresent: true},
|
||||
},
|
||||
SetValue: fsslice.SetScalar(ns.Namespace),
|
||||
SetValue: filtersutil.SetScalar(ns.Namespace),
|
||||
CreateKind: yaml.ScalarNode, // Namespace is a ScalarNode
|
||||
}
|
||||
_, err := f.Filter(obj)
|
||||
@@ -102,28 +98,27 @@ 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
|
||||
}
|
||||
|
||||
// Lookup the namespace field on all elements.
|
||||
// We should change the fieldspec so this isn't necessary.
|
||||
obj, err := obj.Pipe(yaml.Lookup(subjectsField))
|
||||
if err != nil || yaml.IsEmpty(obj) {
|
||||
if err != nil || yaml.IsMissingOrNull(obj) {
|
||||
return err
|
||||
}
|
||||
|
||||
// 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.
|
||||
name, err := o.Pipe(
|
||||
yaml.Lookup("name"), yaml.Match("default"),
|
||||
)
|
||||
if err != nil || yaml.IsEmpty(name) {
|
||||
if err != nil || yaml.IsMissingOrNull(name) {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -194,47 +194,47 @@ metadata:
|
||||
{
|
||||
name: "update-clusterrolebinding",
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
subjects:
|
||||
- name: default
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
subjects:
|
||||
- name: default
|
||||
namespace: foo
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
subjects:
|
||||
- name: something
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
subjects:
|
||||
- name: something
|
||||
namespace: foo
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: example.com/v1
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
subjects:
|
||||
- name: default
|
||||
namespace: bar
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
subjects:
|
||||
- name: default
|
||||
namespace: bar
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
subjects:
|
||||
- name: something
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
subjects:
|
||||
- name: something
|
||||
|
||||
@@ -15,14 +15,22 @@ type Filter struct {
|
||||
|
||||
var _ kio.Filter = Filter{}
|
||||
|
||||
// Filter does a strategic merge patch, which can delete nodes.
|
||||
func (pf Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||
var result []*yaml.RNode
|
||||
for i := range nodes {
|
||||
r, err := merge2.Merge(pf.Patch, nodes[i])
|
||||
r, err := merge2.Merge(
|
||||
pf.Patch, nodes[i],
|
||||
yaml.MergeOptions{
|
||||
ListIncreaseDirection: yaml.MergeOptionsListPrepend,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result = append(result, r)
|
||||
if r != nil {
|
||||
result = append(result, r)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -15,6 +15,101 @@ func TestFilter(t *testing.T) {
|
||||
patch *yaml.RNode
|
||||
expected string
|
||||
}{
|
||||
"simple": {
|
||||
input: `apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: clown
|
||||
spec:
|
||||
numReplicas: 1
|
||||
`,
|
||||
patch: yaml.MustParse(`apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: clown
|
||||
spec:
|
||||
numReplicas: 999
|
||||
`),
|
||||
expected: `apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: clown
|
||||
spec:
|
||||
numReplicas: 999
|
||||
`,
|
||||
},
|
||||
"nullMapEntry1": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: my-foo
|
||||
spec:
|
||||
bar:
|
||||
B:
|
||||
C: Z
|
||||
`,
|
||||
patch: yaml.MustParse(`
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: my-foo
|
||||
spec:
|
||||
bar:
|
||||
C: Z
|
||||
D: W
|
||||
baz:
|
||||
hello: world
|
||||
`),
|
||||
expected: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: my-foo
|
||||
spec:
|
||||
bar:
|
||||
C: Z
|
||||
D: W
|
||||
baz:
|
||||
hello: world
|
||||
`,
|
||||
},
|
||||
"nullMapEntry2": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: my-foo
|
||||
spec:
|
||||
bar:
|
||||
C: Z
|
||||
D: W
|
||||
baz:
|
||||
hello: world
|
||||
`,
|
||||
patch: yaml.MustParse(`
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: my-foo
|
||||
spec:
|
||||
bar:
|
||||
B:
|
||||
C: Z
|
||||
`),
|
||||
expected: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: my-foo
|
||||
spec:
|
||||
bar:
|
||||
C: Z
|
||||
D: W
|
||||
baz:
|
||||
hello: world
|
||||
`,
|
||||
},
|
||||
"simple patch": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
@@ -67,10 +162,10 @@ spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: foo0
|
||||
- name: foo1
|
||||
- name: foo2
|
||||
- name: foo3
|
||||
- name: foo0
|
||||
`,
|
||||
},
|
||||
"volumes patch": {
|
||||
@@ -107,10 +202,10 @@ spec:
|
||||
template:
|
||||
spec:
|
||||
volumes:
|
||||
- name: foo0
|
||||
- name: foo1
|
||||
- name: foo2
|
||||
- name: foo3
|
||||
- name: foo0
|
||||
`,
|
||||
},
|
||||
"nested patch": {
|
||||
@@ -142,6 +237,498 @@ spec:
|
||||
- name: nginx
|
||||
args:
|
||||
- def
|
||||
`,
|
||||
},
|
||||
"remove mapping - directive": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: test
|
||||
image: test
|
||||
`,
|
||||
patch: yaml.MustParse(`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: test
|
||||
image: test
|
||||
$patch: delete
|
||||
`),
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers: []
|
||||
`,
|
||||
},
|
||||
"replace mapping - directive": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: test
|
||||
image: test
|
||||
`,
|
||||
patch: yaml.MustParse(`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
$patch: replace
|
||||
containers:
|
||||
- name: new
|
||||
`),
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: new
|
||||
`,
|
||||
},
|
||||
"merge mapping - directive": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: test
|
||||
image: test
|
||||
`,
|
||||
patch: yaml.MustParse(`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: test
|
||||
image: test1
|
||||
$patch: merge
|
||||
`),
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: test
|
||||
image: test1
|
||||
`,
|
||||
},
|
||||
"remove list - directive": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: test
|
||||
image: test
|
||||
`,
|
||||
patch: yaml.MustParse(`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- whatever
|
||||
- $patch: delete
|
||||
`),
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
template:
|
||||
spec: {}
|
||||
`,
|
||||
},
|
||||
"replace list - directive": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: test
|
||||
image: test
|
||||
`,
|
||||
patch: yaml.MustParse(`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: replace
|
||||
image: replace
|
||||
- $patch: replace
|
||||
`),
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: replace
|
||||
image: replace
|
||||
`,
|
||||
},
|
||||
"merge list - directive": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: test
|
||||
image: test
|
||||
`,
|
||||
patch: yaml.MustParse(`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: test2
|
||||
image: test2
|
||||
- $patch: merge
|
||||
`),
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: test2
|
||||
image: test2
|
||||
- name: test
|
||||
image: test
|
||||
`,
|
||||
},
|
||||
"list map keys - add a port, no names": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: test-deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: test-image
|
||||
name: test-deployment
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
protocol: TCP
|
||||
`,
|
||||
patch: yaml.MustParse(`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: test-deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: test-image
|
||||
name: test-deployment
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
protocol: UDP
|
||||
- containerPort: 80
|
||||
protocol: UDP
|
||||
`),
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: test-deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: test-image
|
||||
name: test-deployment
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
protocol: UDP
|
||||
- containerPort: 80
|
||||
protocol: UDP
|
||||
- containerPort: 8080
|
||||
protocol: TCP
|
||||
`,
|
||||
},
|
||||
"list map keys - add name to port": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: test-deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: test-image
|
||||
name: test-deployment
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
protocol: UDP
|
||||
- containerPort: 8080
|
||||
protocol: TCP
|
||||
`,
|
||||
patch: yaml.MustParse(`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: test-deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: test-image
|
||||
name: test-deployment
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
protocol: UDP
|
||||
name: UDP-name-patch
|
||||
`),
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: test-deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: test-image
|
||||
name: test-deployment
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
protocol: UDP
|
||||
name: UDP-name-patch
|
||||
- containerPort: 8080
|
||||
protocol: TCP
|
||||
`,
|
||||
},
|
||||
"list map keys - replace port name": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: test-deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: test-image
|
||||
name: test-deployment
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
protocol: UDP
|
||||
name: UDP-name-original
|
||||
- containerPort: 8080
|
||||
protocol: TCP
|
||||
name: TCP-name-original
|
||||
`,
|
||||
patch: yaml.MustParse(`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: test-deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: test-image
|
||||
name: test-deployment
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
protocol: UDP
|
||||
name: UDP-name-patch
|
||||
`),
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: test-deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: test-image
|
||||
name: test-deployment
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
protocol: UDP
|
||||
name: UDP-name-patch
|
||||
- containerPort: 8080
|
||||
protocol: TCP
|
||||
name: TCP-name-original
|
||||
`,
|
||||
},
|
||||
"list map keys - add a port, no protocol": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: test-deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: test-image
|
||||
name: test-deployment
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
`,
|
||||
patch: yaml.MustParse(`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: test-deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: test-image
|
||||
name: test-deployment
|
||||
ports:
|
||||
- containerPort: 80
|
||||
`),
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: test-deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: test-image
|
||||
name: test-deployment
|
||||
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"
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -9,12 +9,11 @@ import (
|
||||
"os"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/filters/prefixsuffix"
|
||||
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
)
|
||||
|
||||
func ExampleFilter() {
|
||||
fss := builtinconfig.MakeDefaultConfig().NamePrefix
|
||||
err := kio.Pipeline{
|
||||
Inputs: []kio.Reader{&kio.ByteReader{Reader: bytes.NewBufferString(`
|
||||
apiVersion: example.com/v1
|
||||
@@ -27,7 +26,8 @@ kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
`)}},
|
||||
Filters: []kio.Filter{prefixsuffix.Filter{Prefix: "baz-", FsSlice: fss}},
|
||||
Filters: []kio.Filter{prefixsuffix.Filter{
|
||||
Prefix: "baz-", FieldSpec: types.FieldSpec{Path: "metadata/name"}}},
|
||||
Outputs: []kio.Writer{kio.ByteWriter{Writer: os.Stdout}},
|
||||
}.Execute()
|
||||
if err != nil {
|
||||
|
||||
@@ -6,7 +6,8 @@ package prefixsuffix
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/filters/fsslice"
|
||||
"sigs.k8s.io/kustomize/api/filters/fieldspec"
|
||||
"sigs.k8s.io/kustomize/api/filters/filtersutil"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
@@ -17,28 +18,26 @@ type Filter struct {
|
||||
Prefix string `json:"prefix,omitempty" yaml:"prefix,omitempty"`
|
||||
Suffix string `json:"suffix,omitempty" yaml:"suffix,omitempty"`
|
||||
|
||||
FsSlice types.FsSlice `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
|
||||
FieldSpec types.FieldSpec `json:"fieldSpec,omitempty" yaml:"fieldSpec,omitempty"`
|
||||
}
|
||||
|
||||
var _ kio.Filter = Filter{}
|
||||
|
||||
func (ns Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||
return kio.FilterAll(yaml.FilterFunc(ns.run)).Filter(nodes)
|
||||
func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||
return kio.FilterAll(yaml.FilterFunc(f.run)).Filter(nodes)
|
||||
}
|
||||
|
||||
// Run runs the filter on a single node rather than a slice
|
||||
func (ns Filter) run(node *yaml.RNode) (*yaml.RNode, error) {
|
||||
// transformations based on data -- :)
|
||||
err := node.PipeE(fsslice.Filter{
|
||||
FsSlice: ns.FsSlice,
|
||||
SetValue: ns.set,
|
||||
func (f Filter) run(node *yaml.RNode) (*yaml.RNode, error) {
|
||||
err := node.PipeE(fieldspec.Filter{
|
||||
FieldSpec: f.FieldSpec,
|
||||
SetValue: f.evaluateField,
|
||||
CreateKind: yaml.ScalarNode, // Name is a ScalarNode
|
||||
CreateTag: yaml.StringTag,
|
||||
CreateTag: yaml.NodeTagString,
|
||||
})
|
||||
return node, err
|
||||
}
|
||||
|
||||
func (ns Filter) set(node *yaml.RNode) error {
|
||||
return fsslice.SetScalar(fmt.Sprintf(
|
||||
"%s%s%s", ns.Prefix, node.YNode().Value, ns.Suffix))(node)
|
||||
func (f Filter) evaluateField(node *yaml.RNode) error {
|
||||
return filtersutil.SetScalar(fmt.Sprintf(
|
||||
"%s%s%s", f.Prefix, node.YNode().Value, f.Suffix))(node)
|
||||
}
|
||||
|
||||
@@ -9,14 +9,12 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"sigs.k8s.io/kustomize/api/filters/prefixsuffix"
|
||||
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
||||
filtertest_test "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
)
|
||||
|
||||
var tests = []TestCase{
|
||||
{
|
||||
name: "prefix",
|
||||
var tests = map[string]TestCase{
|
||||
"prefix": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
@@ -40,10 +38,10 @@ metadata:
|
||||
name: foo-instance
|
||||
`,
|
||||
filter: prefixsuffix.Filter{Prefix: "foo-"},
|
||||
fs: types.FieldSpec{Path: "metadata/name"},
|
||||
},
|
||||
|
||||
{
|
||||
name: "suffix",
|
||||
"suffix": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
@@ -67,10 +65,10 @@ metadata:
|
||||
name: instance-foo
|
||||
`,
|
||||
filter: prefixsuffix.Filter{Suffix: "-foo"},
|
||||
fs: types.FieldSpec{Path: "metadata/name"},
|
||||
},
|
||||
|
||||
{
|
||||
name: "prefix-suffix",
|
||||
"prefix-suffix": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
@@ -94,10 +92,10 @@ metadata:
|
||||
name: bar-instance-foo
|
||||
`,
|
||||
filter: prefixsuffix.Filter{Prefix: "bar-", Suffix: "-foo"},
|
||||
fs: types.FieldSpec{Path: "metadata/name"},
|
||||
},
|
||||
|
||||
{
|
||||
name: "data-fieldspecs",
|
||||
"data-fieldspecs": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
@@ -119,7 +117,7 @@ a:
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: foo-instance
|
||||
name: instance
|
||||
a:
|
||||
b:
|
||||
c: foo-d
|
||||
@@ -127,35 +125,28 @@ a:
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: foo-instance
|
||||
name: instance
|
||||
a:
|
||||
b:
|
||||
c: foo-d
|
||||
`,
|
||||
filter: prefixsuffix.Filter{Prefix: "foo-"},
|
||||
fsslice: []types.FieldSpec{
|
||||
{
|
||||
Path: "a/b/c",
|
||||
},
|
||||
},
|
||||
fs: types.FieldSpec{Path: "a/b/c"},
|
||||
},
|
||||
}
|
||||
|
||||
type TestCase struct {
|
||||
name string
|
||||
input string
|
||||
expected string
|
||||
filter prefixsuffix.Filter
|
||||
fsslice types.FsSlice
|
||||
fs types.FieldSpec
|
||||
}
|
||||
|
||||
var config = builtinconfig.MakeDefaultConfig()
|
||||
|
||||
func TestFilter(t *testing.T) {
|
||||
for i := range tests {
|
||||
test := tests[i]
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
test.filter.FsSlice = append(config.NamePrefix, test.fsslice...)
|
||||
for name := range tests {
|
||||
test := tests[name]
|
||||
t.Run(name, func(t *testing.T) {
|
||||
test.filter.FieldSpec = test.fs
|
||||
if !assert.Equal(t,
|
||||
strings.TrimSpace(test.expected),
|
||||
strings.TrimSpace(
|
||||
|
||||
3
api/filters/refvar/doc.go
Normal file
3
api/filters/refvar/doc.go
Normal file
@@ -0,0 +1,3 @@
|
||||
// Package refvar contains a kio.Filter implementation of the kustomize
|
||||
// refvar transformer (find and replace $(FOO) style variables in strings).
|
||||
package refvar
|
||||
@@ -1,12 +1,12 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package expansion provides functions find and replace $(FOO) style variables in strings.
|
||||
package expansion
|
||||
package refvar
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -17,38 +17,64 @@ const (
|
||||
|
||||
// syntaxWrap returns the input string wrapped by the expansion syntax.
|
||||
func syntaxWrap(input string) string {
|
||||
return string(operator) + string(referenceOpener) + input + string(referenceCloser)
|
||||
var sb strings.Builder
|
||||
sb.WriteByte(operator)
|
||||
sb.WriteByte(referenceOpener)
|
||||
sb.WriteString(input)
|
||||
sb.WriteByte(referenceCloser)
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// MappingFuncFor returns a mapping function for use with Expand that
|
||||
// implements the expansion semantics defined in the expansion spec; it
|
||||
// returns the input string wrapped in the expansion syntax if no mapping
|
||||
// for the input is found.
|
||||
func MappingFuncFor(
|
||||
counts map[string]int,
|
||||
context ...map[string]interface{}) func(string) interface{} {
|
||||
return func(input string) interface{} {
|
||||
for _, vars := range context {
|
||||
val, ok := vars[input]
|
||||
if ok {
|
||||
counts[input]++
|
||||
switch typedV := val.(type) {
|
||||
case string, int64, float64, bool:
|
||||
return typedV
|
||||
default:
|
||||
return syntaxWrap(input)
|
||||
}
|
||||
// MappingFunc maps a string to anything.
|
||||
type MappingFunc func(string) interface{}
|
||||
|
||||
// MakePrimitiveReplacer returns a MappingFunc that uses a map to do
|
||||
// replacements, and a histogram to count map hits.
|
||||
//
|
||||
// Func behavior:
|
||||
//
|
||||
// If the input key is NOT found in the map, the key is wrapped up as
|
||||
// as a variable declaration string and returned, e.g. key FOO becomes $(FOO).
|
||||
// This string is presumably put back where it was found, and might get replaced
|
||||
// later.
|
||||
//
|
||||
// If the key is found in the map, the value is returned if it is a primitive
|
||||
// type (string, bool, number), and the hit is counted.
|
||||
//
|
||||
// If it's not a primitive type (e.g. a map, struct, func, etc.) then this
|
||||
// function doesn't know what to do with it and it returns the key wrapped up
|
||||
// again as if it had not been replaced. This should probably be an error.
|
||||
func MakePrimitiveReplacer(
|
||||
counts map[string]int, someMap map[string]interface{}) MappingFunc {
|
||||
return func(key string) interface{} {
|
||||
if value, ok := someMap[key]; ok {
|
||||
switch typedV := value.(type) {
|
||||
case string, int, int32, int64, float32, float64, bool:
|
||||
counts[key]++
|
||||
return typedV
|
||||
default:
|
||||
// If the value is some complicated type (e.g. a map or struct),
|
||||
// this function doesn't know how to jam it into a string,
|
||||
// so just pretend it was a cache miss.
|
||||
// Likely this should be an error instead of a silent failure,
|
||||
// since the programmer passed an impossible value.
|
||||
log.Printf(
|
||||
"MakePrimitiveReplacer: bad replacement type=%T val=%v",
|
||||
typedV, typedV)
|
||||
return syntaxWrap(key)
|
||||
}
|
||||
}
|
||||
return syntaxWrap(input)
|
||||
// If unable to return the mapped variable, return it
|
||||
// as it was found, and a later mapping might be able to
|
||||
// replace it.
|
||||
return syntaxWrap(key)
|
||||
}
|
||||
}
|
||||
|
||||
// Expand replaces variable references in the input string according to
|
||||
// the expansion spec using the given mapping function to resolve the
|
||||
// values of variables.
|
||||
func Expand(input string, mapping func(string) interface{}) interface{} {
|
||||
var buf bytes.Buffer
|
||||
// DoReplacements replaces variable references in the input string
|
||||
// using the mapping function.
|
||||
func DoReplacements(input string, mapping MappingFunc) interface{} {
|
||||
var buf strings.Builder
|
||||
checkpoint := 0
|
||||
for cursor := 0; cursor < len(input); cursor++ {
|
||||
if input[cursor] == operator && cursor+1 < len(input) {
|
||||
@@ -1,13 +1,14 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package expansion_test
|
||||
package refvar_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
. "sigs.k8s.io/kustomize/api/internal/accumulator/expansion"
|
||||
"github.com/stretchr/testify/assert"
|
||||
. "sigs.k8s.io/kustomize/api/filters/refvar"
|
||||
)
|
||||
|
||||
type expected struct {
|
||||
@@ -15,6 +16,48 @@ type expected struct {
|
||||
edited string
|
||||
}
|
||||
|
||||
func TestPrimitiveReplacer(t *testing.T) {
|
||||
varCounts := make(map[string]int)
|
||||
f := MakePrimitiveReplacer(
|
||||
varCounts,
|
||||
map[string]interface{}{
|
||||
"FOO": "bar",
|
||||
"ZOO": "$(FOO)-1",
|
||||
"BLU": "$(ZOO)-2",
|
||||
"EIGHT": 8,
|
||||
"PI": 3.14159,
|
||||
"ZINT": "$(INT)",
|
||||
"BOOL": "true",
|
||||
"HUGENUMBER": int64(9223372036854775807),
|
||||
"CRAZYMAP": map[string]int{"crazy": 200},
|
||||
"ZBOOL": "$(BOOL)",
|
||||
})
|
||||
assert.Equal(t, "$()", f(""))
|
||||
assert.Equal(t, "$( )", f(" "))
|
||||
assert.Equal(t, "$(florida)", f("florida"))
|
||||
assert.Equal(t, "$(0)", f("0"))
|
||||
assert.Equal(t, "bar", f("FOO"))
|
||||
assert.Equal(t, "bar", f("FOO"))
|
||||
assert.Equal(t, "bar", f("FOO"))
|
||||
assert.Equal(t, 8, f("EIGHT"))
|
||||
assert.Equal(t, 8, f("EIGHT"))
|
||||
assert.Equal(t, 3.14159, f("PI"))
|
||||
assert.Equal(t, "true", f("BOOL"))
|
||||
assert.Equal(t, int64(9223372036854775807), f("HUGENUMBER"))
|
||||
assert.Equal(t, "$(FOO)-1", f("ZOO"))
|
||||
assert.Equal(t, "$(CRAZYMAP)", f("CRAZYMAP"))
|
||||
assert.Equal(t,
|
||||
map[string]int{
|
||||
"FOO": 3,
|
||||
"EIGHT": 2,
|
||||
"BOOL": 1,
|
||||
"PI": 1,
|
||||
"ZOO": 1,
|
||||
"HUGENUMBER": 1,
|
||||
},
|
||||
varCounts)
|
||||
}
|
||||
|
||||
func TestMapReference(t *testing.T) {
|
||||
type env struct {
|
||||
Name string
|
||||
@@ -51,7 +94,7 @@ func TestMapReference(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
declaredEnv := map[string]interface{}{
|
||||
varMap := map[string]interface{}{
|
||||
"FOO": "bar",
|
||||
"ZOO": "$(FOO)-1",
|
||||
"BLU": "$(ZOO)-2",
|
||||
@@ -61,11 +104,11 @@ func TestMapReference(t *testing.T) {
|
||||
"ZBOOL": "$(BOOL)",
|
||||
}
|
||||
|
||||
counts := make(map[string]int)
|
||||
mapping := MappingFuncFor(counts, declaredEnv)
|
||||
|
||||
varCounts := make(map[string]int)
|
||||
for _, env := range envs {
|
||||
declaredEnv[env.Name] = Expand(fmt.Sprintf("%v", env.Value), mapping)
|
||||
varMap[env.Name] = DoReplacements(
|
||||
fmt.Sprintf("%v", env.Value),
|
||||
MakePrimitiveReplacer(varCounts, varMap))
|
||||
}
|
||||
|
||||
expectedEnv := map[string]expected{
|
||||
@@ -79,45 +122,20 @@ func TestMapReference(t *testing.T) {
|
||||
}
|
||||
|
||||
for k, v := range expectedEnv {
|
||||
if e, a := v, declaredEnv[k]; e.edited != a || e.count != counts[k] {
|
||||
if e, a := v, varMap[k]; e.edited != a || e.count != varCounts[k] {
|
||||
t.Errorf("Expected %v count=%d, got %v count=%d",
|
||||
e.edited, e.count, a, counts[k])
|
||||
e.edited, e.count, a, varCounts[k])
|
||||
} else {
|
||||
delete(declaredEnv, k)
|
||||
delete(varMap, k)
|
||||
}
|
||||
}
|
||||
|
||||
if len(declaredEnv) != 0 {
|
||||
t.Errorf("Unexpected keys in declared env: %v", declaredEnv)
|
||||
if len(varMap) != 0 {
|
||||
t.Errorf("Unexpected keys in declared env: %v", varMap)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapping(t *testing.T) {
|
||||
context := map[string]interface{}{
|
||||
"VAR_A": "A",
|
||||
"VAR_B": "B",
|
||||
"VAR_C": "C",
|
||||
"VAR_REF": "$(VAR_A)",
|
||||
"VAR_EMPTY": "",
|
||||
}
|
||||
doExpansionTest(t, context)
|
||||
}
|
||||
|
||||
func TestMappingDual(t *testing.T) {
|
||||
context := map[string]interface{}{
|
||||
"VAR_A": "A",
|
||||
"VAR_EMPTY": "",
|
||||
}
|
||||
context2 := map[string]interface{}{
|
||||
"VAR_B": "B",
|
||||
"VAR_C": "C",
|
||||
"VAR_REF": "$(VAR_A)",
|
||||
}
|
||||
|
||||
doExpansionTest(t, context, context2)
|
||||
}
|
||||
|
||||
func doExpansionTest(t *testing.T, context ...map[string]interface{}) {
|
||||
cases := []struct {
|
||||
name string
|
||||
input string
|
||||
@@ -333,11 +351,17 @@ func doExpansionTest(t *testing.T, context ...map[string]interface{}) {
|
||||
expected: "\n",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
counts := make(map[string]int)
|
||||
mapping := MappingFuncFor(counts, context...)
|
||||
expanded := Expand(fmt.Sprintf("%v", tc.input), mapping)
|
||||
expanded := DoReplacements(
|
||||
fmt.Sprintf("%v", tc.input),
|
||||
MakePrimitiveReplacer(counts, map[string]interface{}{
|
||||
"VAR_A": "A",
|
||||
"VAR_B": "B",
|
||||
"VAR_C": "C",
|
||||
"VAR_REF": "$(VAR_A)",
|
||||
"VAR_EMPTY": "",
|
||||
}))
|
||||
if e, a := tc.expected, expanded; e != a {
|
||||
t.Errorf("%v: expected %q, got %q", tc.name, e, a)
|
||||
}
|
||||
@@ -347,8 +371,7 @@ func doExpansionTest(t *testing.T, context ...map[string]interface{}) {
|
||||
}
|
||||
if len(tc.counts) > 0 {
|
||||
for k, expectedCount := range tc.counts {
|
||||
c, ok := counts[k]
|
||||
if ok {
|
||||
if c, ok := counts[k]; ok {
|
||||
if c != expectedCount {
|
||||
t.Errorf(
|
||||
"%v: k=%s, expected count %d, got %d",
|
||||
110
api/filters/refvar/refvar.go
Normal file
110
api/filters/refvar/refvar.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package refvar
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/filters/fieldspec"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
// Filter updates $(VAR) style variables with values.
|
||||
// The fieldSpecs are the places to look for occurrences of $(VAR).
|
||||
type Filter struct {
|
||||
MappingFunc MappingFunc `json:"mappingFunc,omitempty" yaml:"mappingFunc,omitempty"`
|
||||
FieldSpec types.FieldSpec `json:"fieldSpec,omitempty" yaml:"fieldSpec,omitempty"`
|
||||
}
|
||||
|
||||
func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||
return kio.FilterAll(yaml.FilterFunc(f.run)).Filter(nodes)
|
||||
}
|
||||
|
||||
func (f Filter) run(node *yaml.RNode) (*yaml.RNode, error) {
|
||||
err := node.PipeE(fieldspec.Filter{
|
||||
FieldSpec: f.FieldSpec,
|
||||
SetValue: f.set,
|
||||
})
|
||||
return node, err
|
||||
}
|
||||
|
||||
func (f Filter) set(node *yaml.RNode) error {
|
||||
if yaml.IsMissingOrNull(node) {
|
||||
return nil
|
||||
}
|
||||
switch node.YNode().Kind {
|
||||
case yaml.ScalarNode:
|
||||
return f.setScalar(node)
|
||||
case yaml.MappingNode:
|
||||
return f.setMap(node)
|
||||
case yaml.SequenceNode:
|
||||
return f.setSeq(node)
|
||||
default:
|
||||
return fmt.Errorf("invalid type encountered %v", node.YNode().Kind)
|
||||
}
|
||||
}
|
||||
|
||||
func updateNodeValue(node *yaml.Node, newValue interface{}) {
|
||||
switch newValue := newValue.(type) {
|
||||
case int:
|
||||
node.Value = strconv.FormatInt(int64(newValue), 10)
|
||||
node.Tag = yaml.NodeTagInt
|
||||
case int32:
|
||||
node.Value = strconv.FormatInt(int64(newValue), 10)
|
||||
node.Tag = yaml.NodeTagInt
|
||||
case int64:
|
||||
node.Value = strconv.FormatInt(newValue, 10)
|
||||
node.Tag = yaml.NodeTagInt
|
||||
case bool:
|
||||
node.SetString(strconv.FormatBool(newValue))
|
||||
node.Tag = yaml.NodeTagBool
|
||||
case float32:
|
||||
node.SetString(strconv.FormatFloat(float64(newValue), 'f', -1, 32))
|
||||
node.Tag = yaml.NodeTagFloat
|
||||
case float64:
|
||||
node.SetString(strconv.FormatFloat(newValue, 'f', -1, 64))
|
||||
node.Tag = yaml.NodeTagFloat
|
||||
default:
|
||||
node.SetString(newValue.(string))
|
||||
node.Tag = yaml.NodeTagString
|
||||
}
|
||||
node.Style = 0
|
||||
}
|
||||
|
||||
func (f Filter) setScalar(node *yaml.RNode) error {
|
||||
if !yaml.IsYNodeString(node.YNode()) {
|
||||
return nil
|
||||
}
|
||||
v := DoReplacements(node.YNode().Value, f.MappingFunc)
|
||||
updateNodeValue(node.YNode(), v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f Filter) setMap(node *yaml.RNode) error {
|
||||
contents := node.YNode().Content
|
||||
for i := 0; i < len(contents); i += 2 {
|
||||
if !yaml.IsYNodeString(contents[i]) {
|
||||
return fmt.Errorf(
|
||||
"invalid map key: value='%s', tag='%s'",
|
||||
contents[i].Value, contents[i].Tag)
|
||||
}
|
||||
if !yaml.IsYNodeString(contents[i+1]) {
|
||||
continue
|
||||
}
|
||||
newValue := DoReplacements(contents[i+1].Value, f.MappingFunc)
|
||||
updateNodeValue(contents[i+1], newValue)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f Filter) setSeq(node *yaml.RNode) error {
|
||||
for _, item := range node.YNode().Content {
|
||||
if !yaml.IsYNodeString(item) {
|
||||
return fmt.Errorf("invalid value type expect a string")
|
||||
}
|
||||
newValue := DoReplacements(item.Value, f.MappingFunc)
|
||||
updateNodeValue(item, newValue)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
301
api/filters/refvar/refvar_test.go
Normal file
301
api/filters/refvar/refvar_test.go
Normal file
@@ -0,0 +1,301 @@
|
||||
package refvar_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
. "sigs.k8s.io/kustomize/api/filters/refvar"
|
||||
filtertest_test "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
var makeMf = func(theMap map[string]interface{}) MappingFunc {
|
||||
ignored := make(map[string]int)
|
||||
return MakePrimitiveReplacer(ignored, theMap)
|
||||
}
|
||||
|
||||
func TestFilter(t *testing.T) {
|
||||
|
||||
testCases := map[string]struct {
|
||||
input string
|
||||
expected string
|
||||
filter Filter
|
||||
}{
|
||||
"simple scalar": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
spec:
|
||||
replicas: $(VAR)`,
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
spec:
|
||||
replicas: 5`,
|
||||
filter: Filter{
|
||||
MappingFunc: makeMf(map[string]interface{}{
|
||||
"VAR": int64(5),
|
||||
}),
|
||||
FieldSpec: types.FieldSpec{Path: "spec/replicas"},
|
||||
},
|
||||
},
|
||||
"non-string scalar": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
spec:
|
||||
replicas: 1`,
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
spec:
|
||||
replicas: 1`,
|
||||
filter: Filter{
|
||||
MappingFunc: makeMf(map[string]interface{}{
|
||||
"VAR": int64(5),
|
||||
}),
|
||||
FieldSpec: types.FieldSpec{Path: "spec/replicas"},
|
||||
},
|
||||
},
|
||||
"wrong path": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
spec:
|
||||
replicas: 1`,
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
spec:
|
||||
replicas: 1`,
|
||||
filter: Filter{
|
||||
MappingFunc: makeMf(map[string]interface{}{
|
||||
"VAR": int64(5),
|
||||
}),
|
||||
FieldSpec: types.FieldSpec{Path: "a/b/c"},
|
||||
},
|
||||
},
|
||||
"sequence": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
data:
|
||||
- $(FOO)
|
||||
- $(BAR)
|
||||
- $(BAZ)
|
||||
- $(FOO)+$(BAR)
|
||||
- $(BOOL)
|
||||
- $(FLOAT)`,
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
data:
|
||||
- foo
|
||||
- bar
|
||||
- $(BAZ)
|
||||
- foo+bar
|
||||
- false
|
||||
- 1.23`,
|
||||
filter: Filter{
|
||||
MappingFunc: makeMf(map[string]interface{}{
|
||||
"FOO": "foo",
|
||||
"BAR": "bar",
|
||||
"BOOL": false,
|
||||
"FLOAT": 1.23,
|
||||
}),
|
||||
FieldSpec: types.FieldSpec{Path: "data"},
|
||||
},
|
||||
},
|
||||
"maps": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
data:
|
||||
FOO: $(FOO)
|
||||
BAR: $(BAR)
|
||||
BAZ: $(BAZ)
|
||||
PLUS: $(FOO)+$(BAR)`,
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
data:
|
||||
FOO: foo
|
||||
BAR: bar
|
||||
BAZ: $(BAZ)
|
||||
PLUS: foo+bar`,
|
||||
filter: Filter{
|
||||
MappingFunc: makeMf(map[string]interface{}{
|
||||
"FOO": "foo",
|
||||
"BAR": "bar",
|
||||
}),
|
||||
FieldSpec: types.FieldSpec{Path: "data"},
|
||||
},
|
||||
},
|
||||
"complicated case": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
data:
|
||||
slice1:
|
||||
- $(FOO)
|
||||
slice2:
|
||||
FOO: $(FOO)
|
||||
BAR: $(BAR)
|
||||
BOOL: false
|
||||
INT: 0
|
||||
SLICE:
|
||||
- $(FOO)`,
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
data:
|
||||
slice1:
|
||||
- $(FOO)
|
||||
slice2:
|
||||
FOO: foo
|
||||
BAR: bar
|
||||
BOOL: false
|
||||
INT: 0
|
||||
SLICE:
|
||||
- $(FOO)`,
|
||||
filter: Filter{
|
||||
MappingFunc: makeMf(map[string]interface{}{
|
||||
"FOO": "foo",
|
||||
"BAR": "bar",
|
||||
}),
|
||||
FieldSpec: types.FieldSpec{Path: "data/slice2"},
|
||||
},
|
||||
},
|
||||
"null value": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
data:
|
||||
FOO: null`,
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
data:
|
||||
FOO: null`,
|
||||
filter: Filter{
|
||||
MappingFunc: makeMf(map[string]interface{}{
|
||||
// no replacements!
|
||||
}),
|
||||
FieldSpec: types.FieldSpec{Path: "data/FOO"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for tn, tc := range testCases {
|
||||
t.Run(tn, func(t *testing.T) {
|
||||
if !assert.Equal(t,
|
||||
strings.TrimSpace(tc.expected),
|
||||
strings.TrimSpace(
|
||||
filtertest_test.RunFilter(t, tc.input, tc.filter))) {
|
||||
t.FailNow()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterUnhappy(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
input string
|
||||
expectedError string
|
||||
filter Filter
|
||||
}{
|
||||
"non-string in sequence": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
data:
|
||||
slice:
|
||||
- false`,
|
||||
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
|
||||
: invalid value type expect a string`,
|
||||
filter: Filter{
|
||||
MappingFunc: makeMf(map[string]interface{}{
|
||||
"VAR": int64(5),
|
||||
}),
|
||||
FieldSpec: types.FieldSpec{Path: "data/slice"},
|
||||
},
|
||||
},
|
||||
"invalid key in map": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
data:
|
||||
1: str`,
|
||||
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
|
||||
: invalid map key: value='1', tag='` + yaml.NodeTagInt + `'`,
|
||||
filter: Filter{
|
||||
MappingFunc: makeMf(map[string]interface{}{
|
||||
"VAR": int64(5),
|
||||
}),
|
||||
FieldSpec: types.FieldSpec{Path: "data"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for tn, tc := range testCases {
|
||||
t.Run(tn, func(t *testing.T) {
|
||||
_, err := filtertest_test.RunFilterE(t, tc.input, tc.filter)
|
||||
if !assert.EqualError(t, err, tc.expectedError) {
|
||||
t.FailNow()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
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
@@ -33,10 +33,8 @@ spec:
|
||||
Count: 42,
|
||||
Name: "instance",
|
||||
},
|
||||
FsSlice: types.FsSlice{
|
||||
{
|
||||
Path: "spec/template/replicas",
|
||||
},
|
||||
FieldSpec: types.FieldSpec{
|
||||
Path: "spec/template/replicas",
|
||||
},
|
||||
}},
|
||||
Outputs: []kio.Writer{kio.ByteWriter{Writer: os.Stdout}},
|
||||
|
||||
@@ -3,7 +3,8 @@ package replicacount
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/filters/fsslice"
|
||||
"sigs.k8s.io/kustomize/api/filters/fieldspec"
|
||||
"sigs.k8s.io/kustomize/api/filters/filtersutil"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
@@ -11,10 +12,8 @@ import (
|
||||
|
||||
// Filter updates/sets replicas fields using the fieldSpecs
|
||||
type Filter struct {
|
||||
Replica types.Replica `json:"replica,omitempty" yaml:"replica,omitempty"`
|
||||
|
||||
// FsSlice contains the FieldSpecs to locate the namespace field
|
||||
FsSlice types.FsSlice `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
|
||||
Replica types.Replica `json:"replica,omitempty" yaml:"replica,omitempty"`
|
||||
FieldSpec types.FieldSpec `json:"fieldSpec,omitempty" yaml:"fieldSpec,omitempty"`
|
||||
}
|
||||
|
||||
var _ kio.Filter = Filter{}
|
||||
@@ -23,27 +22,16 @@ func (rc Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||
return kio.FilterAll(yaml.FilterFunc(rc.run)).Filter(nodes)
|
||||
}
|
||||
|
||||
// run processes each node individually.
|
||||
func (rc Filter) run(node *yaml.RNode) (*yaml.RNode, error) {
|
||||
meta, err := node.GetMeta()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// only update resources where the name matches the Replica name.
|
||||
if meta.Name != rc.Replica.Name {
|
||||
return node, nil
|
||||
}
|
||||
|
||||
err = node.PipeE(fsslice.Filter{
|
||||
FsSlice: rc.FsSlice,
|
||||
err := node.PipeE(fieldspec.Filter{
|
||||
FieldSpec: rc.FieldSpec,
|
||||
SetValue: rc.set,
|
||||
CreateKind: yaml.ScalarNode, // replicas is a ScalarNode
|
||||
CreateTag: yaml.IntTag,
|
||||
CreateTag: yaml.NodeTagInt,
|
||||
})
|
||||
return node, err
|
||||
}
|
||||
|
||||
func (rc Filter) set(node *yaml.RNode) error {
|
||||
return fsslice.SetScalar(strconv.FormatInt(rc.Replica.Count, 10))(node)
|
||||
return filtersutil.SetScalar(strconv.FormatInt(rc.Replica.Count, 10))(node)
|
||||
}
|
||||
|
||||
@@ -5,19 +5,16 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
||||
filtertest_test "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
)
|
||||
|
||||
func TestFilter(t *testing.T) {
|
||||
var config = builtinconfig.MakeDefaultConfig()
|
||||
|
||||
testCases := map[string]struct {
|
||||
input string
|
||||
expected string
|
||||
filter Filter
|
||||
fsslice types.FsSlice
|
||||
}{
|
||||
"update field": {
|
||||
input: `
|
||||
@@ -41,11 +38,7 @@ spec:
|
||||
Name: "dep",
|
||||
Count: 42,
|
||||
},
|
||||
},
|
||||
fsslice: types.FsSlice{
|
||||
{
|
||||
Path: "spec/replicas",
|
||||
},
|
||||
FieldSpec: types.FieldSpec{Path: "spec/replicas"},
|
||||
},
|
||||
},
|
||||
"add field": {
|
||||
@@ -73,9 +66,7 @@ spec:
|
||||
Name: "cus",
|
||||
Count: 42,
|
||||
},
|
||||
},
|
||||
fsslice: types.FsSlice{
|
||||
{
|
||||
FieldSpec: types.FieldSpec{
|
||||
Path: "spec/template/replicas",
|
||||
CreateIfNotPresent: true,
|
||||
},
|
||||
@@ -108,9 +99,7 @@ spec:
|
||||
Name: "cus",
|
||||
Count: 42,
|
||||
},
|
||||
},
|
||||
fsslice: types.FsSlice{
|
||||
{
|
||||
FieldSpec: types.FieldSpec{
|
||||
Path: "spec/template/replicas",
|
||||
CreateIfNotPresent: true,
|
||||
},
|
||||
@@ -140,9 +129,7 @@ spec:
|
||||
Name: "cus",
|
||||
Count: 42,
|
||||
},
|
||||
},
|
||||
fsslice: types.FsSlice{
|
||||
{
|
||||
FieldSpec: types.FieldSpec{
|
||||
Path: "spec/template/replicas",
|
||||
},
|
||||
},
|
||||
@@ -154,7 +141,6 @@ kind: Custom
|
||||
metadata:
|
||||
name: cus
|
||||
spec:
|
||||
replicas: 5
|
||||
template:
|
||||
replicas: 5
|
||||
`,
|
||||
@@ -164,7 +150,6 @@ kind: Custom
|
||||
metadata:
|
||||
name: cus
|
||||
spec:
|
||||
replicas: 42
|
||||
template:
|
||||
replicas: 42
|
||||
`,
|
||||
@@ -173,21 +158,13 @@ spec:
|
||||
Name: "cus",
|
||||
Count: 42,
|
||||
},
|
||||
},
|
||||
fsslice: types.FsSlice{
|
||||
{
|
||||
Path: "spec/template/replicas",
|
||||
},
|
||||
{
|
||||
Path: "spec/replicas",
|
||||
},
|
||||
FieldSpec: types.FieldSpec{Path: "spec/template/replicas"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for tn, tc := range testCases {
|
||||
t.Run(tn, func(t *testing.T) {
|
||||
tc.filter.FsSlice = append(config.Replicas, tc.fsslice...)
|
||||
if !assert.Equal(t,
|
||||
strings.TrimSpace(tc.expected),
|
||||
strings.TrimSpace(
|
||||
|
||||
@@ -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 {
|
||||
|
||||
23
api/go.mod
23
api/go.mod
@@ -1,21 +1,16 @@
|
||||
module sigs.k8s.io/kustomize/api
|
||||
|
||||
go 1.14
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/evanphx/json-patch v4.5.0+incompatible
|
||||
github.com/go-openapi/spec v0.19.5
|
||||
github.com/golangci/golangci-lint v1.21.0
|
||||
github.com/evanphx/json-patch v4.11.0+incompatible
|
||||
github.com/go-errors/errors v1.0.1
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/stretchr/testify v1.4.0
|
||||
github.com/yujunz/go-getter v1.4.1-lite
|
||||
golang.org/x/tools v0.0.0-20191010075000-0337d82405ff
|
||||
gopkg.in/yaml.v2 v2.3.0
|
||||
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.4.1
|
||||
github.com/imdario/mergo v0.3.5
|
||||
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
|
||||
)
|
||||
|
||||
448
api/go.sum
448
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,290 +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/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/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/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/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-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/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/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/mozilla/tls-observatory v0.0.0-20190404164649-a3c1b6cfecfd/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk=
|
||||
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/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=
|
||||
@@ -340,251 +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/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.5 h1:pFrO0lVpTBXLpYw+pnLj6TbvHuyjXMfjGeCwSqCVwok=
|
||||
github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
|
||||
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.4.1-lite h1:FhvNc94AXMZkfqUwfMKhnQEC9phkphSGdPTL7tIdhOM=
|
||||
github.com/yujunz/go-getter v1.4.1-lite/go.mod h1:sbmqxXjyLunH1PkF3n7zSlnVeMvmYUuIl9ZVs/7NyCc=
|
||||
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/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/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/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/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/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/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.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-20191120175047-4206685974f2 h1:XZx7nhd5GMaZpmDaEHFVafUZC7ya0fuo7cSJ3UCKYmM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/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/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
|
||||
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed h1:WX1yoOaKQfddO/mLzdV4wptyWgoH/6hwLs7QHTixo0I=
|
||||
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc=
|
||||
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b h1:DxJ5nJdkhDlLok9K6qO+5290kphDJbHOQO1DFFFTeBo=
|
||||
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4=
|
||||
mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f h1:Cq7MalBHYACRd6EesksG1Q8EoIAKOsiZviGKbOLIej4=
|
||||
mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZIVpwbkw+04kSxk3rAtzlimaUJw=
|
||||
sigs.k8s.io/kustomize/kyaml v0.4.1 h1:NEqA/35upoAjb+I5vh1ODUqxoX4DOrezeQa9BhhG5Co=
|
||||
sigs.k8s.io/kustomize/kyaml v0.4.1/go.mod h1:XJL84E6sOFeNrQ7CADiemc1B0EjIxHo3OhW4o1aJYNw=
|
||||
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=
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
// SortArrayAndComputeHash sorts a string array and
|
||||
@@ -18,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")
|
||||
@@ -46,7 +48,108 @@ 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)))
|
||||
}
|
||||
|
||||
// Hasher computes the hash of an RNode.
|
||||
type Hasher struct{}
|
||||
|
||||
// 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":
|
||||
encoded, err = encodeSecret(node)
|
||||
default:
|
||||
var encodedBytes []byte
|
||||
encodedBytes, err = json.Marshal(node.YNode())
|
||||
encoded = string(encodedBytes)
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return encode(hex256(encoded))
|
||||
}
|
||||
|
||||
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))
|
||||
if err != nil {
|
||||
return map[string]interface{}{}, err
|
||||
}
|
||||
if vn == nil {
|
||||
values[p] = ""
|
||||
continue
|
||||
}
|
||||
if vn.YNode().Kind != yaml.ScalarNode {
|
||||
vs, err := vn.MarshalJSON()
|
||||
if err != nil {
|
||||
return map[string]interface{}{}, err
|
||||
}
|
||||
// data, binaryData and stringData are all maps
|
||||
var v map[string]interface{}
|
||||
json.Unmarshal(vs, &v)
|
||||
values[p] = v
|
||||
} else {
|
||||
values[p] = vn.YNode().Value
|
||||
}
|
||||
}
|
||||
return values, nil
|
||||
}
|
||||
|
||||
// encodeConfigMap encodes a ConfigMap.
|
||||
// Data, Kind, and Name are taken into account.
|
||||
// BinaryData is included if it's not empty to avoid useless key in output.
|
||||
func encodeConfigMap(node *yaml.RNode) (string, error) {
|
||||
// get fields
|
||||
paths := []string{"metadata/name", "data", "binaryData"}
|
||||
values, err := getNodeValues(node, paths)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
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"]
|
||||
}
|
||||
|
||||
// json.Marshal sorts the keys in a stable order in the encoding
|
||||
data, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
// encodeSecret encodes a Secret.
|
||||
// Data, Kind, Name, and Type are taken into account.
|
||||
// StringData is included if it's not empty to avoid useless key in output.
|
||||
func encodeSecret(node *yaml.RNode) (string, error) {
|
||||
// get fields
|
||||
paths := []string{"type", "metadata/name", "data", "stringData"}
|
||||
values, err := getNodeValues(node, paths)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
m := map[string]interface{}{"kind": "Secret", "type": values["type"],
|
||||
"name": values["metadata/name"], "data": values["data"]}
|
||||
if _, ok := values["stringData"].(map[string]interface{}); ok {
|
||||
m["stringData"] = values["stringData"]
|
||||
}
|
||||
|
||||
// json.Marshal sorts the keys in a stable order in the encoding
|
||||
data, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package hasher_test
|
||||
package hasher
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
. "sigs.k8s.io/kustomize/api/hasher"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
func TestSortArrayAndComputeHash(t *testing.T) {
|
||||
@@ -31,11 +32,325 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigMapHash(t *testing.T) {
|
||||
cases := []struct {
|
||||
desc string
|
||||
cmYaml string
|
||||
hash string
|
||||
err string
|
||||
}{
|
||||
// empty map
|
||||
{"empty data", `
|
||||
apiVersion: v1
|
||||
kind: ConfigMap`, "6ct58987ht", ""},
|
||||
// one key
|
||||
{"one key", `
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
data:
|
||||
one: ""`, "9g67k2htb6", ""},
|
||||
// three keys (tests sorting order)
|
||||
{"three keys", `
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
data:
|
||||
two: 2
|
||||
one: ""
|
||||
three: 3`, "7757f9kkct", ""},
|
||||
// empty binary data map
|
||||
{"empty binary data", `
|
||||
apiVersion: v1
|
||||
kind: ConfigMap`, "6ct58987ht", ""},
|
||||
// one key with binary data
|
||||
{"one key with binary data", `
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
binaryData:
|
||||
one: ""`, "6mtk2m274t", ""},
|
||||
// three keys with binary data (tests sorting order)
|
||||
{"three keys with binary data", `
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
binaryData:
|
||||
two: 2
|
||||
one: ""
|
||||
three: 3`, "9th7kc28dg", ""},
|
||||
// two keys, one with string and another with binary data
|
||||
{"two keys with one each", `
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
data:
|
||||
one: ""
|
||||
binaryData:
|
||||
two: ""`, "698h7c7t9m", ""},
|
||||
}
|
||||
h := &Hasher{}
|
||||
for _, c := range cases {
|
||||
node, err := yaml.Parse(c.cmYaml)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
hashed, err := h.Hash(node)
|
||||
if SkipRest(t, c.desc, err, c.err) {
|
||||
continue
|
||||
}
|
||||
if c.hash != hashed {
|
||||
t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecretHash(t *testing.T) {
|
||||
cases := []struct {
|
||||
desc string
|
||||
secretYaml string
|
||||
hash string
|
||||
err string
|
||||
}{
|
||||
// empty map
|
||||
{"empty data", `
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
type: my-type`, "5gmgkf8578", ""},
|
||||
// one key
|
||||
{"one key", `
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
type: my-type
|
||||
data:
|
||||
one: ""`, "74bd68bm66", ""},
|
||||
// three keys (tests sorting order)
|
||||
{"three keys", `
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
type: my-type
|
||||
data:
|
||||
two: 2
|
||||
one: ""
|
||||
three: 3`, "4gf75c7476", ""},
|
||||
// with stringdata
|
||||
{"stringdata", `
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
type: my-type
|
||||
data:
|
||||
one: ""
|
||||
stringData:
|
||||
two: 2`, "c4h4264gdb", ""},
|
||||
// empty stringdata
|
||||
{"empty stringdata", `
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
type: my-type
|
||||
data:
|
||||
one: ""`, "74bd68bm66", ""},
|
||||
}
|
||||
h := &Hasher{}
|
||||
for _, c := range cases {
|
||||
node, err := yaml.Parse(c.secretYaml)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
hashed, err := h.Hash(node)
|
||||
if SkipRest(t, c.desc, err, c.err) {
|
||||
continue
|
||||
}
|
||||
if c.hash != hashed {
|
||||
t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasicHash(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
res string
|
||||
hash string
|
||||
err string
|
||||
}{
|
||||
"minimal": {`
|
||||
apiVersion: test/v1
|
||||
kind: TestResource
|
||||
metadata:
|
||||
name: my-resource`, "244782mkb7", ""},
|
||||
"with spec": {`
|
||||
apiVersion: test/v1
|
||||
kind: TestResource
|
||||
metadata:
|
||||
name: my-resource
|
||||
spec:
|
||||
foo: 1
|
||||
bar: abc`, "59m2mdccg4", ""},
|
||||
}
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeConfigMap(t *testing.T) {
|
||||
cases := []struct {
|
||||
desc string
|
||||
cmYaml string
|
||||
expect string
|
||||
err string
|
||||
}{
|
||||
// empty map
|
||||
{"empty data", `
|
||||
apiVersion: v1
|
||||
kind: ConfigMap`, `{"data":"","kind":"ConfigMap","name":""}`, ""},
|
||||
// one key
|
||||
{"one key", `
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
data:
|
||||
one: ""`, `{"data":{"one":""},"kind":"ConfigMap","name":""}`, ""},
|
||||
// three keys (tests sorting order)
|
||||
{"three keys", `
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
data:
|
||||
two: 2
|
||||
one: ""
|
||||
three: 3`, `{"data":{"one":"","three":3,"two":2},"kind":"ConfigMap","name":""}`, ""},
|
||||
// empty binary map
|
||||
{"empty data", `
|
||||
apiVersion: v1
|
||||
kind: ConfigMap`, `{"data":"","kind":"ConfigMap","name":""}`, ""},
|
||||
// one key with binary data
|
||||
{"one key", `
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
binaryData:
|
||||
one: ""`, `{"binaryData":{"one":""},"data":"","kind":"ConfigMap","name":""}`, ""},
|
||||
// three keys with binary data (tests sorting order)
|
||||
{"three keys", `
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
binaryData:
|
||||
two: 2
|
||||
one: ""
|
||||
three: 3`, `{"binaryData":{"one":"","three":3,"two":2},"data":"","kind":"ConfigMap","name":""}`, ""},
|
||||
// two keys, one string and one binary values
|
||||
{"two keys with one each", `
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
data:
|
||||
one: ""
|
||||
binaryData:
|
||||
two: ""`, `{"binaryData":{"two":""},"data":{"one":""},"kind":"ConfigMap","name":""}`, ""},
|
||||
}
|
||||
for _, c := range cases {
|
||||
node, err := yaml.Parse(c.cmYaml)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
s, err := encodeConfigMap(node)
|
||||
if SkipRest(t, c.desc, err, c.err) {
|
||||
continue
|
||||
}
|
||||
if s != c.expect {
|
||||
t.Errorf("case %q, expect %q but got %q from encode %#v", c.desc, c.expect, s, c.cmYaml)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeSecret(t *testing.T) {
|
||||
cases := []struct {
|
||||
desc string
|
||||
secretYaml string
|
||||
expect string
|
||||
err string
|
||||
}{
|
||||
// empty map
|
||||
{"empty data", `
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
type: my-type`, `{"data":"","kind":"Secret","name":"","type":"my-type"}`, ""},
|
||||
// one key
|
||||
{"one key", `
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
type: my-type
|
||||
data:
|
||||
one: ""`, `{"data":{"one":""},"kind":"Secret","name":"","type":"my-type"}`, ""},
|
||||
// three keys (tests sorting order) - note json.Marshal base64 encodes the values because they come in as []byte
|
||||
{"three keys", `
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
type: my-type
|
||||
data:
|
||||
two: 2
|
||||
one: ""
|
||||
three: 3`, `{"data":{"one":"","three":3,"two":2},"kind":"Secret","name":"","type":"my-type"}`, ""},
|
||||
// with stringdata
|
||||
{"stringdata", `
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
type: my-type
|
||||
data:
|
||||
one: ""
|
||||
stringData:
|
||||
two: 2`, `{"data":{"one":""},"kind":"Secret","name":"","stringData":{"two":2},"type":"my-type"}`, ""},
|
||||
// empty stringdata
|
||||
{"empty stringdata", `
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
type: my-type
|
||||
data:
|
||||
one: ""`, `{"data":{"one":""},"kind":"Secret","name":"","type":"my-type"}`, ""},
|
||||
}
|
||||
for _, c := range cases {
|
||||
node, err := yaml.Parse(c.secretYaml)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
s, err := encodeSecret(node)
|
||||
if SkipRest(t, c.desc, err, c.err) {
|
||||
continue
|
||||
}
|
||||
if s != c.expect {
|
||||
t.Errorf("case %q, expect %q but got %q from encode %#v", c.desc, c.expect, s, c.secretYaml)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
t.Errorf("case %q, expect nil error but got %q", desc, err.Error())
|
||||
} else if !strings.Contains(err.Error(), contains) {
|
||||
t.Errorf("case %q, expect error to contain %q but got %q", desc, contains, err.Error())
|
||||
}
|
||||
return true
|
||||
} else if len(contains) > 0 {
|
||||
t.Errorf("case %q, expect error to contain %q but got nil error", desc, contains)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -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,51 +38,10 @@ type Loader interface {
|
||||
Cleanup() error
|
||||
}
|
||||
|
||||
// Kunstructured allows manipulation of k8s objects
|
||||
// that do not have Golang structs.
|
||||
type Kunstructured interface {
|
||||
Copy() Kunstructured
|
||||
GetAnnotations() map[string]string
|
||||
GetBool(path string) (bool, error)
|
||||
GetFieldValue(string) (interface{}, error)
|
||||
GetFloat64(path string) (float64, error)
|
||||
GetGvk() resid.Gvk
|
||||
GetInt64(path string) (int64, error)
|
||||
GetKind() string
|
||||
GetLabels() map[string]string
|
||||
GetMap(path string) (map[string]interface{}, error)
|
||||
GetName() string
|
||||
GetSlice(path string) ([]interface{}, error)
|
||||
GetString(string) (string, error)
|
||||
GetStringMap(path string) (map[string]string, error)
|
||||
GetStringSlice(string) ([]string, error)
|
||||
Map() map[string]interface{}
|
||||
MarshalJSON() ([]byte, error)
|
||||
MatchesAnnotationSelector(selector string) (bool, error)
|
||||
MatchesLabelSelector(selector string) (bool, error)
|
||||
Patch(Kunstructured) error
|
||||
SetAnnotations(map[string]string)
|
||||
SetGvk(resid.Gvk)
|
||||
SetLabels(map[string]string)
|
||||
SetMap(map[string]interface{})
|
||||
SetName(string)
|
||||
SetNamespace(string)
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -7,22 +7,27 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/filters/nameref"
|
||||
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/resource"
|
||||
"sigs.k8s.io/kustomize/api/transform"
|
||||
"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(br []builtinconfig.NameBackReferences) resmap.Transformer {
|
||||
func newNameReferenceTransformer(
|
||||
br []builtinconfig.NameBackReferences) resmap.Transformer {
|
||||
if br == nil {
|
||||
log.Fatal("backrefs not expected to be nil")
|
||||
}
|
||||
@@ -35,13 +40,61 @@ func newNameReferenceTransformer(br []builtinconfig.NameBackReferences) resmap.T
|
||||
//
|
||||
// For example, a HorizontalPodAutoscaler (HPA)
|
||||
// necessarily refers to a Deployment, the thing that
|
||||
// the HPA scales. The Deployment name might change
|
||||
// (e.g. prefix added), and the reference in the HPA
|
||||
// has to be fixed.
|
||||
// an HPA scales. In this case:
|
||||
//
|
||||
// In the outer loop over the ResMap below, say we
|
||||
// encounter a specific HPA. Then, in scanning backrefs,
|
||||
// we encounter an entry like
|
||||
// - 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
|
||||
// fieldSpecs:
|
||||
@@ -50,220 +103,60 @@ func newNameReferenceTransformer(br []builtinconfig.NameBackReferences) resmap.T
|
||||
//
|
||||
// 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 (o *nameReferenceTransformer) Transform(m resmap.ResMap) error {
|
||||
// TODO: Too much looping, here and in transitive calls.
|
||||
for _, referrer := range m.Resources() {
|
||||
var candidates resmap.ResMap
|
||||
for _, target := range o.backRefs {
|
||||
for _, fSpec := range target.FieldSpecs {
|
||||
if referrer.OrgId().IsSelected(&fSpec.Gvk) {
|
||||
if candidates == nil {
|
||||
candidates = m.SubsetThatCouldBeReferencedByResource(referrer)
|
||||
}
|
||||
err := transform.MutateField(
|
||||
referrer.Map(),
|
||||
fSpec.PathSlice(),
|
||||
fSpec.CreateIfNotPresent,
|
||||
o.getNewNameFunc(
|
||||
// referrer could be an HPA instance,
|
||||
// target could be Gvk for Deployment,
|
||||
// candidate a list of resources "reachable"
|
||||
// from the HPA.
|
||||
referrer, target.Gvk, candidates))
|
||||
if err != nil {
|
||||
return err
|
||||
// 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
|
||||
}
|
||||
|
||||
// selectReferral picks the referral among a subset of candidates.
|
||||
// It returns the current name and namespace of the selected candidate.
|
||||
// Note that the content of the referricalCandidateSubset slice is most of the time
|
||||
// identical to the referralCandidates resmap. Still in some cases, such
|
||||
// as ClusterRoleBinding, the subset only contains the resources of a specific
|
||||
// namespace.
|
||||
func (o *nameReferenceTransformer) selectReferral(
|
||||
oldName string,
|
||||
referrer *resource.Resource,
|
||||
target resid.Gvk,
|
||||
referralCandidates resmap.ResMap,
|
||||
referralCandidateSubset []*resource.Resource) (interface{}, interface{}, error) {
|
||||
|
||||
for _, res := range referralCandidateSubset {
|
||||
id := res.OrgId()
|
||||
if id.IsSelected(&target) && res.GetOriginalName() == oldName {
|
||||
matches := referralCandidates.GetMatchingResourcesByOriginalId(id.Equals)
|
||||
// If there's more than one match, there's no way
|
||||
// to know which one to pick, so emit error.
|
||||
if len(matches) > 1 {
|
||||
return nil, nil, fmt.Errorf(
|
||||
"multiple matches for %s:\n %v",
|
||||
id, getIds(matches))
|
||||
}
|
||||
// In the resource, note that it is referenced
|
||||
// by the referrer.
|
||||
res.AppendRefBy(referrer.CurId())
|
||||
// Return transformed name of the object,
|
||||
// complete with prefixes, hashes, etc.
|
||||
return res.GetName(), res.GetNamespace(), nil
|
||||
}
|
||||
}
|
||||
|
||||
return oldName, nil, nil
|
||||
}
|
||||
|
||||
// utility function to replace a simple string by the new name
|
||||
func (o *nameReferenceTransformer) getSimpleNameField(
|
||||
oldName string,
|
||||
referrer *resource.Resource,
|
||||
target resid.Gvk,
|
||||
referralCandidates resmap.ResMap,
|
||||
referralCandidateSubset []*resource.Resource) (interface{}, error) {
|
||||
|
||||
newName, _, err := o.selectReferral(oldName, referrer, target,
|
||||
referralCandidates, referralCandidateSubset)
|
||||
|
||||
return newName, err
|
||||
}
|
||||
|
||||
// utility function to replace name field within a map[string]interface{}
|
||||
// and leverage the namespace field.
|
||||
func (o *nameReferenceTransformer) getNameAndNsStruct(
|
||||
inMap map[string]interface{},
|
||||
referrer *resource.Resource,
|
||||
target resid.Gvk,
|
||||
referralCandidates resmap.ResMap) (interface{}, error) {
|
||||
|
||||
// Example:
|
||||
if _, ok := inMap["name"]; !ok {
|
||||
return nil, fmt.Errorf(
|
||||
"%#v is expected to contain a name field", inMap)
|
||||
}
|
||||
oldName, ok := inMap["name"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(
|
||||
"%#v is expected to contain a name field of type string", oldName)
|
||||
}
|
||||
|
||||
subset := referralCandidates.Resources()
|
||||
if namespacevalue, ok := inMap["namespace"]; ok {
|
||||
namespace := namespacevalue.(string)
|
||||
bynamespace := referralCandidates.GroupedByOriginalNamespace()
|
||||
if _, ok := bynamespace[namespace]; !ok {
|
||||
return inMap, nil
|
||||
}
|
||||
subset = bynamespace[namespace]
|
||||
}
|
||||
|
||||
newname, newnamespace, err := o.selectReferral(oldName, referrer, target,
|
||||
referralCandidates, subset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if (newname == oldName) && (newnamespace == nil) {
|
||||
// no candidate found.
|
||||
return inMap, nil
|
||||
}
|
||||
|
||||
inMap["name"] = newname
|
||||
if newnamespace != "" {
|
||||
// We don't want value "" to replace value "default" since
|
||||
// the empty string is handled as a wild card here not default namespace
|
||||
// by kubernetes.
|
||||
inMap["namespace"] = newnamespace
|
||||
}
|
||||
return inMap, nil
|
||||
|
||||
}
|
||||
|
||||
func (o *nameReferenceTransformer) getNewNameFunc(
|
||||
referrer *resource.Resource,
|
||||
target resid.Gvk,
|
||||
referralCandidates resmap.ResMap) func(in interface{}) (interface{}, error) {
|
||||
return func(in interface{}) (interface{}, error) {
|
||||
switch thing := in.(type) {
|
||||
case string:
|
||||
return o.getSimpleNameField(thing, referrer, target,
|
||||
referralCandidates, referralCandidates.Resources())
|
||||
case map[string]interface{}:
|
||||
// Kind: ValidatingWebhookConfiguration
|
||||
// FieldSpec is webhooks/clientConfig/service
|
||||
return o.getNameAndNsStruct(thing, referrer, target,
|
||||
referralCandidates)
|
||||
case []interface{}:
|
||||
for idx, item := range thing {
|
||||
switch value := item.(type) {
|
||||
case string:
|
||||
// Kind: Role/ClusterRole
|
||||
// FieldSpec is rules.resourceNames
|
||||
newName, err := o.getSimpleNameField(value, referrer, target,
|
||||
referralCandidates, referralCandidates.Resources())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
thing[idx] = newName
|
||||
case map[string]interface{}:
|
||||
// Kind: RoleBinding/ClusterRoleBinding
|
||||
// FieldSpec is subjects
|
||||
// Note: The corresponding fieldSpec had been changed from
|
||||
// from path: subjects/name to just path: subjects. This is
|
||||
// what get mutatefield to request the mapping of the whole
|
||||
// map containing namespace and name instead of just a simple
|
||||
// string field containing the name
|
||||
newMap, err := o.getNameAndNsStruct(value, referrer, target,
|
||||
referralCandidates)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
thing[idx] = newMap
|
||||
default:
|
||||
return nil, fmt.Errorf(
|
||||
"%#v is expected to be either a []string or a []map[string]interface{}", in)
|
||||
}
|
||||
}
|
||||
return in, nil
|
||||
default:
|
||||
return nil, fmt.Errorf(
|
||||
"%#v is expected to be either a string or a []interface{}", in)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getIds(rs []*resource.Resource) []string {
|
||||
var result []string
|
||||
for _, r := range rs {
|
||||
result = append(result, r.CurId().String()+"\n")
|
||||
}
|
||||
return result
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -8,17 +8,16 @@ import (
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
||||
"sigs.k8s.io/kustomize/api/k8sdeps/kunstruct"
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/provider"
|
||||
"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/kyaml/resid"
|
||||
)
|
||||
|
||||
const notEqualErrFmt = "expected (self) doesn't match actual (other): %v"
|
||||
|
||||
func TestNameReferenceHappyRun(t *testing.T) {
|
||||
rf := resource.NewFactory(
|
||||
kunstruct.NewKunstructuredFactoryImpl())
|
||||
m := resmaptest_test.NewRmBuilder(t, rf).AddWithName(
|
||||
m := resmaptest_test.NewRmBuilderDefault(t).AddWithName(
|
||||
"cm1",
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
@@ -220,6 +219,7 @@ func TestNameReferenceHappyRun(t *testing.T) {
|
||||
"secret1",
|
||||
"secret1",
|
||||
"secret2",
|
||||
"cm1",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -261,7 +261,8 @@ func TestNameReferenceHappyRun(t *testing.T) {
|
||||
},
|
||||
}).ResMap()
|
||||
|
||||
expected := resmaptest_test.NewSeededRmBuilder(t, rf, m.ShallowCopy()).ReplaceResource(
|
||||
expected := resmaptest_test.NewSeededRmBuilderDefault(
|
||||
t, m.ShallowCopy()).ReplaceResource(
|
||||
map[string]interface{}{
|
||||
"group": "apps",
|
||||
"apiVersion": "v1",
|
||||
@@ -422,6 +423,7 @@ func TestNameReferenceHappyRun(t *testing.T) {
|
||||
"someprefix-secret1-somehash",
|
||||
"someprefix-secret1-somehash",
|
||||
"secret2",
|
||||
"someprefix-cm1-somehash",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -470,19 +472,17 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNameReferenceUnhappyRun(t *testing.T) {
|
||||
rf := resource.NewFactory(
|
||||
kunstruct.NewKunstructuredFactoryImpl())
|
||||
tests := []struct {
|
||||
resMap resmap.ResMap
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
resMap: resmaptest_test.NewRmBuilder(t, rf).Add(
|
||||
resMap: resmaptest_test.NewRmBuilderDefault(t).Add(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "rbac.authorization.k8s.io/v1",
|
||||
"kind": "ClusterRole",
|
||||
@@ -502,7 +502,7 @@ func TestNameReferenceUnhappyRun(t *testing.T) {
|
||||
}).ResMap(),
|
||||
expectedErr: "is expected to be"},
|
||||
{
|
||||
resMap: resmaptest_test.NewRmBuilder(t, rf).Add(
|
||||
resMap: resmaptest_test.NewRmBuilderDefault(t).Add(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "rbac.authorization.k8s.io/v1",
|
||||
"kind": "ClusterRole",
|
||||
@@ -520,7 +520,19 @@ func TestNameReferenceUnhappyRun(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}).ResMap(),
|
||||
expectedErr: "is expected to contain a name field"},
|
||||
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)
|
||||
@@ -531,15 +543,14 @@ 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNameReferencePersistentVolumeHappyRun(t *testing.T) {
|
||||
rf := resource.NewFactory(
|
||||
kunstruct.NewKunstructuredFactoryImpl())
|
||||
rf := provider.NewDefaultDepProvider().GetResourceFactory()
|
||||
|
||||
v1 := rf.FromMapWithName(
|
||||
"volume1",
|
||||
@@ -590,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -664,9 +675,7 @@ const (
|
||||
// object with the same original names (uniquename) in different namespaces
|
||||
// and with different current Id.
|
||||
func TestNameReferenceNamespace(t *testing.T) {
|
||||
rf := resource.NewFactory(
|
||||
kunstruct.NewKunstructuredFactoryImpl())
|
||||
m := resmaptest_test.NewRmBuilder(t, rf).
|
||||
m := resmaptest_test.NewRmBuilderDefault(t).
|
||||
// Add ConfigMap with the same org name in noNs, "ns1" and "ns2" namespaces
|
||||
AddWithName(orgname, map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
@@ -715,7 +724,7 @@ func TestNameReferenceNamespace(t *testing.T) {
|
||||
AddWithNsAndName(ns1, orgname, deploymentMap(ns1, prefixedname, orgname, orgname)).
|
||||
AddWithNsAndName(ns2, orgname, deploymentMap(ns2, suffixedname, orgname, orgname)).ResMap()
|
||||
|
||||
expected := resmaptest_test.NewSeededRmBuilder(t, rf, m.ShallowCopy()).
|
||||
expected := resmaptest_test.NewSeededRmBuilderDefault(t, m.ShallowCopy()).
|
||||
ReplaceResource(deploymentMap(defaultNs, modifiedname, modifiedname, modifiedname)).
|
||||
ReplaceResource(deploymentMap(ns1, prefixedname, prefixedname, prefixedname)).
|
||||
ReplaceResource(deploymentMap(ns2, suffixedname, suffixedname, suffixedname)).ResMap()
|
||||
@@ -726,8 +735,9 @@ func TestNameReferenceNamespace(t *testing.T) {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
m.RemoveBuildAnnotations()
|
||||
if err = expected.ErrorIfNotEqualLists(m); err != nil {
|
||||
t.Fatalf("actual doesn't match expected: %v", err)
|
||||
t.Fatalf(notEqualErrFmt, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -735,9 +745,7 @@ func TestNameReferenceNamespace(t *testing.T) {
|
||||
// object with the same original names (uniquename) in different namespaces
|
||||
// and with different current Id.
|
||||
func TestNameReferenceClusterWide(t *testing.T) {
|
||||
rf := resource.NewFactory(
|
||||
kunstruct.NewKunstructuredFactoryImpl())
|
||||
m := resmaptest_test.NewRmBuilder(t, rf).
|
||||
m := resmaptest_test.NewRmBuilderDefault(t).
|
||||
// Add ServiceAccount with the same org name in noNs, "ns1" and "ns2" namespaces
|
||||
AddWithName(orgname, map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
@@ -789,9 +797,9 @@ func TestNameReferenceClusterWide(t *testing.T) {
|
||||
"name": modifiedname,
|
||||
},
|
||||
"roleRef": map[string]interface{}{
|
||||
"apiVersion": "rbac.authorization.k8s.io/v1",
|
||||
"kind": "ClusterRole",
|
||||
"name": orgname,
|
||||
"apiGroup": "rbac.authorization.k8s.io",
|
||||
"kind": "ClusterRole",
|
||||
"name": orgname,
|
||||
},
|
||||
"subjects": []interface{}{
|
||||
map[string]interface{}{
|
||||
@@ -816,7 +824,7 @@ func TestNameReferenceClusterWide(t *testing.T) {
|
||||
},
|
||||
}}).ResMap()
|
||||
|
||||
expected := resmaptest_test.NewSeededRmBuilder(t, rf, m.ShallowCopy()).
|
||||
expected := resmaptest_test.NewSeededRmBuilderDefault(t, m.ShallowCopy()).
|
||||
ReplaceResource(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "rbac.authorization.k8s.io/v1",
|
||||
@@ -845,9 +853,9 @@ func TestNameReferenceClusterWide(t *testing.T) {
|
||||
"name": modifiedname,
|
||||
},
|
||||
"roleRef": map[string]interface{}{
|
||||
"apiVersion": "rbac.authorization.k8s.io/v1",
|
||||
"kind": "ClusterRole",
|
||||
"name": modifiedname,
|
||||
"apiGroup": "rbac.authorization.k8s.io",
|
||||
"kind": "ClusterRole",
|
||||
"name": modifiedname,
|
||||
},
|
||||
// The following tests required a change in
|
||||
// getNameFunc implementation in order to leverage
|
||||
@@ -877,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)
|
||||
|
||||
@@ -889,8 +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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -898,9 +909,7 @@ func TestNameReferenceClusterWide(t *testing.T) {
|
||||
// object with the same original names (uniquename) in different namespaces
|
||||
// and with different current Id.
|
||||
func TestNameReferenceNamespaceTransformation(t *testing.T) {
|
||||
rf := resource.NewFactory(
|
||||
kunstruct.NewKunstructuredFactoryImpl())
|
||||
m := resmaptest_test.NewRmBuilder(t, rf).
|
||||
m := resmaptest_test.NewRmBuilderDefault(t).
|
||||
AddWithNsAndName(ns4, orgname, map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Secret",
|
||||
@@ -937,9 +946,9 @@ func TestNameReferenceNamespaceTransformation(t *testing.T) {
|
||||
"name": modifiedname,
|
||||
},
|
||||
"roleRef": map[string]interface{}{
|
||||
"apiVersion": "rbac.authorization.k8s.io/v1",
|
||||
"kind": "ClusterRole",
|
||||
"name": orgname,
|
||||
"apiGroup": "rbac.authorization.k8s.io",
|
||||
"kind": "ClusterRole",
|
||||
"name": orgname,
|
||||
},
|
||||
"subjects": []interface{}{
|
||||
map[string]interface{}{
|
||||
@@ -964,7 +973,7 @@ func TestNameReferenceNamespaceTransformation(t *testing.T) {
|
||||
},
|
||||
}}).ResMap()
|
||||
|
||||
expected := resmaptest_test.NewSeededRmBuilder(t, rf, m.ShallowCopy()).
|
||||
expected := resmaptest_test.NewSeededRmBuilderDefault(t, m.ShallowCopy()).
|
||||
ReplaceResource(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "rbac.authorization.k8s.io/v1",
|
||||
@@ -973,9 +982,9 @@ func TestNameReferenceNamespaceTransformation(t *testing.T) {
|
||||
"name": modifiedname,
|
||||
},
|
||||
"roleRef": map[string]interface{}{
|
||||
"apiVersion": "rbac.authorization.k8s.io/v1",
|
||||
"kind": "ClusterRole",
|
||||
"name": modifiedname,
|
||||
"apiGroup": "rbac.authorization.k8s.io",
|
||||
"kind": "ClusterRole",
|
||||
"name": modifiedname,
|
||||
},
|
||||
// The following tests required a change in
|
||||
// getNameFunc implementation in order to leverage
|
||||
@@ -1005,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)
|
||||
|
||||
@@ -1017,8 +1028,9 @@ func TestNameReferenceNamespaceTransformation(t *testing.T) {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
m.RemoveBuildAnnotations()
|
||||
if err = expected.ErrorIfNotEqualLists(m); err != nil {
|
||||
t.Fatalf("actual doesn't match expected: %v", err)
|
||||
t.Fatalf(notEqualErrFmt, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1026,9 +1038,7 @@ func TestNameReferenceNamespaceTransformation(t *testing.T) {
|
||||
// It validates the change done is IsSameFuzzyNamespace which
|
||||
// uses the IsNsEquals method instead of the simple == operator.
|
||||
func TestNameReferenceCandidateSelection(t *testing.T) {
|
||||
rf := resource.NewFactory(
|
||||
kunstruct.NewKunstructuredFactoryImpl())
|
||||
m := resmaptest_test.NewRmBuilder(t, rf).
|
||||
m := resmaptest_test.NewRmBuilderDefault(t).
|
||||
AddWithName("cm1", map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
@@ -1045,7 +1055,7 @@ func TestNameReferenceCandidateSelection(t *testing.T) {
|
||||
AddWithName("deploy1", deploymentMap("", "p1-deploy1", "cm1", "secret1")).
|
||||
ResMap()
|
||||
|
||||
expected := resmaptest_test.NewSeededRmBuilder(t, rf, m.ShallowCopy()).
|
||||
expected := resmaptest_test.NewSeededRmBuilderDefault(t, m.ShallowCopy()).
|
||||
ReplaceResource(deploymentMap("", "p1-deploy1", "p1-cm1-hash", "p1-secret1-hash")).
|
||||
ResMap()
|
||||
|
||||
@@ -1055,7 +1065,8 @@ func TestNameReferenceCandidateSelection(t *testing.T) {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
m.RemoveBuildAnnotations()
|
||||
if err = expected.ErrorIfNotEqualLists(m); err != nil {
|
||||
t.Fatalf("actual doesn't match expected: %v", err)
|
||||
t.Fatalf(notEqualErrFmt, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,8 @@
|
||||
package accumulator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
expansion2 "sigs.k8s.io/kustomize/api/internal/accumulator/expansion"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/filters/refvar"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/transform"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
)
|
||||
|
||||
@@ -17,7 +13,6 @@ type refVarTransformer struct {
|
||||
varMap map[string]interface{}
|
||||
replacementCounts map[string]int
|
||||
fieldSpecs []types.FieldSpec
|
||||
mappingFunc func(string) interface{}
|
||||
}
|
||||
|
||||
// newRefVarTransformer returns a new refVarTransformer
|
||||
@@ -31,66 +26,12 @@ func newRefVarTransformer(
|
||||
}
|
||||
}
|
||||
|
||||
// replaceVars accepts as 'in' a string, or string array, which can have
|
||||
// embedded instances of $VAR style variables, e.g. a container command string.
|
||||
// The function returns the string with the variables expanded to their final
|
||||
// values.
|
||||
func (rv *refVarTransformer) replaceVars(in interface{}) (interface{}, error) {
|
||||
switch vt := in.(type) {
|
||||
case []interface{}:
|
||||
var xs []interface{}
|
||||
for _, a := range in.([]interface{}) {
|
||||
x, ok := a.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected array of strings, found %v", in)
|
||||
}
|
||||
xs = append(xs, expansion2.Expand(x, rv.mappingFunc))
|
||||
}
|
||||
return xs, nil
|
||||
case map[string]interface{}:
|
||||
inMap := in.(map[string]interface{})
|
||||
xs := make(map[string]interface{}, len(inMap))
|
||||
for k, v := range inMap {
|
||||
s, ok := v.(string)
|
||||
if !ok {
|
||||
// This field can not contain a $(VAR) since it is not
|
||||
// of string type. For instance .spec.replicas: 3 in
|
||||
// a Deployment object
|
||||
xs[k] = v
|
||||
} else {
|
||||
// This field can potentially contains a $(VAR) since it is
|
||||
// of string type. For instance .spec.replicas: $(REPLICAS)
|
||||
// in a Deployment object
|
||||
xs[k] = expansion2.Expand(s, rv.mappingFunc)
|
||||
}
|
||||
}
|
||||
return xs, nil
|
||||
case interface{}:
|
||||
s, ok := in.(string)
|
||||
if !ok {
|
||||
// This field can not contain a $(VAR) since it is not of string type.
|
||||
return in, nil
|
||||
}
|
||||
// This field can potentially contain a $(VAR) since it is
|
||||
// of string type.
|
||||
return expansion2.Expand(s, rv.mappingFunc), nil
|
||||
// staticcheck erroneously claims that `case nil`
|
||||
// is unreachable here, so suppressing it.
|
||||
//nolint:staticcheck
|
||||
case nil:
|
||||
return nil, nil
|
||||
default:
|
||||
return "", fmt.Errorf("invalid type encountered %T", vt)
|
||||
}
|
||||
}
|
||||
|
||||
// UnusedVars returns slice of Var names that were unused
|
||||
// after a Transform run.
|
||||
func (rv *refVarTransformer) UnusedVars() []string {
|
||||
var unused []string
|
||||
for k := range rv.varMap {
|
||||
_, ok := rv.replacementCounts[k]
|
||||
if !ok {
|
||||
if _, ok := rv.replacementCounts[k]; !ok {
|
||||
unused = append(unused, k)
|
||||
}
|
||||
}
|
||||
@@ -100,16 +41,15 @@ func (rv *refVarTransformer) UnusedVars() []string {
|
||||
// Transform replaces $(VAR) style variables with values.
|
||||
func (rv *refVarTransformer) Transform(m resmap.ResMap) error {
|
||||
rv.replacementCounts = make(map[string]int)
|
||||
rv.mappingFunc = expansion2.MappingFuncFor(
|
||||
rv.replacementCounts, rv.varMap)
|
||||
mf := refvar.MakePrimitiveReplacer(rv.replacementCounts, rv.varMap)
|
||||
for _, res := range m.Resources() {
|
||||
for _, fieldSpec := range rv.fieldSpecs {
|
||||
if res.OrgId().IsSelected(&fieldSpec.Gvk) {
|
||||
if err := transform.MutateField(
|
||||
res.Map(), fieldSpec.PathSlice(),
|
||||
false, rv.replaceVars); err != nil {
|
||||
return err
|
||||
}
|
||||
err := res.ApplyFilter(refvar.Filter{
|
||||
MappingFunc: mf,
|
||||
FieldSpec: fieldSpec,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,10 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/k8sdeps/kunstruct"
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/resource"
|
||||
resmaptest_test "sigs.k8s.io/kustomize/api/testutils/resmaptest"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/resid"
|
||||
)
|
||||
|
||||
func TestRefVarTransformer(t *testing.T) {
|
||||
@@ -25,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",
|
||||
@@ -44,11 +40,9 @@ func TestRefVarTransformer(t *testing.T) {
|
||||
{Gvk: resid.Gvk{Version: "v1", Kind: "ConfigMap"}, Path: "data/map"},
|
||||
{Gvk: resid.Gvk{Version: "v1", Kind: "ConfigMap"}, Path: "data/slice"},
|
||||
{Gvk: resid.Gvk{Version: "v1", Kind: "ConfigMap"}, Path: "data/interface"},
|
||||
{Gvk: resid.Gvk{Version: "v1", Kind: "ConfigMap"}, Path: "data/nil"},
|
||||
{Gvk: resid.Gvk{Version: "v1", Kind: "ConfigMap"}, Path: "data/num"},
|
||||
},
|
||||
res: resmaptest_test.NewRmBuilder(
|
||||
t, resource.NewFactory(kunstruct.NewKunstructuredFactoryImpl())).
|
||||
res: resmaptest_test.NewRmBuilderDefault(t).
|
||||
Add(map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
@@ -63,7 +57,7 @@ func TestRefVarTransformer(t *testing.T) {
|
||||
"item4": "$(BAZ)+$(BAZ)",
|
||||
"item5": "$(BOO)",
|
||||
"item6": "if $(BOO)",
|
||||
"item7": 2019,
|
||||
"item7": int64(2019),
|
||||
},
|
||||
"slice": []interface{}{
|
||||
"$(FOO)",
|
||||
@@ -74,13 +68,11 @@ func TestRefVarTransformer(t *testing.T) {
|
||||
"if $(BOO)",
|
||||
},
|
||||
"interface": "$(FOO)",
|
||||
"nil": nil,
|
||||
"num": 2019,
|
||||
"num": int64(2019),
|
||||
}}).ResMap(),
|
||||
},
|
||||
expected: expected{
|
||||
res: resmaptest_test.NewRmBuilder(
|
||||
t, resource.NewFactory(kunstruct.NewKunstructuredFactoryImpl())).
|
||||
res: resmaptest_test.NewRmBuilderDefault(t).
|
||||
Add(map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
@@ -95,7 +87,7 @@ func TestRefVarTransformer(t *testing.T) {
|
||||
"item4": "5+5",
|
||||
"item5": true,
|
||||
"item6": "if true",
|
||||
"item7": 2019,
|
||||
"item7": int64(2019),
|
||||
},
|
||||
"slice": []interface{}{
|
||||
"replacementForFoo",
|
||||
@@ -106,21 +98,18 @@ func TestRefVarTransformer(t *testing.T) {
|
||||
"if true",
|
||||
},
|
||||
"interface": "replacementForFoo",
|
||||
"nil": nil,
|
||||
"num": 2019,
|
||||
"num": int64(2019),
|
||||
}}).ResMap(),
|
||||
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{
|
||||
{Gvk: resid.Gvk{Version: "v1", Kind: "ConfigMap"}, Path: "data/slice"},
|
||||
},
|
||||
res: resmaptest_test.NewRmBuilder(
|
||||
t, resource.NewFactory(kunstruct.NewKunstructuredFactoryImpl())).
|
||||
res: resmaptest_test.NewRmBuilderDefault(t).
|
||||
Add(map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
@@ -131,24 +120,60 @@ func TestRefVarTransformer(t *testing.T) {
|
||||
"slice": []interface{}{5}, // noticeably *not* a []string
|
||||
}}).ResMap(),
|
||||
},
|
||||
errMessage: "expected array of strings, found [5]",
|
||||
errMessage: `considering field 'data/slice' of object
|
||||
apiVersion: v1
|
||||
data:
|
||||
slice:
|
||||
- 5
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: cm1
|
||||
: invalid value type expect a string`,
|
||||
},
|
||||
"var replacement in nil": {
|
||||
given: given{
|
||||
varMap: map[string]interface{}{},
|
||||
fs: []types.FieldSpec{
|
||||
{Gvk: resid.Gvk{Version: "v1", Kind: "ConfigMap"}, Path: "data/nil"},
|
||||
},
|
||||
res: resmaptest_test.NewRmBuilderDefault(t).
|
||||
Add(map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "cm1",
|
||||
},
|
||||
"data": map[string]interface{}{
|
||||
"nil": nil, // noticeably *not* a []string
|
||||
}}).ResMap(),
|
||||
},
|
||||
expected: expected{
|
||||
res: resmaptest_test.NewRmBuilderDefault(t).
|
||||
Add(map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "cm1",
|
||||
},
|
||||
"data": map[string]interface{}{
|
||||
"nil": nil, // noticeably *not* a []string
|
||||
}}).ResMap(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -158,7 +183,13 @@ func TestRefVarTransformer(t *testing.T) {
|
||||
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
|
||||
@@ -41,13 +41,11 @@ func (ra *ResAccumulator) Vars() []types.Var {
|
||||
return ra.varSet.AsSlice()
|
||||
}
|
||||
|
||||
func (ra *ResAccumulator) AppendAll(
|
||||
resources resmap.ResMap) error {
|
||||
func (ra *ResAccumulator) AppendAll(resources resmap.ResMap) error {
|
||||
return ra.resMap.AppendAll(resources)
|
||||
}
|
||||
|
||||
func (ra *ResAccumulator) AbsorbAll(
|
||||
resources resmap.ResMap) error {
|
||||
func (ra *ResAccumulator) AbsorbAll(resources resmap.ResMap) error {
|
||||
return ra.resMap.AbsorbAll(resources)
|
||||
}
|
||||
|
||||
@@ -61,16 +59,25 @@ func (ra *ResAccumulator) GetTransformerConfig() *builtinconfig.TransformerConfi
|
||||
return ra.tConfig
|
||||
}
|
||||
|
||||
// MergeVars accumulates vars into ResAccumulator.
|
||||
// A Var is a tuple of name, object reference and field reference.
|
||||
// This func takes a list of vars from the current kustomization file and
|
||||
// annotates the accumulated resources with the names of the vars that match
|
||||
// those resources. E.g. if there's a var named "sam" that wants to get
|
||||
// its data from a ConfigMap named "james", and the resource list contains a
|
||||
// ConfigMap named "james", then that ConfigMap will be annotated with the
|
||||
// var name "sam". Later this annotation is used to find the data for "sam"
|
||||
// by digging into a particular fieldpath of "james".
|
||||
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 "+
|
||||
@@ -100,18 +107,17 @@ 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(
|
||||
"field specified in var '%v' "+
|
||||
"not found in corresponding resource", v)
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf(
|
||||
"var '%v' cannot be mapped to a field "+
|
||||
"in the set of known resources", v)
|
||||
@@ -127,10 +133,8 @@ func (ra *ResAccumulator) makeVarReplacementMap() (map[string]interface{}, error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result[v.Name] = s
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -161,6 +165,26 @@ func (ra *ResAccumulator) FixBackReferences() (err error) {
|
||||
if ra.tConfig.NameReference == nil {
|
||||
return nil
|
||||
}
|
||||
return ra.Transform(newNameReferenceTransformer(
|
||||
ra.tConfig.NameReference))
|
||||
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,26 +10,25 @@ 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/k8sdeps/kunstruct"
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/provider"
|
||||
"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, *resource.Factory) {
|
||||
func makeResAccumulator(t *testing.T) *ResAccumulator {
|
||||
ra := MakeEmptyAccumulator()
|
||||
err := ra.MergeConfig(builtinconfig.MakeDefaultConfig())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
rf := resource.NewFactory(
|
||||
kunstruct.NewKunstructuredFactoryImpl())
|
||||
err = ra.AppendAll(
|
||||
resmaptest_test.NewRmBuilder(t, rf).
|
||||
resmaptest_test.NewRmBuilderDefault(t).
|
||||
Add(map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
@@ -66,11 +65,11 @@ func makeResAccumulator(t *testing.T) (*ResAccumulator, *resource.Factory) {
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
return ra, rf
|
||||
return ra
|
||||
}
|
||||
|
||||
func TestResolveVarsHappy(t *testing.T) {
|
||||
ra, _ := makeResAccumulator(t)
|
||||
ra := makeResAccumulator(t)
|
||||
err := ra.MergeVars([]types.Var{
|
||||
{
|
||||
Name: "SERVICE_ONE",
|
||||
@@ -99,7 +98,7 @@ func TestResolveVarsHappy(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestResolveVarsOneUnused(t *testing.T) {
|
||||
ra, _ := makeResAccumulator(t)
|
||||
ra := makeResAccumulator(t)
|
||||
err := ra.MergeVars([]types.Var{
|
||||
{
|
||||
Name: "SERVICE_ONE",
|
||||
@@ -140,11 +139,10 @@ func expectLog(t *testing.T, log bytes.Buffer, expect string) {
|
||||
}
|
||||
|
||||
func TestResolveVarsVarNeedsDisambiguation(t *testing.T) {
|
||||
ra, rf := makeResAccumulator(t)
|
||||
|
||||
ra := makeResAccumulator(t)
|
||||
rm0 := resmap.New()
|
||||
err := rm0.Append(
|
||||
rf.FromMap(
|
||||
provider.NewDefaultDepProvider().GetResourceFactory().FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Service",
|
||||
@@ -213,8 +211,7 @@ func makeVarToNamepaceAndPath(
|
||||
}
|
||||
|
||||
func TestResolveVarConflicts(t *testing.T) {
|
||||
rf := resource.NewFactory(kunstruct.NewKunstructuredFactoryImpl())
|
||||
|
||||
rf := provider.NewDefaultDepProvider().GetResourceFactory()
|
||||
// create configmaps in foo and bar namespaces with `data.provider` values.
|
||||
fooAws := makeNamespacedConfigMapWithDataProviderValue("foo", "aws")
|
||||
barAws := makeNamespacedConfigMapWithDataProviderValue("bar", "aws")
|
||||
@@ -228,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")
|
||||
}
|
||||
@@ -250,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")
|
||||
@@ -261,7 +267,7 @@ func TestResolveVarConflicts(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestResolveVarsGoodResIdBadField(t *testing.T) {
|
||||
ra, _ := makeResAccumulator(t)
|
||||
ra := makeResAccumulator(t)
|
||||
err := ra.MergeVars([]types.Var{
|
||||
{
|
||||
Name: "SERVICE_ONE",
|
||||
@@ -286,7 +292,7 @@ func TestResolveVarsGoodResIdBadField(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestResolveVarsUnmappableVar(t *testing.T) {
|
||||
ra, _ := makeResAccumulator(t)
|
||||
ra := makeResAccumulator(t)
|
||||
err := ra.MergeVars([]types.Var{
|
||||
{
|
||||
Name: "SERVICE_THREE",
|
||||
@@ -310,7 +316,7 @@ func TestResolveVarsUnmappableVar(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestResolveVarsWithNoambiguation(t *testing.T) {
|
||||
ra1, rf := makeResAccumulator(t)
|
||||
ra1 := makeResAccumulator(t)
|
||||
err := ra1.MergeVars([]types.Var{
|
||||
{
|
||||
Name: "SERVICE_ONE",
|
||||
@@ -327,7 +333,7 @@ func TestResolveVarsWithNoambiguation(t *testing.T) {
|
||||
// Create another accumulator having a resource with different prefix
|
||||
ra2 := MakeEmptyAccumulator()
|
||||
|
||||
m := resmaptest_test.NewRmBuilder(t, rf).
|
||||
m := resmaptest_test.NewRmBuilderDefault(t).
|
||||
Add(map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
@@ -348,19 +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") // original name remains "backendOne"
|
||||
|
||||
err = ra2.AppendAll(m)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
@@ -402,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,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])
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user