mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-14 10:30:59 +00:00
Compare commits
3785 Commits
kustomize/
...
cmd/config
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb768687ea | ||
|
|
7bc51153d0 | ||
|
|
2b3d9dd67b | ||
|
|
98fce99f96 | ||
|
|
3c1e695f3f | ||
|
|
91ff4eefb2 | ||
|
|
f0c0d931fe | ||
|
|
f5b2b751eb | ||
|
|
cfd9278fd8 | ||
|
|
ba55d95542 | ||
|
|
851b3fc28c | ||
|
|
90493ec374 | ||
|
|
51b767b06e | ||
|
|
e3160373f0 | ||
|
|
a5b61016bb | ||
|
|
d62cc6d6a2 | ||
|
|
bcebad1664 | ||
|
|
9abf5fca3c | ||
|
|
c65ef489ca | ||
|
|
c754eadabc | ||
|
|
816e2365bf | ||
|
|
302cc866ad | ||
|
|
bf97d23a00 | ||
|
|
3687250ca2 | ||
|
|
69e5228264 | ||
|
|
7af5ce56bd | ||
|
|
d711b275f0 | ||
|
|
9577d61167 | ||
|
|
2c23b960ff | ||
|
|
6e5d2674ce | ||
|
|
2554d690c8 | ||
|
|
cec9298b2d | ||
|
|
6e82b210a9 | ||
|
|
b0636459dd | ||
|
|
13e26004fd | ||
|
|
a1a0a49822 | ||
|
|
ff2a3d368b | ||
|
|
1c5ce6975f | ||
|
|
fe99674fcd | ||
|
|
7674c220b1 | ||
|
|
469ae33b50 | ||
|
|
fbd949a95d | ||
|
|
c996d1fcab | ||
|
|
41917ca588 | ||
|
|
37668d87c4 | ||
|
|
92197fda9e | ||
|
|
ff7b2f20d5 | ||
|
|
59c8268245 | ||
|
|
4f21d60045 | ||
|
|
77b1af2d91 | ||
|
|
bb0a520f40 | ||
|
|
987437857f | ||
|
|
1843b6f25f | ||
|
|
31b7cf9e0a | ||
|
|
964bb38ba2 | ||
|
|
c659306ee2 | ||
|
|
233f1a3c2a | ||
|
|
75de98e2ef | ||
|
|
3dbc88bf94 | ||
|
|
d701792aa1 | ||
|
|
4079056501 | ||
|
|
6dfc238aa2 | ||
|
|
3608f335fd | ||
|
|
56efec5abc | ||
|
|
2a608bd71c | ||
|
|
ee4b7847f0 | ||
|
|
ec38bbeb99 | ||
|
|
26999664e6 | ||
|
|
c0b61b9442 | ||
|
|
274a76fe84 | ||
|
|
a8f58b4080 | ||
|
|
542b7c7c4c | ||
|
|
16495c6ed7 | ||
|
|
738573b079 | ||
|
|
e65e571ed1 | ||
|
|
49b464fd4d | ||
|
|
9a875add84 | ||
|
|
a3d547ccd3 | ||
|
|
c4a8a99834 | ||
|
|
bc3b249489 | ||
|
|
cd2c6a1ad1 | ||
|
|
8c6af9440c | ||
|
|
6850408f6c | ||
|
|
ec445049be | ||
|
|
646915cb86 | ||
|
|
fe551be87b | ||
|
|
30280f81af | ||
|
|
8cb60f0c5d | ||
|
|
9d29f57789 | ||
|
|
a3e1c99915 | ||
|
|
0fe1236e20 | ||
|
|
b28f1e55b7 | ||
|
|
25ee506af4 | ||
|
|
a1c5d79d94 | ||
|
|
de5210b43a | ||
|
|
d9c4c749e2 | ||
|
|
01420768c8 | ||
|
|
d11342489a | ||
|
|
bd7bad19a1 | ||
|
|
dfc627068b | ||
|
|
166c2e766b | ||
|
|
2ee2d3e389 | ||
|
|
81edfb7ee8 | ||
|
|
4e7aebc20c | ||
|
|
5caed5b90a | ||
|
|
f4d8ccda10 | ||
|
|
4cde50ab14 | ||
|
|
2f115223cc | ||
|
|
92c505a211 | ||
|
|
e9ea7657ee | ||
|
|
4bcc57de74 | ||
|
|
b2d65ddc98 | ||
|
|
894ffec36a | ||
|
|
3db4a94281 | ||
|
|
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 | ||
|
|
3479b6691e | ||
|
|
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 | ||
|
|
699cc70a7c | ||
|
|
a63a472024 | ||
|
|
e894756003 | ||
|
|
55f55a5091 | ||
|
|
8401196ef9 | ||
|
|
14edc3890c | ||
|
|
897698fb29 | ||
|
|
ec9ae3d7b0 | ||
|
|
3a828941fa | ||
|
|
b63b5ce7cc | ||
|
|
23bd4390d3 | ||
|
|
21a0fd33a2 | ||
|
|
c6b6dec91f | ||
|
|
9cf4367db7 | ||
|
|
e9c118fd55 | ||
|
|
bfbb1971d4 | ||
|
|
6fabfe963e | ||
|
|
67cdd2e27e | ||
|
|
6c6b5f744d | ||
|
|
7775666c50 | ||
|
|
bdd7ae085e | ||
|
|
ba3e09849a | ||
|
|
236ae29e9a | ||
|
|
5c3bd83252 | ||
|
|
3674e0a91d | ||
|
|
f9631e4bb2 | ||
|
|
c419c1efc3 | ||
|
|
63f9f79fc0 | ||
|
|
33e68c0f97 | ||
|
|
556eb48651 | ||
|
|
5b26c3b4cc | ||
|
|
42e19d610a | ||
|
|
950b1c895f | ||
|
|
ca5feb7751 | ||
|
|
488a88ec6e | ||
|
|
fd3a4a88be | ||
|
|
e6147347a8 | ||
|
|
0b756877e1 | ||
|
|
0f4b5e6787 | ||
|
|
1b531c6ac7 | ||
|
|
f6cac7e7e8 | ||
|
|
fe0577a15f | ||
|
|
f68740be66 | ||
|
|
855ce1a8db | ||
|
|
6a50372dd5 | ||
|
|
5a0228629f | ||
|
|
def00220ce | ||
|
|
d3a7335bbc | ||
|
|
94095a63ff | ||
|
|
42d1f7b792 | ||
|
|
8912c454ff | ||
|
|
d4eb2c9426 | ||
|
|
d2b95fb09a | ||
|
|
298b3c8622 | ||
|
|
c12e95fe06 | ||
|
|
87c7a32ffe | ||
|
|
a7545bdad3 | ||
|
|
622a121042 | ||
|
|
c2ccfd72ad | ||
|
|
6d324d70c4 | ||
|
|
c7bc9d2066 | ||
|
|
9567d7ef16 | ||
|
|
e5b0ceb4e3 | ||
|
|
5fb238a581 | ||
|
|
3ddc9af6c5 | ||
|
|
128e171c20 | ||
|
|
c2681b6fae | ||
|
|
ae5c392319 | ||
|
|
66bcf84682 | ||
|
|
436dada184 | ||
|
|
a689e0c2b4 | ||
|
|
30ae7183a4 | ||
|
|
279a9b673f | ||
|
|
0ac45c65c9 | ||
|
|
d7fb813c16 | ||
|
|
83c5c4d1f1 | ||
|
|
7259d3eb48 | ||
|
|
0e44202c20 | ||
|
|
0d90b769f1 | ||
|
|
7ad2791072 | ||
|
|
6de94548ba | ||
|
|
b1b190227e | ||
|
|
4ceddaa8f4 | ||
|
|
9e64ac5315 | ||
|
|
f24ec14956 | ||
|
|
98a92a6443 | ||
|
|
8bb612889c | ||
|
|
e3ec184e92 | ||
|
|
3019230283 | ||
|
|
69adcf9aaf | ||
|
|
8d543d8483 | ||
|
|
94a55210e1 | ||
|
|
441581b745 | ||
|
|
a066ba9628 | ||
|
|
3a0dd72c88 | ||
|
|
d4ed285fd1 | ||
|
|
4cae8cfe9b | ||
|
|
b044a52a84 | ||
|
|
5607478d8e | ||
|
|
d60cf8ebc5 | ||
|
|
ef04983392 | ||
|
|
2153863355 | ||
|
|
4afab168c5 | ||
|
|
2c52e3a851 | ||
|
|
781e396122 | ||
|
|
cebb1b31ab | ||
|
|
9137d2a39a | ||
|
|
95f4ecd261 | ||
|
|
600d4f2c0b | ||
|
|
6c4c5cf9ad | ||
|
|
5f5b23af58 | ||
|
|
ae5a690146 | ||
|
|
625e011e2b | ||
|
|
01ce33b926 | ||
|
|
a323d78bbc | ||
|
|
69dc34500a | ||
|
|
919bdb84c9 | ||
|
|
178f4e21f0 | ||
|
|
d732a6faab | ||
|
|
f053ca6a5f | ||
|
|
dfc5c32af5 | ||
|
|
80b3f4e00a | ||
|
|
4646bca230 | ||
|
|
57ca8fa321 | ||
|
|
de7fa4bf3a | ||
|
|
af1280ea43 | ||
|
|
0dd191c0f6 | ||
|
|
e20e126d65 | ||
|
|
abf862fff1 | ||
|
|
27cf3981ca | ||
|
|
2c39ff0fa0 | ||
|
|
afc14afe45 | ||
|
|
6e91e0667d | ||
|
|
1aca8b8b9e | ||
|
|
448c060084 | ||
|
|
b3951942e3 | ||
|
|
b78464c8b1 | ||
|
|
9bd4f78288 | ||
|
|
85e9fa94b0 | ||
|
|
c754927112 | ||
|
|
8049c57b72 | ||
|
|
695ec44bf7 | ||
|
|
5b394b2079 | ||
|
|
97c2ac77cd | ||
|
|
3183fcc926 | ||
|
|
0af9ca1266 | ||
|
|
a6111b5c3c | ||
|
|
fdbd1bdbbb | ||
|
|
16baf7a955 | ||
|
|
69aea07c4e | ||
|
|
2250ad3e18 | ||
|
|
c107d1ddff | ||
|
|
e32aa8ddb2 | ||
|
|
b92de5cb80 | ||
|
|
ecfa732a04 | ||
|
|
f17b893dd2 | ||
|
|
09894d3022 | ||
|
|
fd3e84f701 | ||
|
|
22d5d4d2c1 | ||
|
|
145ba0c7ff | ||
|
|
a83433d5cf | ||
|
|
2d496e0efe | ||
|
|
171412cc98 | ||
|
|
68ab3b87d9 | ||
|
|
e9bd11caaa | ||
|
|
a895220743 | ||
|
|
ab2dc7fcb9 | ||
|
|
afde29601a | ||
|
|
be0f1a7fcb | ||
|
|
99d2994b98 | ||
|
|
94101fb7cc | ||
|
|
346071897e | ||
|
|
0772540214 | ||
|
|
6791688f5f | ||
|
|
28307bc435 | ||
|
|
29cadfe8b0 | ||
|
|
68f4f330f5 | ||
|
|
507779c9dc | ||
|
|
062d0f7b75 | ||
|
|
e783af2881 | ||
|
|
f2da1f621f | ||
|
|
294312f2fc | ||
|
|
ff79bd0b31 | ||
|
|
410e300243 | ||
|
|
e994b3b566 | ||
|
|
b39c522cc1 | ||
|
|
dc4bf03da2 | ||
|
|
a158eeaaff | ||
|
|
4cd3944860 | ||
|
|
79a48a8802 | ||
|
|
5a33e90f18 | ||
|
|
e77d1a881f | ||
|
|
343b938c73 | ||
|
|
25186e94af | ||
|
|
e4ba898e20 | ||
|
|
22a6017870 | ||
|
|
e62f1adabf | ||
|
|
7e2d3ff5ab | ||
|
|
fb6830c98a | ||
|
|
52e8a701ac | ||
|
|
160485ef19 | ||
|
|
cea1154cd9 | ||
|
|
4843718a2b | ||
|
|
a0c1979798 | ||
|
|
8576acf1aa | ||
|
|
1e7a764900 | ||
|
|
9b5ce5002a | ||
|
|
0952421800 | ||
|
|
df9af1869f | ||
|
|
dbb16dcb6d | ||
|
|
cc8fc99999 | ||
|
|
0f0efe2a4c | ||
|
|
36165d2843 | ||
|
|
3e9276271a | ||
|
|
af7df1448d | ||
|
|
518147c129 | ||
|
|
a0072a2cf9 | ||
|
|
c4518e964f | ||
|
|
c2eb09cd8f | ||
|
|
89b4e7ae02 | ||
|
|
714ff85806 | ||
|
|
589ddcb4fa | ||
|
|
3d09b0cb83 | ||
|
|
3012dd67ac | ||
|
|
60f826fb2d | ||
|
|
5d24062d42 | ||
|
|
d553919dfb | ||
|
|
661743c7e5 | ||
|
|
1dc22e3f0d | ||
|
|
808843457a | ||
|
|
42497c664f | ||
|
|
88cc78d5b7 | ||
|
|
9ac9a28db7 | ||
|
|
5cf0d887b1 | ||
|
|
58c2df2dec | ||
|
|
1644fdd076 | ||
|
|
cd25740b61 | ||
|
|
28045399f3 | ||
|
|
e630334837 | ||
|
|
b97df057c1 | ||
|
|
546a7386ff | ||
|
|
4b117c4736 | ||
|
|
bc968d17e3 | ||
|
|
51c2fe9742 | ||
|
|
25a38ad2b6 | ||
|
|
8046efd261 | ||
|
|
9a4acba118 | ||
|
|
d9d67e33e7 | ||
|
|
18d6a96f80 | ||
|
|
458a7c532c | ||
|
|
701c217791 | ||
|
|
e042a22d0f | ||
|
|
4fca00014c | ||
|
|
1c2eaa9d29 | ||
|
|
1f32e89d95 | ||
|
|
b7a379604c | ||
|
|
cea461e8f2 | ||
|
|
73157c7141 | ||
|
|
72dc895c52 | ||
|
|
2aca65a126 | ||
|
|
ac2e6990c3 | ||
|
|
8f0900f95e | ||
|
|
2549a8c6be | ||
|
|
88ebe1c73e | ||
|
|
08922cfe29 | ||
|
|
952a2ebbf4 | ||
|
|
075846c731 | ||
|
|
d531de6774 | ||
|
|
cedcf3ac04 | ||
|
|
8ce637a004 | ||
|
|
10250286c7 | ||
|
|
a43d43f2c4 | ||
|
|
8356979e1f | ||
|
|
46d9a8dc46 | ||
|
|
cb3854edaa | ||
|
|
083a266b0b | ||
|
|
d6fdf1c01a | ||
|
|
b679e33d47 | ||
|
|
e63b9ef825 | ||
|
|
4b5b0dfdce | ||
|
|
572260c0f0 | ||
|
|
0e5e2648b3 | ||
|
|
144471ce78 | ||
|
|
98cb7fc8cd | ||
|
|
ca8e00fc15 | ||
|
|
93b0b1b0b1 | ||
|
|
34a442bbef | ||
|
|
37918ae745 | ||
|
|
dca164e967 | ||
|
|
8ac2365406 | ||
|
|
c1a2bf14da | ||
|
|
a7af7df9cb | ||
|
|
8d148c7ee4 | ||
|
|
0b94a1405d | ||
|
|
b083187e6a | ||
|
|
4dcf81050e | ||
|
|
c97fa946d5 | ||
|
|
c35f5b0189 | ||
|
|
dd4aafe148 | ||
|
|
adb56abd99 | ||
|
|
def978b334 | ||
|
|
f7ca4fa106 | ||
|
|
ff7a7937c5 | ||
|
|
90c23026c2 | ||
|
|
e5e4f33ac7 | ||
|
|
5449dbc775 | ||
|
|
52c5a9d380 | ||
|
|
74d59f87c2 | ||
|
|
98ba8b7491 | ||
|
|
e05404f89c | ||
|
|
9e40cc177a | ||
|
|
315f7c6e7f | ||
|
|
13eedd95f9 | ||
|
|
a193129920 | ||
|
|
d2c205b082 | ||
|
|
02184dbfc5 | ||
|
|
5b1edcdd0e | ||
|
|
b4f9e9ae56 | ||
|
|
a17022f7cc | ||
|
|
615f36119e | ||
|
|
fd8c63772f | ||
|
|
07cfa207f1 | ||
|
|
d1a3758c39 | ||
|
|
7de391e0d6 | ||
|
|
c62a83c903 | ||
|
|
82a05738f0 | ||
|
|
e25651f250 | ||
|
|
75d8492c51 | ||
|
|
eba34f4071 | ||
|
|
823847d90a | ||
|
|
80dc4763bf | ||
|
|
589f274493 | ||
|
|
f63faca999 | ||
|
|
d596330a0a | ||
|
|
31a3a08f53 | ||
|
|
ffde2c452d | ||
|
|
93bfb5b6f6 | ||
|
|
28c919912a | ||
|
|
12d5ca68e9 | ||
|
|
847d415f50 | ||
|
|
31d70c4ee2 | ||
|
|
06174fb52f | ||
|
|
276270eb57 | ||
|
|
b3f5874978 | ||
|
|
4b68425d86 | ||
|
|
4d88032e11 | ||
|
|
909bc25e12 | ||
|
|
2df3a7fc08 | ||
|
|
2b0b29aec5 | ||
|
|
1dced55f60 | ||
|
|
68d6b9add6 | ||
|
|
44f5093ae3 | ||
|
|
557cd656ab | ||
|
|
650f111e63 | ||
|
|
81da8f6f99 | ||
|
|
32ed552ea6 | ||
|
|
0b12f6c73d | ||
|
|
cd3b87e483 | ||
|
|
d616c9c315 | ||
|
|
006953265f | ||
|
|
4c520697fe | ||
|
|
8f5a55f89b | ||
|
|
c15fb14ccd | ||
|
|
24711f9fae | ||
|
|
7db4996638 | ||
|
|
8efc528117 | ||
|
|
d48952451a | ||
|
|
cf6fd30c73 | ||
|
|
f206241e2a | ||
|
|
07b679955e | ||
|
|
59f8f4ae14 | ||
|
|
33d72e9ae6 | ||
|
|
e28af71b6f | ||
|
|
b738cc221c | ||
|
|
f17cec0b3f | ||
|
|
ab9d4c7e7b | ||
|
|
7bae481252 | ||
|
|
765a4888df | ||
|
|
f9822c014f | ||
|
|
1e36c1c74f | ||
|
|
c5c53011da | ||
|
|
fdfb58cc3e | ||
|
|
1079d8604c | ||
|
|
4454edc14c | ||
|
|
7d5304928b | ||
|
|
bf17177270 | ||
|
|
2320c10463 | ||
|
|
50cba50a8d | ||
|
|
2eabe24896 | ||
|
|
99b850555d | ||
|
|
69e3c01cbf | ||
|
|
77b5a4a215 | ||
|
|
214b5bcfb9 | ||
|
|
e1087cdfbc | ||
|
|
844824e8e9 | ||
|
|
8d8773fc0e | ||
|
|
bf71dce60c | ||
|
|
66e889e4c1 | ||
|
|
ee2228c5fc | ||
|
|
dd3a79bcf7 | ||
|
|
84ed659d8c | ||
|
|
fbceeb1770 | ||
|
|
a7b58257e1 | ||
|
|
b622306ff7 | ||
|
|
ee095371b8 | ||
|
|
73cb596122 | ||
|
|
d0425791ca | ||
|
|
bacb9e2249 | ||
|
|
c0c70b26d1 | ||
|
|
6837d1f196 | ||
|
|
f247955942 | ||
|
|
f7f0923375 | ||
|
|
d1556837e1 | ||
|
|
7c731f0109 | ||
|
|
ff125b8c6c | ||
|
|
9a4927607d | ||
|
|
6951623dd0 | ||
|
|
eb7602fe19 | ||
|
|
a1def720cc | ||
|
|
0866e2a54b | ||
|
|
99933bf571 | ||
|
|
b1fbf1958d | ||
|
|
e3971ffbff | ||
|
|
802d79ae32 | ||
|
|
6ae53cb732 | ||
|
|
0a8d367633 | ||
|
|
75cba52f60 | ||
|
|
5884290fff | ||
|
|
0152dbb0dc | ||
|
|
5d7269968a | ||
|
|
a0b997308a | ||
|
|
aa95c9a773 | ||
|
|
5907c9e141 | ||
|
|
266af31bcb | ||
|
|
988afcb82d | ||
|
|
ec96854186 | ||
|
|
26a6c8e47a | ||
|
|
d5666c4ff4 | ||
|
|
ec43561c2d | ||
|
|
4faf00b928 | ||
|
|
4b78440c59 | ||
|
|
aafe6b0260 | ||
|
|
74b45392fc | ||
|
|
f8ef55eeac | ||
|
|
897e7b6e61 | ||
|
|
70b1cfef4a | ||
|
|
4c9cc343cd | ||
|
|
5213426f0a | ||
|
|
5e22d9fedb | ||
|
|
a5cdc6c912 | ||
|
|
6a32d8ac42 | ||
|
|
abb9d5a5b1 | ||
|
|
e54455705d | ||
|
|
c39cf55509 | ||
|
|
1a89d09f40 | ||
|
|
4ee126c840 | ||
|
|
39cb87cf2b | ||
|
|
3c6d3845ec | ||
|
|
51e3e0ff29 | ||
|
|
5c382b3c57 | ||
|
|
065f70705d | ||
|
|
a3fba0cb07 | ||
|
|
77fa5c2921 | ||
|
|
cc84eb108d | ||
|
|
2409845437 | ||
|
|
83c7c29132 | ||
|
|
7a03fe7353 | ||
|
|
5bae01fa68 | ||
|
|
9d6bbfb38a | ||
|
|
dac97f6aeb | ||
|
|
1494cd3702 | ||
|
|
3dfc76b769 | ||
|
|
dab0f3cf22 | ||
|
|
8d5fc2f281 | ||
|
|
9b26390d92 | ||
|
|
7309a5cb06 | ||
|
|
099b6ff460 | ||
|
|
eb86d6a2d7 | ||
|
|
7d600c1c5a | ||
|
|
259624ac07 | ||
|
|
d16d1f81b0 | ||
|
|
b3ff51b9bc | ||
|
|
c9f50745ff | ||
|
|
95f0a44fc0 | ||
|
|
174b2ed62e | ||
|
|
594c48d19a | ||
|
|
096ad8c95c | ||
|
|
fd70213ca2 | ||
|
|
212ec66e91 | ||
|
|
02235007e1 | ||
|
|
6063a6bde8 | ||
|
|
6d92608877 | ||
|
|
e771902a07 | ||
|
|
2ce66c0a85 | ||
|
|
f7909fad71 | ||
|
|
5e00158b00 | ||
|
|
218880cdd9 | ||
|
|
31e5ab1e6d | ||
|
|
a9e998d27d | ||
|
|
b579bf2b03 | ||
|
|
cae8fd0013 | ||
|
|
de9f80c41b | ||
|
|
085b64cde4 | ||
|
|
0abad97de3 | ||
|
|
a2f2cc6a85 | ||
|
|
028811eb97 | ||
|
|
d67c03af12 | ||
|
|
c95a89c1f1 | ||
|
|
499246a4a4 | ||
|
|
bd3d413ffd | ||
|
|
9195f05bba | ||
|
|
63f7495e88 | ||
|
|
6994e41eb3 | ||
|
|
1d46edccb5 | ||
|
|
c771f24626 | ||
|
|
3e6b5918a2 | ||
|
|
7feb873ef6 | ||
|
|
3c72a93c21 | ||
|
|
f07cae82b0 | ||
|
|
2b48c4b08d | ||
|
|
855b2c3171 | ||
|
|
82f20da46e | ||
|
|
72c7db39f2 | ||
|
|
c852bb00f2 | ||
|
|
a5db82e3a0 | ||
|
|
0636368c8c | ||
|
|
3516bf1bb1 | ||
|
|
d5ba8533fa | ||
|
|
40db077d3d | ||
|
|
d4d95012c7 | ||
|
|
99b15004dd | ||
|
|
c777a3805d | ||
|
|
52facc13ed | ||
|
|
53e7c87604 | ||
|
|
8618613325 | ||
|
|
59b1662b92 | ||
|
|
efeb26c634 | ||
|
|
e287d68d0f | ||
|
|
3786db2dba | ||
|
|
46316198cb | ||
|
|
243394002f | ||
|
|
60f3ca6f70 | ||
|
|
5e9619d3fb | ||
|
|
b9053655db | ||
|
|
1f35b37712 | ||
|
|
d7484aacad | ||
|
|
98414e8bf6 | ||
|
|
c1f2dd3688 | ||
|
|
982b9e77d4 | ||
|
|
c75a47322b | ||
|
|
bb05d19a5a | ||
|
|
8c18970b56 | ||
|
|
2c615d78a2 | ||
|
|
45a9805656 | ||
|
|
504029281a | ||
|
|
2d699f93fc | ||
|
|
1653a70693 | ||
|
|
4667372114 | ||
|
|
6d9702561a | ||
|
|
4533e43009 | ||
|
|
c7b00733c1 | ||
|
|
b023570fae | ||
|
|
8c315ab874 | ||
|
|
014db05f9c | ||
|
|
d8f601248b | ||
|
|
9c75afca58 | ||
|
|
8f2f2c201f | ||
|
|
e66235f380 | ||
|
|
636167ac7f | ||
|
|
4eac2b10d6 | ||
|
|
d15a573544 | ||
|
|
8d22cbdcca | ||
|
|
b72db9e783 | ||
|
|
a446a4f4fe | ||
|
|
f81d766584 | ||
|
|
ef592606a0 | ||
|
|
4e322fb501 | ||
|
|
b1e48a46c7 | ||
|
|
83700461fc | ||
|
|
853181e36f | ||
|
|
e7e7648c55 | ||
|
|
20643c933e | ||
|
|
0227582684 | ||
|
|
c4db0f9a60 | ||
|
|
5479d32aa3 | ||
|
|
6aabe72fce | ||
|
|
ee110a8e02 | ||
|
|
65b6e1c2bb | ||
|
|
826da4b19e | ||
|
|
630ac1b318 | ||
|
|
23589cc2af | ||
|
|
b349351e6e | ||
|
|
7bddd14419 | ||
|
|
894e21acbf | ||
|
|
524d672307 | ||
|
|
d051588789 | ||
|
|
ea807fcdcc | ||
|
|
a7f0af939b | ||
|
|
4f926df7cf | ||
|
|
1dad3f0975 | ||
|
|
885a9db52e | ||
|
|
8d40ead9b7 | ||
|
|
86bcb47b7d | ||
|
|
f08594cc22 | ||
|
|
56b6fa90b3 | ||
|
|
292d950465 | ||
|
|
ee431f755c | ||
|
|
e00d0b98de | ||
|
|
a8b9741866 | ||
|
|
7855031ecc | ||
|
|
e12d57e6f2 | ||
|
|
0d2ae19c80 | ||
|
|
804cf6d71c | ||
|
|
e342b68f0a | ||
|
|
022805b56b | ||
|
|
eb57d4b510 | ||
|
|
a5a51ba76a | ||
|
|
32efef71f4 | ||
|
|
98c08b2b66 | ||
|
|
5ea34b2efb | ||
|
|
0f3d5c80e4 | ||
|
|
cabaccb9fd | ||
|
|
4d1fe6678f | ||
|
|
04af0e6648 | ||
|
|
7a696ef616 | ||
|
|
691c11d520 | ||
|
|
109ffdaec5 | ||
|
|
aac1b7dc24 | ||
|
|
96782d9584 | ||
|
|
b17ea88bf7 | ||
|
|
091964c2c4 | ||
|
|
38973a80c3 | ||
|
|
4a7b22cf23 | ||
|
|
21a544f188 | ||
|
|
43f44b88ce | ||
|
|
0086470fe1 | ||
|
|
6a3eaf8ba0 | ||
|
|
26e801d2f2 | ||
|
|
d5d908610d | ||
|
|
8a22efb213 | ||
|
|
8bd989abb1 | ||
|
|
a3f59f2f4f | ||
|
|
a4ee1c2e72 | ||
|
|
39fe903498 | ||
|
|
78abd4193a | ||
|
|
96926fecce | ||
|
|
7d852c7bab | ||
|
|
d36a5a9c2f | ||
|
|
f81cc75925 | ||
|
|
ab39586960 | ||
|
|
17503ae9d8 | ||
|
|
24988df444 | ||
|
|
11ce0128ad | ||
|
|
4c0ab89c87 | ||
|
|
50cc1968e4 | ||
|
|
51ba40d39b | ||
|
|
20184e9835 | ||
|
|
8e3b7b195e | ||
|
|
ba48f615e8 | ||
|
|
2be48ca96a | ||
|
|
85e9127071 | ||
|
|
06e70f74c2 | ||
|
|
5184872cb5 | ||
|
|
f39f28d38f | ||
|
|
114e676cb1 | ||
|
|
2826dddc32 | ||
|
|
7164e55831 | ||
|
|
39629ed3e7 | ||
|
|
002993379a | ||
|
|
63622dbf2f | ||
|
|
85e9779bd6 | ||
|
|
81bbd2f2e3 | ||
|
|
d41df982c5 | ||
|
|
7629a03dd6 | ||
|
|
3def13d479 | ||
|
|
51a79d554c | ||
|
|
6c2adc48dd | ||
|
|
efdd812cc1 | ||
|
|
cd6614a93b | ||
|
|
04bec8ed2e | ||
|
|
51b29d7023 | ||
|
|
3a2635bd2d | ||
|
|
443fa792c3 | ||
|
|
064f0641ba | ||
|
|
a36d5b76be | ||
|
|
5c0fbf9a7f | ||
|
|
9a9bdee605 | ||
|
|
11ef1f3921 | ||
|
|
ce2e685619 | ||
|
|
aa46b6ec44 | ||
|
|
c4d949333d | ||
|
|
702b10d524 | ||
|
|
7840b7f949 | ||
|
|
2cf5dc16cb | ||
|
|
036b93c8d0 | ||
|
|
cad69ae415 | ||
|
|
5b774d09e1 | ||
|
|
9cbabe9135 | ||
|
|
7e8f8a649c | ||
|
|
68a9389bfe | ||
|
|
5364b2198a | ||
|
|
63ff34ffe2 | ||
|
|
6944cea234 | ||
|
|
370502ed4b | ||
|
|
711bab85ae | ||
|
|
0b1ad031a9 | ||
|
|
300dd108d5 | ||
|
|
65522b9e9f | ||
|
|
270f832b07 | ||
|
|
f2dddf48ae | ||
|
|
37bab58095 | ||
|
|
76e72ada16 | ||
|
|
0571f13da1 | ||
|
|
b33d0f86f0 | ||
|
|
d43fc9979c | ||
|
|
735cefa456 | ||
|
|
a24bf6aece | ||
|
|
fae66446a8 | ||
|
|
6c2c08c4df | ||
|
|
fcfe798b75 | ||
|
|
a484bd2efd | ||
|
|
c8602cf9b6 | ||
|
|
caa8fdc3cd | ||
|
|
f0ae77abd5 | ||
|
|
5d1a0346b5 | ||
|
|
55284dc290 | ||
|
|
382425d974 | ||
|
|
ff6250cdb4 | ||
|
|
2a8a17e3af | ||
|
|
70ac18f720 | ||
|
|
bb6677f80a | ||
|
|
3408c3e4c6 | ||
|
|
f6ad78650e | ||
|
|
a3800701f1 | ||
|
|
6c4370cd0d | ||
|
|
052deacbb2 | ||
|
|
523ab2e35f | ||
|
|
9a015ca8d5 | ||
|
|
a24cc4d305 | ||
|
|
ef76575ab6 | ||
|
|
39094f2aeb | ||
|
|
49ebb3f155 | ||
|
|
fa507f782f | ||
|
|
da548f65ea | ||
|
|
8991b193c6 | ||
|
|
3c776b3435 | ||
|
|
cf61a360e0 | ||
|
|
a588f498ea | ||
|
|
1503b4c834 | ||
|
|
573d7b7234 | ||
|
|
1d94ee86df | ||
|
|
275bf05ae2 | ||
|
|
aec264d2b5 | ||
|
|
62f21cbe69 | ||
|
|
d70f3a7958 | ||
|
|
5707962df5 | ||
|
|
63c06eeaf1 | ||
|
|
b3ce3a7882 | ||
|
|
a8b5ec2c61 | ||
|
|
d190e1c18a | ||
|
|
96ac25fff5 | ||
|
|
1d988a0fd8 | ||
|
|
6f176b1507 | ||
|
|
d2f0b1b345 | ||
|
|
c95a40933b | ||
|
|
bc7b880ab1 | ||
|
|
e004c31700 | ||
|
|
ca9aa62c26 | ||
|
|
bfb1b44b15 | ||
|
|
9ebee1e247 | ||
|
|
cee7cb6589 | ||
|
|
4f905c9cff | ||
|
|
232c1c8ee9 | ||
|
|
61cf3e6ec5 | ||
|
|
1ce469f1fd | ||
|
|
a49c9de4a4 | ||
|
|
bada055cd3 | ||
|
|
d7e0b1ac31 | ||
|
|
5549035b69 | ||
|
|
025200cc12 | ||
|
|
154939803f | ||
|
|
64c30a0678 | ||
|
|
b7bef5dc44 | ||
|
|
87b680e1c0 | ||
|
|
0075d0a88c | ||
|
|
3fc359043a | ||
|
|
6b6a74af19 | ||
|
|
89fc3cbb94 | ||
|
|
437be2831f | ||
|
|
b05ab6e0e3 | ||
|
|
7097013426 | ||
|
|
29fbc564e3 | ||
|
|
42abcbd516 | ||
|
|
b7b7a5a79f | ||
|
|
ebcc49d064 | ||
|
|
807ca9c1e3 | ||
|
|
6cdcb1f436 | ||
|
|
91da8525c1 | ||
|
|
b604f03740 | ||
|
|
422ba21df0 | ||
|
|
20e13abbb4 | ||
|
|
5975761fbf | ||
|
|
ea7f74e9e0 | ||
|
|
90e1dbe5d0 | ||
|
|
daa9504890 | ||
|
|
baccf58ccf | ||
|
|
c7bdb3fbe4 | ||
|
|
967fe44e3f | ||
|
|
d0602c732b | ||
|
|
a4179fa87f | ||
|
|
c9bce3fc0a | ||
|
|
11aa07b17f | ||
|
|
72e7084639 | ||
|
|
073a11f3f1 | ||
|
|
0b3e63c85d | ||
|
|
32fc17fedd | ||
|
|
bf6982afa3 | ||
|
|
79d591e2b0 | ||
|
|
69bc776d30 | ||
|
|
2d54981bcd | ||
|
|
0cfc3b10fc | ||
|
|
beb30d79ec | ||
|
|
4f49d2883b | ||
|
|
0d36ff958f | ||
|
|
c683e6ae3c | ||
|
|
3ebeebabde | ||
|
|
a3b3449b1f | ||
|
|
1b8488da2c | ||
|
|
f5419e9f72 | ||
|
|
7a87c84403 | ||
|
|
0fcb3a014c | ||
|
|
0b38e6d284 | ||
|
|
d5c66cb3d4 | ||
|
|
b35b5aa73d | ||
|
|
bb409a5ea8 | ||
|
|
74e1b5d54b | ||
|
|
c626eae9bd | ||
|
|
7372a371b4 | ||
|
|
03cc4e3848 | ||
|
|
0b33b3501f | ||
|
|
8e2ec69d85 | ||
|
|
0ce076758d | ||
|
|
68195ffabb | ||
|
|
7eca29daee | ||
|
|
154208d331 | ||
|
|
a851232100 | ||
|
|
0c022db1e6 | ||
|
|
fec8881819 | ||
|
|
7b44f71caf | ||
|
|
2d3cb22bc0 | ||
|
|
59ce165355 | ||
|
|
a7201a38e4 | ||
|
|
80633137a9 | ||
|
|
b7b88cae76 | ||
|
|
e787144811 | ||
|
|
0f5256d952 | ||
|
|
53432ba4bb | ||
|
|
00f68c12a8 | ||
|
|
32ffbdf5ca | ||
|
|
0820865e1d | ||
|
|
9f9a1d4159 | ||
|
|
2a28b37b3c | ||
|
|
866303a0d7 | ||
|
|
bce9cb5710 | ||
|
|
a9d35cc598 | ||
|
|
e2f4339ec6 | ||
|
|
1a7e2561ff | ||
|
|
c7d78970fb | ||
|
|
8e5bce17dc | ||
|
|
39c7a06829 | ||
|
|
bf2e398b33 | ||
|
|
758d428264 | ||
|
|
5353db36f0 | ||
|
|
3623d9205e | ||
|
|
73d44f9d31 | ||
|
|
b024157c2e | ||
|
|
5c55915c57 | ||
|
|
1120c6bc7a | ||
|
|
da23b9a8b4 | ||
|
|
e851e5eb94 | ||
|
|
0bd872e6d5 | ||
|
|
96ee9e9146 | ||
|
|
377eb5b66d | ||
|
|
f4636f8555 | ||
|
|
89367be008 | ||
|
|
736f826e7e | ||
|
|
331bab494d | ||
|
|
39bbe6efe0 | ||
|
|
13c891f54a | ||
|
|
d93b5a161a | ||
|
|
4d07004977 | ||
|
|
e35eaaff17 | ||
|
|
c96cd82cab | ||
|
|
365583bc36 | ||
|
|
74e325db60 | ||
|
|
36b6a63066 | ||
|
|
5dde9485a2 | ||
|
|
9f80da28ae | ||
|
|
f454449cdb | ||
|
|
d49b8cdf90 | ||
|
|
087086cf3b | ||
|
|
8633763e9d | ||
|
|
d2f9cf171f | ||
|
|
18d3b9ad8b | ||
|
|
2bcf82c6a4 | ||
|
|
35e24067fc | ||
|
|
32c959cde0 | ||
|
|
5477bde7e5 | ||
|
|
3ead42fe27 | ||
|
|
cf8d53a195 | ||
|
|
a61d478f0d | ||
|
|
c340c30f25 | ||
|
|
aaaba99389 | ||
|
|
29e50ab476 | ||
|
|
3519cc56a1 | ||
|
|
983ac2be31 | ||
|
|
d050276662 | ||
|
|
37ee56fc9a | ||
|
|
ade4f8969c | ||
|
|
5ad69d27e3 | ||
|
|
dc6e31c23f | ||
|
|
af27ada685 | ||
|
|
474dfc916b | ||
|
|
b1122a3e0b | ||
|
|
863eca1c32 | ||
|
|
2e895c147e | ||
|
|
af131c7471 | ||
|
|
09ec25b045 | ||
|
|
7ac573ae51 | ||
|
|
02dbc0da98 | ||
|
|
bb09f82f3c | ||
|
|
778f92ca0d | ||
|
|
4152a91609 | ||
|
|
2e118b7c68 | ||
|
|
72eda992bd | ||
|
|
230e0ca752 | ||
|
|
14eb524b9e | ||
|
|
81d62f90bf | ||
|
|
d71d2df364 | ||
|
|
34f21f44a1 | ||
|
|
070e128e47 | ||
|
|
2e5222f8e2 | ||
|
|
3893e12897 | ||
|
|
186df6f7c8 | ||
|
|
5d3a904283 | ||
|
|
065a4b7e90 | ||
|
|
1a330f89d9 | ||
|
|
4655c01c9b | ||
|
|
1d3c3995ed | ||
|
|
62e5abd437 | ||
|
|
ecff981d1c | ||
|
|
0d1e085680 | ||
|
|
dae3ebcafe | ||
|
|
8b3723603c | ||
|
|
3ff8d4a099 | ||
|
|
2fc340db62 | ||
|
|
936dd090e6 | ||
|
|
7bbcba5d23 | ||
|
|
d7a6e35fec | ||
|
|
ed31a60e9b | ||
|
|
569fafba81 | ||
|
|
ae458d0c80 | ||
|
|
3af514fa9f | ||
|
|
3bb7c1ccc7 | ||
|
|
f364030557 | ||
|
|
52efd8c932 | ||
|
|
83e75a0f0a | ||
|
|
e02c48abd0 | ||
|
|
39c42d71f0 | ||
|
|
0c9a3756b4 | ||
|
|
3f417c7b5b | ||
|
|
4526cb14e8 | ||
|
|
595e41a3ec | ||
|
|
c801958d40 | ||
|
|
f9a4d5a14e | ||
|
|
118ba7eefe | ||
|
|
488bc5aceb | ||
|
|
4c6b995435 | ||
|
|
2786287444 | ||
|
|
4e7446540c | ||
|
|
90ecc5d30a | ||
|
|
8840085a32 | ||
|
|
1bd62ffce9 | ||
|
|
344e6f18dd | ||
|
|
8b9d374170 | ||
|
|
982ad409bd | ||
|
|
9555095de9 | ||
|
|
c6cc457f45 | ||
|
|
6d58848970 | ||
|
|
8a2c886ab2 | ||
|
|
891ba0f461 | ||
|
|
2f5be62387 | ||
|
|
a46046dac5 | ||
|
|
1404d2749d | ||
|
|
9fe9a2500a | ||
|
|
6186e4edb7 | ||
|
|
cfcf885031 | ||
|
|
54e92f1ab0 | ||
|
|
04f5e6c953 | ||
|
|
9000eb7f81 | ||
|
|
abeab51cae | ||
|
|
b154af8be4 | ||
|
|
ccd129f7a5 | ||
|
|
e2b56910f9 | ||
|
|
21f7fa07c0 | ||
|
|
92f4a09e0b | ||
|
|
ed83b2d8fa | ||
|
|
bda865e9e4 | ||
|
|
77b59760c1 | ||
|
|
4628705494 | ||
|
|
e619cec090 | ||
|
|
0cca76fbb8 | ||
|
|
e473433cba | ||
|
|
0cae0feb9b | ||
|
|
2437e1ffe7 | ||
|
|
00f7656f1b | ||
|
|
32c280664d | ||
|
|
abc57e481b | ||
|
|
594a3bf0d2 | ||
|
|
2094f23414 | ||
|
|
7b1a5f85ed | ||
|
|
7190ea2688 | ||
|
|
6bdb4fe2a6 | ||
|
|
62964bfcb4 | ||
|
|
e13c26b2f8 | ||
|
|
647731a6ad | ||
|
|
426407a1b2 | ||
|
|
3276e74d2d | ||
|
|
bbceb49fc4 | ||
|
|
950660ff63 | ||
|
|
f749a4a194 | ||
|
|
79cfdb0976 | ||
|
|
9ec4100ee1 | ||
|
|
6e7f7ce194 | ||
|
|
b1f514632a | ||
|
|
745b58b3d0 | ||
|
|
142c105500 | ||
|
|
5f8a8b545b | ||
|
|
ee659a70e4 | ||
|
|
837df94d67 | ||
|
|
6b90f13281 | ||
|
|
db5e2c42b0 | ||
|
|
d489bdedd7 | ||
|
|
8b10aea859 | ||
|
|
a7a28a85a4 | ||
|
|
2c8736ccb2 | ||
|
|
9062a83276 | ||
|
|
5ee6380b1c | ||
|
|
2880c2ae5d | ||
|
|
e0b766ee46 | ||
|
|
2ab884c879 | ||
|
|
ac9b0a3e9e | ||
|
|
5e1ddf38db | ||
|
|
011804e14d | ||
|
|
7753a04fdc | ||
|
|
f0d81c4fac | ||
|
|
fa8f504ff4 | ||
|
|
3577a7e174 | ||
|
|
e4274bccba | ||
|
|
4b64801d44 | ||
|
|
a9c5f90805 | ||
|
|
29a1b96b96 | ||
|
|
b37abbf057 | ||
|
|
3bef339186 | ||
|
|
fc4a73f816 | ||
|
|
6e6878730c | ||
|
|
71ce46416e | ||
|
|
a47eff804b | ||
|
|
c4d3a2ff3f | ||
|
|
e0f62c67f6 | ||
|
|
0988f74d39 | ||
|
|
ef82c736b9 | ||
|
|
fc57f530ee | ||
|
|
4bdfb1c511 | ||
|
|
697a6e9759 | ||
|
|
f6320ca379 | ||
|
|
24cf0c1fdc | ||
|
|
3900166fdf | ||
|
|
ad4eb87e2e | ||
|
|
19928abb6f | ||
|
|
8a1874d20d | ||
|
|
a280cdf5ee | ||
|
|
fdf8f1b3df | ||
|
|
105f25860e | ||
|
|
e35e0bff60 | ||
|
|
8095b16c9a | ||
|
|
d04b4a2899 | ||
|
|
aafeb75ef1 | ||
|
|
98431f6a00 | ||
|
|
1ce3d9e099 | ||
|
|
e199c7f805 | ||
|
|
49f17586ca | ||
|
|
be2e03681d | ||
|
|
127541f610 | ||
|
|
f5ff254203 | ||
|
|
a35f002139 | ||
|
|
bef157d6b3 | ||
|
|
2c2aa928cc | ||
|
|
1eb713157c | ||
|
|
272b7a6fcd | ||
|
|
5598d35e4b | ||
|
|
8c89f0946c | ||
|
|
12fc8f41c7 | ||
|
|
e44d1298df | ||
|
|
7e56c2c768 | ||
|
|
cc8b100331 | ||
|
|
2c1cd6de41 | ||
|
|
32b55109f7 | ||
|
|
21bf05d05e | ||
|
|
d5e88977b3 | ||
|
|
c66dd497c3 | ||
|
|
3dd3654792 | ||
|
|
de824c2e4d | ||
|
|
502f86a982 | ||
|
|
ae2bfc8ee6 | ||
|
|
49127f702c | ||
|
|
7a384bc0d8 | ||
|
|
79a891f488 | ||
|
|
a4da55b1bc | ||
|
|
0b9aa418c0 | ||
|
|
eaaefc128f | ||
|
|
60188ebe20 | ||
|
|
01105af14d | ||
|
|
751491551b | ||
|
|
b38c0bc5c1 | ||
|
|
b07ae5558a | ||
|
|
ae8143a9be | ||
|
|
7edea2646f | ||
|
|
90597d56c9 | ||
|
|
62e969c719 | ||
|
|
9853948ac6 | ||
|
|
53db9b8de4 | ||
|
|
2fc224c2ff | ||
|
|
3b904108bd | ||
|
|
6fbab07df5 | ||
|
|
46f1f1b5da | ||
|
|
b5dba5b4da | ||
|
|
75e9079baa | ||
|
|
7e205b46b8 | ||
|
|
a599d781ae | ||
|
|
c6c099a9d1 | ||
|
|
a9244f759e | ||
|
|
60e2375521 | ||
|
|
e0ba78f292 | ||
|
|
322f368cdf | ||
|
|
ec3668fd34 | ||
|
|
68af986e09 | ||
|
|
735c249bc4 | ||
|
|
7c8e2f3948 | ||
|
|
b8a13b6335 | ||
|
|
e0597683d4 | ||
|
|
425cc04a34 | ||
|
|
2252b3cc8b | ||
|
|
943693e8e4 | ||
|
|
05829497d8 | ||
|
|
87cf1231c3 | ||
|
|
1cae8f9858 | ||
|
|
129206a3c8 | ||
|
|
7bdb5d3cfa | ||
|
|
6309af43a7 | ||
|
|
ac1f4fec4a | ||
|
|
792caeac82 | ||
|
|
bb03507d98 | ||
|
|
4fc859b62e | ||
|
|
7cdb157a55 | ||
|
|
50ce2a66a3 | ||
|
|
dae03a9618 | ||
|
|
6dd48142cf | ||
|
|
3f1b100edc | ||
|
|
61c5afdf83 | ||
|
|
71e75283de | ||
|
|
98d2be5550 | ||
|
|
634c780d1b | ||
|
|
400140a401 | ||
|
|
e80bd10374 | ||
|
|
9242269b5b | ||
|
|
7e847dc907 | ||
|
|
6dbed7c16c | ||
|
|
1a3f622207 | ||
|
|
1a28e493cd | ||
|
|
d9239104aa | ||
|
|
f5805c64b2 | ||
|
|
f299b28693 | ||
|
|
ee678d6ce9 | ||
|
|
d5b44461b9 | ||
|
|
57059b74f5 | ||
|
|
cd0b06c4fd | ||
|
|
057893a254 | ||
|
|
1e14cc93c2 | ||
|
|
54e8aab374 | ||
|
|
4312ab3f4d | ||
|
|
6484259632 | ||
|
|
92dcc02ff1 | ||
|
|
fe6dffff2e | ||
|
|
e1205698e6 | ||
|
|
a6535c4be2 | ||
|
|
a28c353eab | ||
|
|
4791bf8e0b | ||
|
|
4025603396 | ||
|
|
c2dc7ae789 | ||
|
|
484ad36529 | ||
|
|
0e251ed96e | ||
|
|
afd24c6faf | ||
|
|
6b7236a206 | ||
|
|
f65cac96d6 | ||
|
|
0d79219e46 | ||
|
|
e4635b456a | ||
|
|
1b3b8522f9 | ||
|
|
4098d6fdfb | ||
|
|
4ba69b27f8 | ||
|
|
bffc0d7071 | ||
|
|
54b1549586 | ||
|
|
3803541bfd | ||
|
|
c7307a9b28 | ||
|
|
2102ddab7c | ||
|
|
f186a0d6bb | ||
|
|
b0f1f66d9a | ||
|
|
8c037ba109 | ||
|
|
5e5e3b19f5 | ||
|
|
ad94f2c0eb | ||
|
|
b333115314 | ||
|
|
50c9916eae | ||
|
|
e1b18d125d | ||
|
|
8d72bf6e5c | ||
|
|
a4c69d9cde | ||
|
|
ecb9b9efa4 | ||
|
|
2eacbeaa87 | ||
|
|
46be801a48 | ||
|
|
0bace652c3 | ||
|
|
f0779cd02f | ||
|
|
105a8e57c8 | ||
|
|
0a8d7c2be2 | ||
|
|
20d995c87e | ||
|
|
3703450931 | ||
|
|
95ad5d05b3 | ||
|
|
d4fa006ccb | ||
|
|
b2814c5310 | ||
|
|
c00cf120ff | ||
|
|
decca7fcd3 | ||
|
|
9914017f60 | ||
|
|
a077482e99 | ||
|
|
849e0f357a | ||
|
|
df7688002a | ||
|
|
e811da14d8 | ||
|
|
9e5af98a94 | ||
|
|
f2c7066088 | ||
|
|
3f5dc1e80a | ||
|
|
04bfb3e94d | ||
|
|
567bc834c9 | ||
|
|
49f8b2bee8 | ||
|
|
b710a76818 | ||
|
|
823c59666b | ||
|
|
e14c441e77 | ||
|
|
de4d50386d | ||
|
|
969a1e8f37 | ||
|
|
184735fa89 | ||
|
|
caa71a73fe | ||
|
|
2625502ddb | ||
|
|
7c1b477ff6 | ||
|
|
d25b6ff3dc | ||
|
|
68a196dbe5 | ||
|
|
c7600bc079 | ||
|
|
79884240ae | ||
|
|
3880b8df1d | ||
|
|
8aaa3f56f5 | ||
|
|
4a7ade6421 | ||
|
|
e9ab3da164 | ||
|
|
dff30b926e | ||
|
|
fd5db20a48 | ||
|
|
8e99dbdaf0 | ||
|
|
861c86a70a | ||
|
|
0a19a5dbd9 | ||
|
|
382c330f5b | ||
|
|
ce935448c1 | ||
|
|
49287a0f8f | ||
|
|
1433ea4faa | ||
|
|
daa14ae4bd | ||
|
|
46af583a57 | ||
|
|
f33069fc15 | ||
|
|
cf5d3e73c0 | ||
|
|
4f8eaacc9b | ||
|
|
47d6d498d6 | ||
|
|
1f85ce454d | ||
|
|
fc8c45cff5 | ||
|
|
d98af3f06a | ||
|
|
d942e6fa59 | ||
|
|
fdd2cc5004 | ||
|
|
59b1c81e7e | ||
|
|
ae0658869a | ||
|
|
5491202d69 | ||
|
|
c3716ff3e0 | ||
|
|
8e7c53b9e7 | ||
|
|
cbfe314778 | ||
|
|
2d39d64d3a | ||
|
|
cd84b65972 | ||
|
|
fd7574cd61 | ||
|
|
d28ef820ea | ||
|
|
e5c314a3ea | ||
|
|
189f65dab9 | ||
|
|
9942a9278f | ||
|
|
44db041682 | ||
|
|
a49a764705 | ||
|
|
680a0812c6 | ||
|
|
50c63d0021 | ||
|
|
37e167e8d1 | ||
|
|
038c070626 | ||
|
|
8bb8637213 | ||
|
|
7ae21cb933 | ||
|
|
8fb64ffb0c | ||
|
|
f5a7227e26 | ||
|
|
8a2b3cd1d9 | ||
|
|
61133f3e2e | ||
|
|
bf339173c8 | ||
|
|
e46108ada0 | ||
|
|
9e16c8ca50 | ||
|
|
763cb4e925 | ||
|
|
f0153997e1 | ||
|
|
6d141f2ad0 | ||
|
|
89e7b76d48 | ||
|
|
b7855dc959 | ||
|
|
6485a7cf3e | ||
|
|
fc92f4acd0 | ||
|
|
52a5e6ec99 | ||
|
|
752ddd087b | ||
|
|
2b22bfc16b | ||
|
|
7ce1f7e95a | ||
|
|
c722d4cd17 | ||
|
|
1a9d62617e | ||
|
|
dc66de6bf3 | ||
|
|
cb64e19da3 | ||
|
|
9bba761a14 | ||
|
|
f3e735153f | ||
|
|
31c5e89b1f | ||
|
|
a2b84fce86 | ||
|
|
eccef3bb0d | ||
|
|
a489f30183 | ||
|
|
7eaaedf9f6 | ||
|
|
d1b33e7468 | ||
|
|
3b2988bda8 | ||
|
|
84b75afae4 | ||
|
|
73fb32c85a | ||
|
|
5876a8cce0 | ||
|
|
c9ee7f3787 | ||
|
|
23f9f819eb | ||
|
|
7fe518b0c6 | ||
|
|
7baabf7a97 | ||
|
|
4a65ea8056 | ||
|
|
598854440a | ||
|
|
9f2163ae54 | ||
|
|
ee8598dcbd | ||
|
|
f8a25cc2b3 | ||
|
|
80dc481f9c | ||
|
|
be4d6f77b2 | ||
|
|
2a35bbffe4 | ||
|
|
59a70525fa | ||
|
|
ee4f404ae8 | ||
|
|
752ca4b37c | ||
|
|
964a5082b1 | ||
|
|
81d809f15d | ||
|
|
5f80480068 | ||
|
|
416e1fcc1a | ||
|
|
bf8af8efba | ||
|
|
980209e87c | ||
|
|
3345464b25 | ||
|
|
2a5f513bc3 | ||
|
|
f531ac065d | ||
|
|
b240092058 | ||
|
|
f0ca8f9c1a | ||
|
|
5d4e45c24d | ||
|
|
04516803f2 | ||
|
|
56d57c3088 | ||
|
|
b6d760dc6f | ||
|
|
c856f800d0 | ||
|
|
b86bea9749 | ||
|
|
675faaced2 | ||
|
|
248d0d57d6 | ||
|
|
a7cff1c75b | ||
|
|
96c2873cc0 | ||
|
|
ff60138efd | ||
|
|
672153f021 | ||
|
|
d81913de42 | ||
|
|
3ec583a3df | ||
|
|
14d4059f46 | ||
|
|
99701a1932 | ||
|
|
d8e5891498 | ||
|
|
53b5e0f602 | ||
|
|
e5382c59a2 | ||
|
|
bf119bf5b7 | ||
|
|
277f565f2e | ||
|
|
8019cb0405 | ||
|
|
362454413a | ||
|
|
562aaadf2c | ||
|
|
c868f01f82 | ||
|
|
83db25d6c4 | ||
|
|
e6306f60f4 | ||
|
|
dd3ff341e0 | ||
|
|
5381daf95a | ||
|
|
fd588879e8 | ||
|
|
03dfb03a16 | ||
|
|
927d0d0c63 | ||
|
|
0924269e76 | ||
|
|
2326362fd2 | ||
|
|
3da0afdda0 | ||
|
|
85d34531a2 | ||
|
|
4db30d16a6 | ||
|
|
a49507c79e | ||
|
|
7f1e93c6f5 | ||
|
|
7b71263583 | ||
|
|
75b8103215 | ||
|
|
2fa944b1cd | ||
|
|
aa09e3f3f9 | ||
|
|
8aaac77397 | ||
|
|
9255c991f4 | ||
|
|
d08140d3f7 | ||
|
|
f69d2d2e69 | ||
|
|
d0b874a643 | ||
|
|
b52c6a77f4 | ||
|
|
0a84a0d525 | ||
|
|
12a26e1cbc | ||
|
|
1a7cb3137e | ||
|
|
2605c124cc | ||
|
|
95f4d9d2bb | ||
|
|
7164f840aa | ||
|
|
c0d1bde30c | ||
|
|
75a761112c | ||
|
|
73f94c1981 | ||
|
|
003b3eccc2 | ||
|
|
df25f3a93a | ||
|
|
4448bcf22c | ||
|
|
5eef5f2777 | ||
|
|
52466ffe83 | ||
|
|
4542e4a122 | ||
|
|
875385609e | ||
|
|
24f3d7556f | ||
|
|
ffeae451ab | ||
|
|
c99278c67b | ||
|
|
867b795158 | ||
|
|
301e529a4a | ||
|
|
563564a198 | ||
|
|
bfa5c59dc6 | ||
|
|
f202ce34eb | ||
|
|
912a9c3baa | ||
|
|
b473faccca | ||
|
|
1bbd8b2c43 | ||
|
|
1be5292eb6 | ||
|
|
a8ece4b5f7 | ||
|
|
a6a5976304 | ||
|
|
c91e2283db | ||
|
|
2c9635967a | ||
|
|
2dd148af24 | ||
|
|
bae6418ca2 | ||
|
|
1db0248748 | ||
|
|
387afef19b | ||
|
|
9e47e585d4 | ||
|
|
1f8696865f | ||
|
|
87b6a4d6bc | ||
|
|
5fe0b9e1b2 | ||
|
|
9a25cb96b7 | ||
|
|
0f81cf52e7 | ||
|
|
eb75c039e5 | ||
|
|
0f0c6618a0 | ||
|
|
3e4354dfb8 | ||
|
|
06e10cc084 | ||
|
|
97f2f33460 | ||
|
|
382981fb43 | ||
|
|
519dacc10f | ||
|
|
02c0c2692f | ||
|
|
6df4efd145 | ||
|
|
4dab6268da | ||
|
|
4150026ec7 | ||
|
|
10cd82ce31 | ||
|
|
d0ce4fcf15 | ||
|
|
d54ff23560 | ||
|
|
8e4609a850 | ||
|
|
eabb476461 | ||
|
|
74255f6bad | ||
|
|
3dfe62fe55 | ||
|
|
ad9b869ddb | ||
|
|
5c4c19bf19 | ||
|
|
1f86a0ca5d | ||
|
|
db630a9d07 | ||
|
|
388dd13a5f | ||
|
|
67c9d469c4 | ||
|
|
28a55bbd9c | ||
|
|
bdacb941ab | ||
|
|
d87ad523de | ||
|
|
ec36993d42 | ||
|
|
6b5aebac22 | ||
|
|
883714e2e5 | ||
|
|
8b0f4bf714 | ||
|
|
e83b97ea1f | ||
|
|
48dfcf5a3e | ||
|
|
cb4498594b | ||
|
|
b0bd4b5410 | ||
|
|
dbf8a0fde4 | ||
|
|
3db1111f8e | ||
|
|
e482ffa3f9 | ||
|
|
2b1749778f | ||
|
|
6af51a1bfe | ||
|
|
e0b46acf2f | ||
|
|
2e33a69388 | ||
|
|
018698ec85 | ||
|
|
0029a8ce32 | ||
|
|
04d5494246 | ||
|
|
41a8bd208d | ||
|
|
6bb470dbd0 | ||
|
|
9fa0391ce9 | ||
|
|
bda22d08cc | ||
|
|
09faaa1b2c | ||
|
|
9a65634df6 | ||
|
|
300383959d | ||
|
|
ab17d8dd74 | ||
|
|
c5ba2ced3b | ||
|
|
4103580834 | ||
|
|
d36e3f015d | ||
|
|
975e071394 | ||
|
|
6542af600d | ||
|
|
9e6e928725 | ||
|
|
ad24ba2234 | ||
|
|
c7fd1dee8b | ||
|
|
606eb8a74c | ||
|
|
2340c98f6a | ||
|
|
a7607c20e3 | ||
|
|
0b5cf74945 | ||
|
|
0687699d27 | ||
|
|
357126fc4e | ||
|
|
0aeb53a2b3 | ||
|
|
efd7c8e3f7 | ||
|
|
152ee44b92 | ||
|
|
588297f1f9 | ||
|
|
6b81ae9a93 | ||
|
|
077c7b2d20 | ||
|
|
1a4330a7cb | ||
|
|
d08690a6aa | ||
|
|
ba925e833d | ||
|
|
4c15c42447 | ||
|
|
690e01c2ba | ||
|
|
133d8bff5e | ||
|
|
079c3307c1 | ||
|
|
e0070f7ec5 | ||
|
|
a45eca7e22 | ||
|
|
79c5f8a977 | ||
|
|
69d1699963 | ||
|
|
b6b8f4396f | ||
|
|
9ca5284e4f | ||
|
|
335292fe8a | ||
|
|
cf9edbfb3c | ||
|
|
364a2342df | ||
|
|
3301913178 | ||
|
|
ac3c53557e | ||
|
|
8bf98dd255 | ||
|
|
f2368201d9 | ||
|
|
f44574cf43 | ||
|
|
3054d69dd9 | ||
|
|
739c2a5bac | ||
|
|
a780bd6465 | ||
|
|
4716cb026f | ||
|
|
a186144a78 | ||
|
|
2ea4762d0f | ||
|
|
eb268fa722 | ||
|
|
3b3a1309e4 | ||
|
|
72db6dde9e | ||
|
|
6d30bc5c35 | ||
|
|
773629e544 | ||
|
|
cd50bf4e1e | ||
|
|
ff59e9b52f | ||
|
|
9a954d4f0b | ||
|
|
5c63c20390 | ||
|
|
a84f8d65db | ||
|
|
6e2335ec3d | ||
|
|
4cf6630fc0 | ||
|
|
d239d52217 | ||
|
|
c260105212 | ||
|
|
44cdf98a47 | ||
|
|
92390eabe4 | ||
|
|
7b8fa51ec5 | ||
|
|
af8e17a1ed | ||
|
|
e2eeb90639 | ||
|
|
1704977a4b | ||
|
|
7050c6a7b6 | ||
|
|
02f9b98b5a | ||
|
|
ce7ebe3299 | ||
|
|
3928ada0e5 | ||
|
|
0a8faced8f | ||
|
|
3c06debf98 | ||
|
|
fcee91eafd | ||
|
|
b0b3a705f4 | ||
|
|
67c8fbcc3c | ||
|
|
9b50b78ec8 | ||
|
|
943a1b0195 | ||
|
|
f77143cd34 | ||
|
|
b7d2ba2376 | ||
|
|
c28a0eb83d | ||
|
|
f7e5b5138b | ||
|
|
61149cbf21 | ||
|
|
310d516030 | ||
|
|
a9e3fe155b | ||
|
|
24837bad40 | ||
|
|
9686cc9861 | ||
|
|
a4784ee5ec | ||
|
|
3063560e77 | ||
|
|
bff0604bee | ||
|
|
9f8faa7d7e | ||
|
|
335077eade | ||
|
|
491baa74cb | ||
|
|
2f2d078669 | ||
|
|
b7bcb90057 | ||
|
|
35dc15b16b | ||
|
|
83f70877c8 | ||
|
|
98cd31b820 | ||
|
|
5416ae7365 | ||
|
|
644f2ddcdc | ||
|
|
46524d3b6a | ||
|
|
4f014d0262 | ||
|
|
0cf2057fc5 | ||
|
|
3f08e1546c | ||
|
|
10619fb0f7 | ||
|
|
c88e8cc057 | ||
|
|
327a3f5300 | ||
|
|
fddde81f9c | ||
|
|
22d07ed37d | ||
|
|
dee1c425da | ||
|
|
951d15bf17 | ||
|
|
0f82d2932c | ||
|
|
e2d7a06e9f | ||
|
|
286b9c1aed | ||
|
|
f54d4a5837 | ||
|
|
d9031fb2c9 | ||
|
|
3af5a8afea | ||
|
|
e2fd33c54a | ||
|
|
c90e0a4080 | ||
|
|
5de000ee3d | ||
|
|
c28b82510c | ||
|
|
fda3ba8af9 | ||
|
|
fd1356e5d8 | ||
|
|
a62f1364fe | ||
|
|
d1240bcc63 | ||
|
|
1c24fe7d16 | ||
|
|
e5c8b5ec8f | ||
|
|
180429774a | ||
|
|
586bba0b31 | ||
|
|
2ce138ab3a | ||
|
|
5e99ad000e | ||
|
|
0f0e740c21 | ||
|
|
33600189bc | ||
|
|
07d2500ee3 | ||
|
|
de6eb14867 | ||
|
|
85b71a31e3 | ||
|
|
16e7638220 | ||
|
|
04c23b2085 | ||
|
|
24db94dd0d | ||
|
|
5f862ba17c | ||
|
|
18ba3ee91b | ||
|
|
4e9d42fae7 | ||
|
|
52e57dab7f | ||
|
|
ba464a5e11 | ||
|
|
2734085fb0 | ||
|
|
d21ff7cfe6 | ||
|
|
3a15f450a9 | ||
|
|
bb77e7491a | ||
|
|
aa82240b4c | ||
|
|
cac7b46ebd | ||
|
|
367d0e042c | ||
|
|
d851305c33 | ||
|
|
0c52bd71ba | ||
|
|
41a008e9a3 | ||
|
|
2fadb4dd59 | ||
|
|
a88ee3f93c | ||
|
|
237848a80b | ||
|
|
9e3b837093 | ||
|
|
c4eca908ac | ||
|
|
72d9b4cbca | ||
|
|
19d94110b1 | ||
|
|
1756765dbc | ||
|
|
b306f8511c | ||
|
|
9778f867b5 | ||
|
|
a69ebf2b11 | ||
|
|
cf9c81f908 | ||
|
|
b95164b9a8 | ||
|
|
0551338958 | ||
|
|
40b7ad23ea | ||
|
|
ce66ceeed6 | ||
|
|
4e45af6265 | ||
|
|
07a9454215 | ||
|
|
9f5a936236 | ||
|
|
e6770e5f1e | ||
|
|
705b4ab212 | ||
|
|
2cb964ab8e | ||
|
|
949b10bf93 | ||
|
|
0115fbc3da | ||
|
|
cc4341c546 | ||
|
|
d0caea0ce1 | ||
|
|
f2ac5a2d0d | ||
|
|
78d14d0d75 | ||
|
|
d5034af5ca | ||
|
|
40ed9e6a44 | ||
|
|
c1d20546ec | ||
|
|
3cf6b8ec4d | ||
|
|
3aee7a9081 | ||
|
|
abefa2b155 | ||
|
|
5caba59073 | ||
|
|
281f932814 |
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 -->
|
||||
113
.github/workflows/go.yml
vendored
Normal file
113
.github/workflows/go.yml
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
name: Go
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: [ubuntu-latest]
|
||||
steps:
|
||||
|
||||
- name: Set up Go 1.x
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ^1.16
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Lint
|
||||
run: ./hack/kyaml-pre-commit.sh
|
||||
env:
|
||||
KUSTOMIZE_DOCKER_E2E: false # don't need to do e2e tests for linting
|
||||
|
||||
test-linux:
|
||||
name: Test Linux
|
||||
runs-on: [ubuntu-latest]
|
||||
steps:
|
||||
|
||||
- name: Set up Go 1.x
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ^1.16
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Test kyaml
|
||||
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
|
||||
env:
|
||||
KUSTOMIZE_DOCKER_E2E: true
|
||||
|
||||
test-macos:
|
||||
name: Test MacOS
|
||||
runs-on: [macos-latest]
|
||||
steps:
|
||||
|
||||
- name: Set up Go 1.x
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ^1.16
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Test kyaml
|
||||
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
|
||||
env:
|
||||
KUSTOMIZE_DOCKER_E2E: false # docker not installed on mac
|
||||
|
||||
test-windows:
|
||||
name: Test Windows
|
||||
runs-on: [windows-latest]
|
||||
steps:
|
||||
|
||||
- name: Set up Go 1.x
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ^1.16
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Test kyaml
|
||||
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
|
||||
env:
|
||||
KUSTOMIZE_DOCKER_E2E: false # docker on windows not working well yet
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -5,6 +5,9 @@
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
.idea
|
||||
*.iml
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
@@ -16,3 +19,5 @@
|
||||
|
||||
# macOS
|
||||
*.DS_store
|
||||
|
||||
.bin
|
||||
|
||||
50
.golangci-kustomize.yml
Normal file
50
.golangci-kustomize.yml
Normal file
@@ -0,0 +1,50 @@
|
||||
run:
|
||||
deadline: 5m
|
||||
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- bodyclose
|
||||
- deadcode
|
||||
- depguard
|
||||
# - dogsled
|
||||
- dupl
|
||||
# - errcheck
|
||||
# - funlen
|
||||
# - gochecknoinits
|
||||
- goconst
|
||||
# - gocritic
|
||||
- gocyclo
|
||||
- gofmt
|
||||
- goimports
|
||||
- golint
|
||||
- gosec
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- interfacer
|
||||
- lll
|
||||
- misspell
|
||||
- nakedret
|
||||
# - scopelint
|
||||
- staticcheck
|
||||
- structcheck
|
||||
# stylecheck demands that acronyms not be treated as words
|
||||
# in camelCase, so JsonOp become JSONOp, etc. Yuck.
|
||||
# - stylecheck
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unparam
|
||||
- unused
|
||||
- varcheck
|
||||
# - whitespace
|
||||
|
||||
linters-settings:
|
||||
dupl:
|
||||
threshold: 400
|
||||
lll:
|
||||
line-length: 170
|
||||
gocyclo:
|
||||
min-complexity: 15
|
||||
golint:
|
||||
min-confidence: 0.85
|
||||
@@ -1,30 +0,0 @@
|
||||
run:
|
||||
deadline: 5m
|
||||
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- dupl
|
||||
- goconst
|
||||
- gocyclo
|
||||
- gofmt
|
||||
- golint
|
||||
- govet
|
||||
- ineffassign
|
||||
- interfacer
|
||||
- lll
|
||||
- misspell
|
||||
- nakedret
|
||||
- structcheck
|
||||
- unparam
|
||||
- varcheck
|
||||
|
||||
linters-settings:
|
||||
dupl:
|
||||
threshold: 400
|
||||
lll:
|
||||
line-length: 170
|
||||
gocyclo:
|
||||
min-complexity: 15
|
||||
golint:
|
||||
min-confidence: 0.85
|
||||
38
.travis.yml
38
.travis.yml
@@ -1,38 +0,0 @@
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
# TODO: Uncomment when tests running on Windows.
|
||||
# - windows
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- tree
|
||||
homebrew:
|
||||
packages:
|
||||
- tree
|
||||
update: true
|
||||
|
||||
# Only clone the most recent commit.
|
||||
git:
|
||||
depth: 1
|
||||
|
||||
language: go
|
||||
|
||||
go:
|
||||
- "1.12"
|
||||
|
||||
go_import_path: sigs.k8s.io/kustomize
|
||||
|
||||
before_install:
|
||||
- source ./travis/consider-early-travis-exit.sh
|
||||
|
||||
# Skip the install process; let pre-commit.sh do it.
|
||||
install: true
|
||||
|
||||
script:
|
||||
- ./travis/pre-commit.sh
|
||||
|
||||
# TBD. Suppressing for now.
|
||||
notifications:
|
||||
email: false
|
||||
299
ARCHITECTURE.md
Normal file
299
ARCHITECTURE.md
Normal file
@@ -0,0 +1,299 @@
|
||||
# Architecture
|
||||
|
||||
* _Updated: December 2021_
|
||||
|
||||
This document describes the repository organization and the kustomize
|
||||
build process. It's meant to lower the barrier to learning and
|
||||
contributing to the code base.
|
||||
|
||||
If not kept up to date, it will just be a historical snapshot.
|
||||
|
||||
|
||||
## Repository layout
|
||||
|
||||
[human-edited docs]: https://github.com/kubernetes-sigs/cli-experimental/tree/master/site
|
||||
[generated docs]: https://github.com/kubernetes-sigs/cli-experimental/tree/master/docs
|
||||
[rendered docs]: https://kubectl.docs.kubernetes.io
|
||||
[openapi]: https://kubernetes.io/blog/2016/12/kubernetes-supports-openapi
|
||||
|
||||
[`api` module]: https://github.com/kubernetes-sigs/kustomize/blob/master/api/go.mod
|
||||
[`api`]: #the-api-module
|
||||
[`cmd/config` module]: https://github.com/kubernetes-sigs/kustomize/blob/master/cmd/config/go.mod
|
||||
[`cmd/config`]: #the-cmdconfig-module
|
||||
[`kustomize` module]: https://github.com/kubernetes-sigs/kustomize/blob/master/kustomize/go.mod
|
||||
[`kustomize`]: #the-kustomize-module
|
||||
[`kyaml` module]: https://github.com/kubernetes-sigs/kustomize/blob/master/kyaml/go.mod
|
||||
[`kyaml`]: #the-kyaml-module
|
||||
[`kyaml/kio.Filter`]: https://github.com/Kubernetes-sigs/kustomize/blob/master/kyaml/kio/kio.go
|
||||
[`go-yaml`]: https://github.com/go-yaml/yaml/tree/v3
|
||||
|
||||
|
||||
[3922]: https://github.com/kubernetes-sigs/kustomize/issues/3922
|
||||
|
||||
|
||||
|
||||
| directory | purpose |
|
||||
| ---------: | :---------- |
|
||||
| `api` | The [`api`] module, holding high level kustomize code, suitable for import by other programs. |
|
||||
| `cmd` | Various Go programs aiding repo management. See also `hack`. As an outlier, includes the special [`cmd/config`] module. |
|
||||
| `docs` | Old home of documentation; contains pointers to new homes: [human-edited docs], [generated docs] and [rendered docs]. |
|
||||
| `examples` | Full kustomization examples that run as pre-merge tests. |
|
||||
| `functions` | Examples of plugins in KRM function form. TODO([3922]): Move under `plugin`. |
|
||||
| `hack` | Various shell scripts to help with code management. |
|
||||
| `kustomize` | The [`kustomize`] module holds the `main.go` for kustomize. |
|
||||
| `kyaml` | The [`kyaml`] module, holding Kubernetes-specific YAML editing packages used by the [`api`] module. Wraps [`go-yaml`] v3.|
|
||||
| `plugin` | Examples of Kustomize plugins. |
|
||||
| `releasing` | Instructions for releasing the various modules. |
|
||||
| `site` | Old generated documentation, kept to provide redirection links to the new docs. |
|
||||
|
||||
|
||||
## Modules
|
||||
|
||||
[semantically versioned]: https://semver.org
|
||||
[Go modules]: https://github.com/golang/go/wiki/Modules
|
||||
|
||||
The [Go modules] in the kustomize repository are [semantically versioned].
|
||||
|
||||
|
||||
### `kustomize`
|
||||
|
||||
> _Depends on [`api`], [`cmd/config`], [`kyaml`]_
|
||||
|
||||
The [`kustomize` module] contains the `main.go` for `kustomize`, buildable with
|
||||
|
||||
```
|
||||
(cd kustomize; go install .)
|
||||
```
|
||||
|
||||
[appears in kubectl]: https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/kubectl/pkg/cmd/kustomize/kustomize.go
|
||||
|
||||
Below this are packages containing
|
||||
[cobra](http://github.com/spf13/cobra) commands implementing `build`,
|
||||
`edit`, `fix`, etc., packages linked together by `main.go`.
|
||||
|
||||
These command packages are intentionally public, semantically
|
||||
versioned, and can be used in other programs. Specifically, the
|
||||
`kustomize build` command [appears in kubectl] as `kubectl kustomize`.
|
||||
|
||||
The code in the `build` package is dominated by flag validation,
|
||||
with minimal business logic. The critical lines are something
|
||||
like
|
||||
|
||||
```
|
||||
# Make a kustomizer.
|
||||
k := krusty.MakeKustomizer(
|
||||
HonorKustomizeFlags(krusty.MakeDefaultOptions()),
|
||||
)
|
||||
|
||||
# Run the kustomizer, sending location of kustomization.yaml
|
||||
m := k.Run(fSys, "/path/to/dir")
|
||||
|
||||
# Write the result as YAML.
|
||||
writer.Write(m.AsYaml())
|
||||
```
|
||||
|
||||
The `krusty` package is in the [`api`] module.
|
||||
|
||||
### `api`
|
||||
|
||||
> _Depends on [`kyaml`] and code generated from builtin plugin modules_
|
||||
|
||||
The [`api` module] is used by CLI programs like `kustomize` and `kubectl`
|
||||
to read and honor `kustomization.yaml` files and all that implies.
|
||||
|
||||
The main public packages in the [`api` module] are
|
||||
|
||||
| package | |
|
||||
| --------: | :---------- |
|
||||
| `filters` | Implementations of [`kyaml/kio.Filter`] used by kustomize to transform Kubernetes objects. |
|
||||
| `konfig` | Configuration methods and constants in the kustomize API. |
|
||||
| `krusty` | Primary API entry point. Holds the kustomizer and hundreds of tests for it. |
|
||||
| `loader` | Loads kustomization files and the files they refer to, enforcing security rules. |
|
||||
| `resmap` | The primary internal data structure over which the kustomizer and filters work. |
|
||||
| `types` | The `Kustomization` object and ancillary structs. |
|
||||
|
||||
### `cmd/config`
|
||||
|
||||
> _Depends on [`kyaml`]_
|
||||
|
||||
This module contains cobra commands and kyaml-based functionality to
|
||||
provide unix-like file manipulation commands to kustomize like `grep`
|
||||
and `tree`. These commands may be included in any program that
|
||||
manipulates k8s YAML (e.g. kustomize).
|
||||
|
||||
### `kyaml`
|
||||
|
||||
> _Has no in-repo dependence_
|
||||
|
||||
The [`kyaml` module] is a kubernetes-focussed enhancement of [go-yaml].
|
||||
|
||||
The YAML manipulation performed by a kustomize is based on these libraries.
|
||||
|
||||
These libraries evolve independently of kustomize, and other programs depend on them.
|
||||
|
||||
The key public packages in the [`kyaml` module] include
|
||||
|
||||
| package | |
|
||||
| --------: | :---------- |
|
||||
| `errors` | Wrapper for the go-errors/errors lib |
|
||||
| `filesys` | A kustomize-specific file system abstraction, to ease writing tests |
|
||||
| `fn/framework` | An SDK for writing KRM Functions in Go |
|
||||
| `fn/runtime` | Implements the runtime for KRM Function extensions |
|
||||
| `kio` | Libraries for reading and writing collections of Kubernetes resources as RNodes |
|
||||
| `openapi` | Loads and accesses openapi schemas for schema-aware resource manipultaion |
|
||||
| `resid` | Representations to aid in unique identification of Kubernetes resources |
|
||||
| `yaml` | A Kubernetes-focused wrapper of [go-yaml], notably including the RNode object |
|
||||
|
||||
|
||||
-------
|
||||
|
||||
## How _kustomize build_ works
|
||||
|
||||
The command `kustomize build` accepts a single string argument,
|
||||
which must resolve to a directory, possibly in a git repository,
|
||||
called the _kustomization root_.
|
||||
|
||||
This directory must contain a file called `kustomization.yaml`, with
|
||||
YAML that marshals into a single instance of a `Kustomization` object.
|
||||
|
||||
For the remainder of this document, the word _kustomization_ refers to
|
||||
either of these things.
|
||||
|
||||
This kustomization is the access point to a directed, acyclic graph of
|
||||
Kubernetes objects, including other kustomizations, to include in a
|
||||
build.
|
||||
|
||||
Execution of `build` starts and ends in the [`api`] module,
|
||||
frequently dipping into the [`kyaml`] module for lower level
|
||||
YAML manipulation.
|
||||
|
||||
### The `build` flow
|
||||
|
||||
- Validate command lines arguments and flags.
|
||||
|
||||
- Make a `Kustomizer` as a function of those arguments.
|
||||
|
||||
- Call `Run` on the kustomizer, passing it the path to the
|
||||
kustomization.
|
||||
|
||||
`Run` returns an instance of `ResMap`, the `api` package's
|
||||
representation of a set of kubernetes `Resource` objects.
|
||||
|
||||
This structure offers resource lookup methods (map behavior),
|
||||
but also retains the resources in the order they were
|
||||
specified in kustomization files (list behavior).
|
||||
|
||||
Post-run, the objects are fully hydrated, per the
|
||||
instructions in the kustomization.
|
||||
|
||||
- Marshal the objects as YAML to a file or `stdout`.
|
||||
|
||||
|
||||
### The `Run` function
|
||||
|
||||
- Create various objects
|
||||
|
||||
- A `ResMap` factory.
|
||||
|
||||
Makes `ResMaps` from byte streams, other `ResMaps`, etc.
|
||||
|
||||
- A file `loader.Loader`.
|
||||
|
||||
It's fed an appropriate set of restrictions, and the path to the kustomization.
|
||||
|
||||
- A plugin loader.
|
||||
|
||||
It finds plugins (transformers, generators or validators)
|
||||
and prepares them for running.
|
||||
|
||||
- A `KustTarget` encapsulating all of the above.
|
||||
|
||||
A KustTarget contains one `Kustomization` and represents
|
||||
everything that kustomization can reach. This will include
|
||||
other `KustTarget` instances, each having a smaller purview than
|
||||
the one referencing it.
|
||||
|
||||
- Call `KustTarget.Load` to load its kustomization.
|
||||
|
||||
This step deals with deprecations and field changes.
|
||||
|
||||
- Load [openapi] data specified by the kustomization.
|
||||
|
||||
This is needed to recognize k8s kinds and their special
|
||||
properties, e.g. which kinds are cluster-scoped, which kinds
|
||||
refer to others, etc.
|
||||
|
||||
- Call `KustTarget.makeCustomizedResmap` to create the `ResMap` result.
|
||||
|
||||
This visits everything referenced by the kustomization,
|
||||
performing all generation, transformation and validation.
|
||||
|
||||
- Finish the `Run` with
|
||||
|
||||
- Optional reordering of objects in `ResMap`, overriding the
|
||||
FIFO rule.
|
||||
|
||||
- Optional addition of _kustomize build annotations_ to the
|
||||
resources. E.g. from which repo and file the resource was
|
||||
read, the fact that kustomize touched the resource, etc.
|
||||
These kustomize-specific annotations are intended for
|
||||
server-side data analytics, file structure traceability and
|
||||
reconstruction, etc.
|
||||
|
||||
### The `makeCustomizedResmap` function
|
||||
|
||||
This function starts the process of object transformation,
|
||||
as well as accumulation of recursively referenced data.
|
||||
|
||||
- Call `ra := KustTarget.AccumulateTarget`.
|
||||
|
||||
The result, `ra`, is a resource accumulator that contains
|
||||
everything referred to by the current kustomization, now fully
|
||||
hydrated.
|
||||
|
||||
- Uniquify names of generated objects by appending content hashes.
|
||||
|
||||
This cannot be done until the objects are complete.
|
||||
|
||||
- Fix all name references (given that names may have changed).
|
||||
|
||||
E.g. if a ConfigMaps was given a generated name, all objects that
|
||||
refer to that ConfigMap must be given its name.
|
||||
|
||||
- Resolve vars, replacing them with whatever they refer to (a legacy feature).
|
||||
|
||||
### The `AccumulateTarget` function
|
||||
|
||||
- Call `AccumulateResources` over the `resources` field (this can recurse).
|
||||
- Call `AccumulateComponents` over the `components` field (this can recurse),
|
||||
- Load legacy (pre-plugin) global kustomize configuration,
|
||||
- Load legacy (pre-openapi) _Custom Resource Definition_ data.
|
||||
- In the context of the data loaded above, run the kustomization's
|
||||
- generators,
|
||||
- transformers,
|
||||
- and validators.
|
||||
- Accumulate `vars` (make note of them for later replacement).
|
||||
|
||||
### `AccumulateResources` and component accumulation
|
||||
|
||||
- If the path is a file:
|
||||
- Accumulate the objects in the file (treating them
|
||||
as opaque kubernetes objects).
|
||||
|
||||
- If the path is a directory:
|
||||
- Create a new `KustTarget` referring to that directory's kustomization.
|
||||
- Call `subRa := KustTarget.AccumulateTarget`.
|
||||
- Call `ra.MergeAccumulator(subRa)`
|
||||
This completes a recursion.
|
||||
|
||||
- If the path is a git URL:
|
||||
- Clone the repository to a temporary directory.
|
||||
- Process the path optionally specified in the URL
|
||||
as a path in the clone.
|
||||
- If no path specified, work from the repository root.
|
||||
|
||||
|
||||
That's as deep as this discussion will go.
|
||||
|
||||
The deeper this document goes into the details, the faster
|
||||
it will get out of date.
|
||||
@@ -1,22 +1,71 @@
|
||||
[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._
|
||||
|
||||
## Getting Started
|
||||
|
||||
We have full documentation on how to get started contributing here:
|
||||
Dev guides:
|
||||
|
||||
- [Contributor License Agreement](https://git.k8s.io/community/CLA.md) Kubernetes projects require that you sign a Contributor License Agreement (CLA) before we can accept your pull requests
|
||||
- [Kubernetes Contributor Guide](http://git.k8s.io/community/contributors/guide) - Main contributor documentation, or you can just jump directly to the [contributing section](http://git.k8s.io/community/contributors/guide#contributing)
|
||||
- [Contributor Cheat Sheet](https://git.k8s.io/community/contributors/guide/contributor-cheatsheet.md) - Common resources for existing developers
|
||||
- [Contribution Guide]
|
||||
- [MacOS Dev Guide]
|
||||
- [Windows Dev Guide]
|
||||
|
||||
General resources for contributors:
|
||||
|
||||
- [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]
|
||||
|
||||
362
Makefile
362
Makefile
@@ -1,37 +1,351 @@
|
||||
BIN_NAME=kustomize
|
||||
# Copyright 2019 The Kubernetes Authors.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Makefile for kustomize CLI and API.
|
||||
|
||||
COVER_FILE=coverage.out
|
||||
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.1
|
||||
|
||||
export GO111MODULE=on
|
||||
# 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
|
||||
|
||||
all: test build
|
||||
ifndef REPO_NAME
|
||||
REPO_NAME := "kustomize"
|
||||
endif
|
||||
|
||||
test: generate-code test-lint test-go
|
||||
.PHONY: all
|
||||
all: install-tools verify-kustomize
|
||||
|
||||
test-go:
|
||||
go test -v ./...
|
||||
.PHONY: verify-kustomize
|
||||
verify-kustomize: \
|
||||
lint-kustomize \
|
||||
test-unit-kustomize-all \
|
||||
test-examples-kustomize-against-HEAD \
|
||||
test-examples-kustomize-against-v4-release
|
||||
|
||||
test-lint:
|
||||
golangci-lint run ./...
|
||||
# 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-unit-kustomize-all \
|
||||
test-unit-cmd-all \
|
||||
test-go-mod \
|
||||
test-examples-kustomize-against-HEAD \
|
||||
test-examples-kustomize-against-v4-release
|
||||
|
||||
generate-code:
|
||||
./plugin/generateBuiltins.sh $(GOPATH)
|
||||
.PHONY: verify-kustomize-e2e
|
||||
verify-kustomize-e2e: test-examples-e2e-kustomize
|
||||
|
||||
build:
|
||||
go build -o $(BIN_NAME) cmd/kustomize/main.go
|
||||
# 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.
|
||||
# This installs what kustomize wants to use.
|
||||
$(MYGOBIN)/golangci-lint-kustomize:
|
||||
rm -f $(CURDIR)/hack/golangci-lint
|
||||
GOBIN=$(CURDIR)/hack go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.23.8
|
||||
mv $(CURDIR)/hack/golangci-lint $(MYGOBIN)/golangci-lint-kustomize
|
||||
|
||||
install:
|
||||
go install $(PWD)/cmd/kustomize
|
||||
$(MYGOBIN)/mdrip:
|
||||
go install github.com/monopole/mdrip@v1.0.2
|
||||
|
||||
cover:
|
||||
# The plugin directory eludes coverage, and is therefore omitted
|
||||
go test ./pkg/... ./k8sdeps/... ./internal/... -coverprofile=$(COVER_FILE) && \
|
||||
go tool cover -html=$(COVER_FILE)
|
||||
$(MYGOBIN)/stringer:
|
||||
go get golang.org/x/tools/cmd/stringer
|
||||
|
||||
$(MYGOBIN)/goimports:
|
||||
go get golang.org/x/tools/cmd/goimports
|
||||
|
||||
clean:
|
||||
go clean
|
||||
rm -f $(BIN_NAME)
|
||||
rm -f $(COVER_FILE)
|
||||
# Build from local source.
|
||||
$(MYGOBIN)/gorepomod:
|
||||
cd cmd/gorepomod; \
|
||||
go install .
|
||||
|
||||
.PHONY: test build install clean generate-code test-go test-lint cover
|
||||
# Build from local source.
|
||||
$(MYGOBIN)/k8scopy:
|
||||
cd cmd/k8scopy; \
|
||||
go install .
|
||||
|
||||
# Build from local source.
|
||||
$(MYGOBIN)/pluginator:
|
||||
cd cmd/pluginator; \
|
||||
go install .
|
||||
|
||||
# Build from local source.
|
||||
$(MYGOBIN)/kustomize: build-kustomize-api
|
||||
cd kustomize; \
|
||||
go install .
|
||||
|
||||
.PHONY: install-tools
|
||||
install-tools: \
|
||||
$(MYGOBIN)/goimports \
|
||||
$(MYGOBIN)/golangci-lint-kustomize \
|
||||
$(MYGOBIN)/gorepomod \
|
||||
$(MYGOBIN)/helmV3 \
|
||||
$(MYGOBIN)/k8scopy \
|
||||
$(MYGOBIN)/mdrip \
|
||||
$(MYGOBIN)/pluginator \
|
||||
$(MYGOBIN)/stringer
|
||||
|
||||
### Begin kustomize plugin rules.
|
||||
#
|
||||
# The rules to deal with builtin plugins are a bit
|
||||
# complicated because
|
||||
#
|
||||
# - Every builtin plugin is a Go plugin -
|
||||
# meaning it gets its own module directory
|
||||
# (outside of the api module) with Go
|
||||
# code in a 'main' package per Go plugin rules.
|
||||
# - kustomize locates plugins using the
|
||||
# 'apiVersion' and 'kind' fields from the
|
||||
# plugin config file.
|
||||
# - k8s wants CamelCase in 'kind' fields.
|
||||
# - The module name (the last name in the path)
|
||||
# must be the lowercased 'kind' of the
|
||||
# plugin because Go and related tools
|
||||
# demand lowercase in import paths, but
|
||||
# allow CamelCase in file names.
|
||||
# - the generated code must live in the api
|
||||
# module (it's linked into the api).
|
||||
|
||||
# Where all generated builtin plugin code should go.
|
||||
pGen=api/internal/builtins
|
||||
# Where the builtin Go plugin modules live.
|
||||
pSrc=plugin/builtin
|
||||
|
||||
_builtinplugins = \
|
||||
AnnotationsTransformer.go \
|
||||
ConfigMapGenerator.go \
|
||||
IAMPolicyGenerator.go \
|
||||
HashTransformer.go \
|
||||
ImageTagTransformer.go \
|
||||
LabelTransformer.go \
|
||||
LegacyOrderTransformer.go \
|
||||
NamespaceTransformer.go \
|
||||
PatchJson6902Transformer.go \
|
||||
PatchStrategicMergeTransformer.go \
|
||||
PatchTransformer.go \
|
||||
PrefixTransformer.go \
|
||||
SuffixTransformer.go \
|
||||
ReplacementTransformer.go \
|
||||
ReplicaCountTransformer.go \
|
||||
SecretGenerator.go \
|
||||
ValueAddTransformer.go \
|
||||
HelmChartInflationGenerator.go
|
||||
|
||||
# Maintaining this explicit list of generated files, and
|
||||
# adding it as a dependency to a few targets, to assure
|
||||
# they get recreated if deleted. The rules below on how
|
||||
# to make them don't, by themselves, assure they will be
|
||||
# recreated if deleted.
|
||||
builtinplugins = $(patsubst %,$(pGen)/%,$(_builtinplugins))
|
||||
|
||||
# These rules are verbose, but assure that if a source file
|
||||
# is modified, the corresponding generated file, and only
|
||||
# that file, will be recreated.
|
||||
$(pGen)/AnnotationsTransformer.go: $(pSrc)/annotationstransformer/AnnotationsTransformer.go
|
||||
$(pGen)/ConfigMapGenerator.go: $(pSrc)/configmapgenerator/ConfigMapGenerator.go
|
||||
$(pGen)/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
|
||||
$(pGen)/LegacyOrderTransformer.go: $(pSrc)/legacyordertransformer/LegacyOrderTransformer.go
|
||||
$(pGen)/NamespaceTransformer.go: $(pSrc)/namespacetransformer/NamespaceTransformer.go
|
||||
$(pGen)/PatchJson6902Transformer.go: $(pSrc)/patchjson6902transformer/PatchJson6902Transformer.go
|
||||
$(pGen)/PatchStrategicMergeTransformer.go: $(pSrc)/patchstrategicmergetransformer/PatchStrategicMergeTransformer.go
|
||||
$(pGen)/PatchTransformer.go: $(pSrc)/patchtransformer/PatchTransformer.go
|
||||
$(pGen)/PrefixTransformer.go: $(pSrc)/prefixtransformer/PrefixTransformer.go
|
||||
$(pGen)/SuffixTransformer.go: $(pSrc)/suffixtransformer/SuffixTransformer.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))))))))))))))))))))))))))
|
||||
|
||||
$(pGen)/%.go: $(MYGOBIN)/pluginator
|
||||
@echo "generating $*"
|
||||
( \
|
||||
set -e; \
|
||||
cd $(pSrc)/$(call toLowerCase,$*); \
|
||||
go generate .; \
|
||||
cd ../../../$(pGen); \
|
||||
$(MYGOBIN)/goimports -w $*.go \
|
||||
)
|
||||
|
||||
# Target is for debugging.
|
||||
.PHONY: generate-kustomize-builtin-plugins
|
||||
generate-kustomize-builtin-plugins: $(builtinplugins)
|
||||
|
||||
.PHONY: build-kustomize-external-go-plugin
|
||||
build-kustomize-external-go-plugin:
|
||||
./hack/buildExternalGoPlugins.sh ./plugin
|
||||
|
||||
.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: $(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.
|
||||
.PHONY: build-kustomize-api
|
||||
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/krusty; OPENAPI_TEST=true go test -run TestCustomOpenAPIFieldFromComponentWithOverlays
|
||||
|
||||
.PHONY: test-unit-kustomize-plugins
|
||||
test-unit-kustomize-plugins:
|
||||
./hack/testUnitKustomizePlugins.sh
|
||||
|
||||
.PHONY: test-unit-kustomize-cli
|
||||
test-unit-kustomize-cli:
|
||||
cd kustomize; go test ./...
|
||||
|
||||
.PHONY: test-unit-kustomize-all
|
||||
test-unit-kustomize-all: \
|
||||
test-unit-kustomize-api \
|
||||
test-unit-kustomize-cli \
|
||||
test-unit-kustomize-plugins
|
||||
|
||||
test-unit-cmd-all:
|
||||
./hack/kyaml-pre-commit.sh
|
||||
|
||||
test-go-mod:
|
||||
./hack/check-go-mod.sh
|
||||
|
||||
.PHONY:
|
||||
test-examples-e2e-kustomize: $(MYGOBIN)/mdrip $(MYGOBIN)/kind
|
||||
( \
|
||||
set -e; \
|
||||
/bin/rm -f $(MYGOBIN)/kustomize; \
|
||||
echo "Installing kustomize from ."; \
|
||||
cd kustomize; go install .; cd ..; \
|
||||
./hack/testExamplesE2EAgainstKustomize.sh .; \
|
||||
)
|
||||
|
||||
.PHONY:
|
||||
test-examples-kustomize-against-HEAD: $(MYGOBIN)/kustomize $(MYGOBIN)/mdrip
|
||||
./hack/testExamplesAgainstKustomize.sh HEAD
|
||||
|
||||
.PHONY:
|
||||
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
|
||||
# uses kubeval for validation.
|
||||
# Don't want to add a hard dependence in go.mod file
|
||||
# to github.com/instrumenta/kubeval.
|
||||
# Instead, download the binary.
|
||||
$(MYGOBIN)/kubeval:
|
||||
( \
|
||||
set -e; \
|
||||
d=$(shell mktemp -d); cd $$d; \
|
||||
wget https://github.com/instrumenta/kubeval/releases/latest/download/kubeval-$(GOOS)-$(GOARCH).tar.gz; \
|
||||
tar xf kubeval-$(GOOS)-$(GOARCH).tar.gz; \
|
||||
mv kubeval $(MYGOBIN); \
|
||||
rm -rf $$d; \
|
||||
)
|
||||
|
||||
# linux only.
|
||||
# This is for testing an example plugin that uses helm to inflate a chart
|
||||
# for subsequent kustomization.
|
||||
# Don't want to add a hard dependence in go.mod file to helm.
|
||||
# Instead, download the binaries.
|
||||
$(MYGOBIN)/helmV2:
|
||||
( \
|
||||
set -e; \
|
||||
d=$(shell mktemp -d); cd $$d; \
|
||||
tgzFile=helm-v2.13.1-$(GOOS)-$(GOARCH).tar.gz; \
|
||||
wget https://storage.googleapis.com/kubernetes-helm/$$tgzFile; \
|
||||
tar -xvzf $$tgzFile; \
|
||||
mv $(GOOS)-$(GOARCH)/helm $(MYGOBIN)/helmV2; \
|
||||
rm -rf $$d \
|
||||
)
|
||||
|
||||
# Helm V3 differs from helm V2; downloading it to provide coverage for the
|
||||
# chart inflator plugin under helm v3.
|
||||
$(MYGOBIN)/helmV3:
|
||||
( \
|
||||
set -e; \
|
||||
d=$(shell mktemp -d); cd $$d; \
|
||||
tgzFile=helm-v3.6.3-$(GOOS)-$(GOARCH).tar.gz; \
|
||||
wget https://get.helm.sh/$$tgzFile; \
|
||||
tar -xvzf $$tgzFile; \
|
||||
mv $(GOOS)-$(GOARCH)/helm $(MYGOBIN)/helmV3; \
|
||||
rm -rf $$d \
|
||||
)
|
||||
|
||||
$(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-$(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: clean-kustomize-external-go-plugin
|
||||
go clean --cache
|
||||
rm -f $(builtinplugins)
|
||||
rm -f $(MYGOBIN)/goimports
|
||||
rm -f $(MYGOBIN)/golangci-lint-kustomize
|
||||
rm -f $(MYGOBIN)/kustomize
|
||||
rm -f $(MYGOBIN)/mdrip
|
||||
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
|
||||
nuke: clean
|
||||
go clean --modcache
|
||||
|
||||
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,11 +1,29 @@
|
||||
# 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:
|
||||
- monopole
|
||||
- pwittrock
|
||||
kustomize-maintainers:
|
||||
- droot
|
||||
- justinsb
|
||||
- liujingfang1
|
||||
kustomize-owners:
|
||||
- knverey
|
||||
- natasha41575
|
||||
kustomize-approvers:
|
||||
- knverey
|
||||
- natasha41575
|
||||
kustomize-reviewers:
|
||||
- knverey
|
||||
- natasha41575
|
||||
- yuwenma
|
||||
|
||||
kyaml-approvers:
|
||||
- mengqiy
|
||||
- mortent
|
||||
- phanimarupaka
|
||||
kyaml-reviewers:
|
||||
- mengqiy
|
||||
- mortent
|
||||
- phanimarupaka
|
||||
|
||||
emeritus-approvers:
|
||||
- liujingfang1
|
||||
- Shell32-Natsu
|
||||
- justinsb
|
||||
- monopole
|
||||
- pwittrock
|
||||
|
||||
93
README.md
93
README.md
@@ -9,30 +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://travis-ci.org/kubernetes-sigs/kustomize)
|
||||
[](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](docs/INSTALL.md).
|
||||
|
||||
Browse the [docs](docs) or jump right into the
|
||||
tested [examples](examples).
|
||||
|
||||
## kubectl integration
|
||||
|
||||
Since [v1.14][kubectl announcement] the kustomize build system has been included in kubectl.
|
||||
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](https://github.com/kubernetes-sigs/kustomize/tree/v2.0.3) |
|
||||
| v1.15.x | [v2.0.3](https://github.com/kubernetes-sigs/kustomize/tree/v2.0.3) |
|
||||
| v1.14.x | [v2.0.3](https://github.com/kubernetes-sigs/kustomize/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
|
||||
|
||||
@@ -107,7 +115,7 @@ Take the work from step (1) above, move it into a
|
||||
`someApp` subdirectory called `base`, then
|
||||
place overlays in a sibling directory.
|
||||
|
||||
An overlay is just another kustomization, refering to
|
||||
An overlay is just another kustomization, referring to
|
||||
the base, and referring to patches to apply to that
|
||||
base.
|
||||
|
||||
@@ -133,20 +141,8 @@ The YAML can be directly [applied] to a cluster:
|
||||
|
||||
## Community
|
||||
|
||||
To file bugs please read [this](docs/bugs.md).
|
||||
|
||||
Before working on an implementation, please
|
||||
|
||||
* Read the [eschewed feature list].
|
||||
* File an issue describing
|
||||
how the new feature would behave
|
||||
and label it [kind/feature].
|
||||
|
||||
### Other communication channels
|
||||
|
||||
- [Slack]
|
||||
- [Mailing List]
|
||||
- General kubernetes [community page]
|
||||
- [file a bug](https://kubernetes-sigs.github.io/kustomize/contributing/bugs/) instructions
|
||||
- [contribute a feature](https://kubernetes-sigs.github.io/kustomize/contributing/features/) instructions
|
||||
|
||||
### Code of conduct
|
||||
|
||||
@@ -155,32 +151,27 @@ is governed by the [Kubernetes Code of Conduct].
|
||||
|
||||
[`make`]: https://www.gnu.org/software/make
|
||||
[`sed`]: https://www.gnu.org/software/sed
|
||||
[DAM]: docs/glossary.md#declarative-application-management
|
||||
[KEP]: https://github.com/kubernetes/enhancements/blob/master/keps/sig-cli/0008-kustomize.md
|
||||
[DAM]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#declarative-application-management
|
||||
[KEP]: https://github.com/kubernetes/enhancements/blob/master/keps/sig-cli/2377-Kustomize/README.md
|
||||
[Kubernetes Code of Conduct]: code-of-conduct.md
|
||||
[Mailing List]: https://groups.google.com/forum/#!forum/kubernetes-sig-cli
|
||||
[Slack]: https://kubernetes.slack.com/messages/sig-cli
|
||||
[applied]: docs/glossary.md#apply
|
||||
[base]: docs/glossary.md#base
|
||||
[community page]: http://kubernetes.io/community/
|
||||
[declarative configuration]: docs/glossary.md#declarative-application-management
|
||||
[eschewed feature list]: docs/eschewedFeatures.md
|
||||
[applied]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#apply
|
||||
[base]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#base
|
||||
[declarative configuration]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#declarative-application-management
|
||||
[imageBase]: docs/images/base.jpg
|
||||
[imageOverlay]: docs/images/overlay.jpg
|
||||
[kind/feature]: https://github.com/kubernetes-sigs/kustomize/labels/kind%2Ffeature
|
||||
[kubectl announcement]: https://kubernetes.io/blog/2019/03/25/kubernetes-1-14-release-announcement
|
||||
[kubectl book]: https://kubectl.docs.kubernetes.io/pages/app_customization/introduction.html
|
||||
[kubectl book]: https://kubectl.docs.kubernetes.io/guides/introduction/kustomize/
|
||||
[kubernetes documentation]: https://kubernetes.io/docs/tasks/manage-kubernetes-objects/kustomization/
|
||||
[kubernetes style]: docs/glossary.md#kubernetes-style-object
|
||||
[kustomization]: docs/glossary.md#kustomization
|
||||
[overlay]: docs/glossary.md#overlay
|
||||
[overlays]: docs/glossary.md#overlay
|
||||
[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
|
||||
[overlay]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#overlay
|
||||
[overlays]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#overlay
|
||||
[release page]: https://github.com/kubernetes-sigs/kustomize/releases
|
||||
[resource]: docs/glossary.md#resource
|
||||
[resources]: docs/glossary.md#resource
|
||||
[resource]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#resource
|
||||
[resources]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#resource
|
||||
[sig-cli]: https://github.com/kubernetes/community/blob/master/sig-cli/README.md
|
||||
[variant]: docs/glossary.md#variant
|
||||
[variants]: docs/glossary.md#variant
|
||||
[variant]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#variant
|
||||
[variants]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#variant
|
||||
[v2.0.3]: https://github.com/kubernetes-sigs/kustomize/releases/tag/v2.0.3
|
||||
[v2.1.0]: https://github.com/kubernetes-sigs/kustomize/releases/tag/v2.1.0
|
||||
[workflows]: docs/workflows.md
|
||||
[workflows]: https://kubernetes-sigs.github.io/kustomize/guides
|
||||
|
||||
51
api/builtins/builtins.go
Normal file
51
api/builtins/builtins.go
Normal file
@@ -0,0 +1,51 @@
|
||||
// Copyright 2021 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Deprecated: Package api/builtins will not be available in API v1.
|
||||
package builtins
|
||||
|
||||
import (
|
||||
internal "sigs.k8s.io/kustomize/api/internal/builtins"
|
||||
)
|
||||
|
||||
type (
|
||||
AnnotationsTransformerPlugin = internal.AnnotationsTransformerPlugin
|
||||
ConfigMapGeneratorPlugin = internal.ConfigMapGeneratorPlugin
|
||||
HashTransformerPlugin = internal.HashTransformerPlugin
|
||||
HelmChartInflationGeneratorPlugin = internal.HelmChartInflationGeneratorPlugin
|
||||
IAMPolicyGeneratorPlugin = internal.IAMPolicyGeneratorPlugin
|
||||
ImageTagTransformerPlugin = internal.ImageTagTransformerPlugin
|
||||
LabelTransformerPlugin = internal.LabelTransformerPlugin
|
||||
LegacyOrderTransformerPlugin = internal.LegacyOrderTransformerPlugin
|
||||
NamespaceTransformerPlugin = internal.NamespaceTransformerPlugin
|
||||
PatchJson6902TransformerPlugin = internal.PatchJson6902TransformerPlugin
|
||||
PatchStrategicMergeTransformerPlugin = internal.PatchStrategicMergeTransformerPlugin
|
||||
PatchTransformerPlugin = internal.PatchTransformerPlugin
|
||||
PrefixTransformerPlugin = internal.PrefixTransformerPlugin
|
||||
SuffixTransformerPlugin = internal.SuffixTransformerPlugin
|
||||
ReplacementTransformerPlugin = internal.ReplacementTransformerPlugin
|
||||
ReplicaCountTransformerPlugin = internal.ReplicaCountTransformerPlugin
|
||||
SecretGeneratorPlugin = internal.SecretGeneratorPlugin
|
||||
ValueAddTransformerPlugin = internal.ValueAddTransformerPlugin
|
||||
)
|
||||
|
||||
var (
|
||||
NewAnnotationsTransformerPlugin = internal.NewAnnotationsTransformerPlugin
|
||||
NewConfigMapGeneratorPlugin = internal.NewConfigMapGeneratorPlugin
|
||||
NewHashTransformerPlugin = internal.NewHashTransformerPlugin
|
||||
NewHelmChartInflationGeneratorPlugin = internal.NewHelmChartInflationGeneratorPlugin
|
||||
NewIAMPolicyGeneratorPlugin = internal.NewIAMPolicyGeneratorPlugin
|
||||
NewImageTagTransformerPlugin = internal.NewImageTagTransformerPlugin
|
||||
NewLabelTransformerPlugin = internal.NewLabelTransformerPlugin
|
||||
NewLegacyOrderTransformerPlugin = internal.NewLegacyOrderTransformerPlugin
|
||||
NewNamespaceTransformerPlugin = internal.NewNamespaceTransformerPlugin
|
||||
NewPatchJson6902TransformerPlugin = internal.NewPatchJson6902TransformerPlugin
|
||||
NewPatchStrategicMergeTransformerPlugin = internal.NewPatchStrategicMergeTransformerPlugin
|
||||
NewPatchTransformerPlugin = internal.NewPatchTransformerPlugin
|
||||
NewPrefixTransformerPlugin = internal.NewPrefixTransformerPlugin
|
||||
NewSuffixTransformerPlugin = internal.NewSuffixTransformerPlugin
|
||||
NewReplacementTransformerPlugin = internal.NewReplacementTransformerPlugin
|
||||
NewReplicaCountTransformerPlugin = internal.NewReplicaCountTransformerPlugin
|
||||
NewSecretGeneratorPlugin = internal.NewSecretGeneratorPlugin
|
||||
NewValueAddTransformerPlugin = internal.NewValueAddTransformerPlugin
|
||||
)
|
||||
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)
|
||||
}
|
||||
52
api/filters/annotations/annotations.go
Normal file
52
api/filters/annotations/annotations.go
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package annotations
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
type annoMap map[string]string
|
||||
|
||||
type Filter struct {
|
||||
// Annotations is the set of annotations to apply to the inputs
|
||||
Annotations annoMap `yaml:"annotations,omitempty"`
|
||||
|
||||
// FsSlice contains the FieldSpecs to locate the namespace field
|
||||
FsSlice types.FsSlice
|
||||
|
||||
trackableSetter filtersutil.TrackableSetter
|
||||
}
|
||||
|
||||
var _ kio.Filter = Filter{}
|
||||
var _ kio.TrackableFilter = &Filter{}
|
||||
|
||||
// WithMutationTracker registers a callback which will be invoked each time a field is mutated
|
||||
func (f *Filter) WithMutationTracker(callback func(key, value, tag string, node *yaml.RNode)) {
|
||||
f.trackableSetter.WithMutationTracker(callback)
|
||||
}
|
||||
|
||||
func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||
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: f.trackableSetter.SetEntry(
|
||||
k, f.Annotations[k], yaml.NodeTagString),
|
||||
CreateKind: yaml.MappingNode, // Annotations are MappingNodes.
|
||||
CreateTag: yaml.NodeTagMap,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return node, nil
|
||||
})).Filter(nodes)
|
||||
return nodes, err
|
||||
}
|
||||
299
api/filters/annotations/annotations_test.go
Normal file
299
api/filters/annotations/annotations_test.go
Normal file
@@ -0,0 +1,299 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package annotations
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"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"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
var annosFs = builtinconfig.MakeDefaultConfig().CommonAnnotations
|
||||
|
||||
func TestAnnotations_Filter(t *testing.T) {
|
||||
mutationTrackStub := filtertest_test.MutationTrackerStub{}
|
||||
testCases := map[string]struct {
|
||||
input string
|
||||
expectedOutput string
|
||||
filter Filter
|
||||
fsslice types.FsSlice
|
||||
setEntryCallback func(key, value, tag string, node *yaml.RNode)
|
||||
expectedSetEntryArgs []filtertest_test.SetValueArg
|
||||
}{
|
||||
"add": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
annotations:
|
||||
hero: batman
|
||||
fiend: riddler
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
annotations:
|
||||
hero: batman
|
||||
fiend: riddler
|
||||
auto: ford
|
||||
bean: cannellini
|
||||
clown: emmett kelley
|
||||
dragon: smaug
|
||||
`,
|
||||
filter: Filter{Annotations: annoMap{
|
||||
"clown": "emmett kelley",
|
||||
"auto": "ford",
|
||||
"dragon": "smaug",
|
||||
"bean": "cannellini",
|
||||
}},
|
||||
},
|
||||
"update": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
annotations:
|
||||
hero: batman
|
||||
fiend: riddler
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
annotations:
|
||||
hero: superman
|
||||
fiend: luthor
|
||||
bean: cannellini
|
||||
clown: emmett kelley
|
||||
`,
|
||||
filter: Filter{Annotations: annoMap{
|
||||
"clown": "emmett kelley",
|
||||
"hero": "superman",
|
||||
"fiend": "luthor",
|
||||
"bean": "cannellini",
|
||||
}},
|
||||
},
|
||||
"data-fieldspecs": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
annotations:
|
||||
sleater: kinney
|
||||
a:
|
||||
b:
|
||||
sleater: kinney
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
annotations:
|
||||
sleater: kinney
|
||||
a:
|
||||
b:
|
||||
sleater: kinney
|
||||
`,
|
||||
filter: Filter{Annotations: annoMap{
|
||||
"sleater": "kinney",
|
||||
}},
|
||||
fsslice: []types.FieldSpec{
|
||||
{
|
||||
Path: "a/b",
|
||||
CreateIfNotPresent: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
"number": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
annotations:
|
||||
hero: batman
|
||||
fiend: riddler
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
annotations:
|
||||
hero: batman
|
||||
fiend: riddler
|
||||
2: ford
|
||||
clown: "1"
|
||||
`,
|
||||
filter: Filter{Annotations: annoMap{
|
||||
"clown": "1",
|
||||
"2": "ford",
|
||||
}},
|
||||
},
|
||||
|
||||
// test quoting of values which are not considered strings in yaml 1.1
|
||||
"yaml_1_1_compatibility": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
annotations:
|
||||
hero: batman
|
||||
fiend: riddler
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
annotations:
|
||||
hero: batman
|
||||
fiend: riddler
|
||||
a: "y"
|
||||
b: y1
|
||||
c: "yes"
|
||||
d: yes1
|
||||
e: "true"
|
||||
f: true1
|
||||
`,
|
||||
filter: Filter{Annotations: annoMap{
|
||||
"a": "y",
|
||||
"b": "y1",
|
||||
"c": "yes",
|
||||
"d": "yes1",
|
||||
"e": "true",
|
||||
"f": "true1",
|
||||
}},
|
||||
},
|
||||
|
||||
// test quoting of values which are not considered strings in yaml 1.1
|
||||
"null_annotations": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
annotations: null
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
annotations:
|
||||
a: a1
|
||||
b: b1
|
||||
`,
|
||||
filter: Filter{Annotations: annoMap{
|
||||
"a": "a1",
|
||||
"b": "b1",
|
||||
}},
|
||||
},
|
||||
|
||||
// test usage of SetEntryCallback
|
||||
"set_entry_callback": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
annotations:
|
||||
a: a1
|
||||
b: b1
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
a: a1
|
||||
b: b1
|
||||
`,
|
||||
filter: Filter{
|
||||
Annotations: annoMap{
|
||||
"a": "a1",
|
||||
"b": "b1",
|
||||
},
|
||||
},
|
||||
setEntryCallback: mutationTrackStub.MutationTracker,
|
||||
fsslice: []types.FieldSpec{
|
||||
{
|
||||
Path: "spec/template/metadata/annotations",
|
||||
CreateIfNotPresent: true,
|
||||
},
|
||||
},
|
||||
expectedSetEntryArgs: []filtertest_test.SetValueArg{
|
||||
{
|
||||
Key: "a",
|
||||
Value: "a1",
|
||||
Tag: "!!str",
|
||||
NodePath: []string{"metadata", "annotations"},
|
||||
},
|
||||
{
|
||||
Key: "a",
|
||||
Value: "a1",
|
||||
Tag: "!!str",
|
||||
NodePath: []string{"spec", "template", "metadata", "annotations"},
|
||||
},
|
||||
{
|
||||
Key: "b",
|
||||
Value: "b1",
|
||||
Tag: "!!str",
|
||||
NodePath: []string{"metadata", "annotations"},
|
||||
},
|
||||
{
|
||||
Key: "b",
|
||||
Value: "b1",
|
||||
Tag: "!!str",
|
||||
NodePath: []string{"spec", "template", "metadata", "annotations"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for tn, tc := range testCases {
|
||||
mutationTrackStub.Reset()
|
||||
t.Run(tn, func(t *testing.T) {
|
||||
filter := tc.filter
|
||||
filter.WithMutationTracker(tc.setEntryCallback)
|
||||
filter.FsSlice = append(annosFs, tc.fsslice...)
|
||||
if !assert.Equal(t,
|
||||
strings.TrimSpace(tc.expectedOutput),
|
||||
strings.TrimSpace(filtertest_test.RunFilter(t, tc.input, filter))) {
|
||||
t.FailNow()
|
||||
}
|
||||
if !assert.Equal(t, tc.expectedSetEntryArgs, mutationTrackStub.SetValueArgs()) {
|
||||
t.FailNow()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
6
api/filters/annotations/doc.go
Normal file
6
api/filters/annotations/doc.go
Normal file
@@ -0,0 +1,6 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package annotations contains a kio.Filter implementation of the kustomize
|
||||
// annotations transformer.
|
||||
package annotations
|
||||
61
api/filters/annotations/example_test.go
Normal file
61
api/filters/annotations/example_test.go
Normal file
@@ -0,0 +1,61 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package annotations
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
)
|
||||
|
||||
func ExampleFilter() {
|
||||
fss := builtinconfig.MakeDefaultConfig().CommonAnnotations
|
||||
err := kio.Pipeline{
|
||||
Inputs: []kio.Reader{&kio.ByteReader{Reader: bytes.NewBufferString(`
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
`)}},
|
||||
Filters: []kio.Filter{Filter{
|
||||
Annotations: map[string]string{
|
||||
"foo": "bar",
|
||||
"booleanValue": "true",
|
||||
"numberValue": "42",
|
||||
},
|
||||
FsSlice: fss,
|
||||
}},
|
||||
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
|
||||
// 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
|
||||
}
|
||||
182
api/filters/fieldspec/fieldspec.go
Normal file
182
api/filters/fieldspec/fieldspec.go
Normal file
@@ -0,0 +1,182 @@
|
||||
// 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 {
|
||||
return nil, errors.WrapPrefixf(err,
|
||||
"considering field '%s' of object %s", fltr.FieldSpec.Path, resid.FromRNode(obj))
|
||||
}
|
||||
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 {
|
||||
// set an accurate FieldPath for nested elements
|
||||
node.AppendToFieldPath(obj.FieldPath()...)
|
||||
// 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
|
||||
}
|
||||
642
api/filters/fieldspec/fieldspec_test.go
Normal file
642
api/filters/fieldspec/fieldspec_test.go
Normal file
@@ -0,0 +1,642 @@
|
||||
// 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 Bar.v1.foo/[noName].[noNs]: 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 Bar.[noVer].[noGrp]/[noName].[noNs]: 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()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilter_FieldPaths(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
input string
|
||||
fieldSpec string
|
||||
expected []string
|
||||
}{
|
||||
"fieldpath containing SequenceNode": {
|
||||
input: `
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: app
|
||||
spec:
|
||||
containers:
|
||||
- name: store
|
||||
image: redis:6.2.6
|
||||
- name: server
|
||||
image: nginx:latest
|
||||
`,
|
||||
fieldSpec: `
|
||||
path: spec/containers[]/image
|
||||
kind: Pod
|
||||
`,
|
||||
expected: []string{
|
||||
"spec.containers.image",
|
||||
"spec.containers.image",
|
||||
},
|
||||
},
|
||||
"fieldpath with MappingNode": {
|
||||
input: `
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: app
|
||||
spec:
|
||||
containers:
|
||||
- name: store
|
||||
image: redis:6.2.6
|
||||
- name: server
|
||||
image: nginx:latest
|
||||
`,
|
||||
fieldSpec: `
|
||||
path: metadata/name
|
||||
kind: Pod
|
||||
`,
|
||||
expected: []string{
|
||||
"metadata.name",
|
||||
},
|
||||
},
|
||||
}
|
||||
for name, tc := range testCases {
|
||||
var fieldPaths []string
|
||||
trackableSetter := filtersutil.TrackableSetter{}
|
||||
trackableSetter.WithMutationTracker(func(key, value, tag string, node *yaml.RNode) {
|
||||
fieldPaths = append(fieldPaths, strings.Join(node.FieldPath(), "."))
|
||||
})
|
||||
filter := fieldspec.Filter{
|
||||
SetValue: trackableSetter.SetScalar("foo"),
|
||||
}
|
||||
|
||||
t.Run(name, func(t *testing.T) {
|
||||
err := yaml.Unmarshal([]byte(tc.fieldSpec), &filter.FieldSpec)
|
||||
assert.NoError(t, err)
|
||||
rw := &kio.ByteReadWriter{
|
||||
Reader: bytes.NewBufferString(tc.input),
|
||||
Writer: &bytes.Buffer{},
|
||||
OmitReaderAnnotations: true,
|
||||
}
|
||||
|
||||
// run the filter
|
||||
err = kio.Pipeline{
|
||||
Inputs: []kio.Reader{rw},
|
||||
Filters: []kio.Filter{kio.FilterAll(filter)},
|
||||
Outputs: []kio.Writer{rw},
|
||||
}.Execute()
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tc.expected, fieldPaths)
|
||||
})
|
||||
}
|
||||
}
|
||||
69
api/filters/filtersutil/setters.go
Normal file
69
api/filters/filtersutil/setters.go
Normal file
@@ -0,0 +1,69 @@
|
||||
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),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type TrackableSetter struct {
|
||||
// SetValueCallback will be invoked each time a field is set
|
||||
setValueCallback func(key, value, tag string, node *yaml.RNode)
|
||||
}
|
||||
|
||||
// WithMutationTracker registers a callback which will be invoked each time a field is mutated
|
||||
func (s *TrackableSetter) WithMutationTracker(callback func(key, value, tag string, node *yaml.RNode)) {
|
||||
s.setValueCallback = callback
|
||||
}
|
||||
|
||||
// SetScalar returns a SetFn to set a scalar value
|
||||
// if a mutation tracker has been registered, the tracker will be invoked each
|
||||
// time a scalar is set
|
||||
func (s TrackableSetter) SetScalar(value string) SetFn {
|
||||
origSetScalar := SetScalar(value)
|
||||
return func(node *yaml.RNode) error {
|
||||
if s.setValueCallback != nil {
|
||||
s.setValueCallback("", value, "", node)
|
||||
}
|
||||
return origSetScalar(node)
|
||||
}
|
||||
}
|
||||
|
||||
// SetEntry returns a SetFn to set an entry in a map
|
||||
// if a mutation tracker has been registered, the tracker will be invoked each
|
||||
// time an entry is set
|
||||
func (s TrackableSetter) SetEntry(key, value, tag string) SetFn {
|
||||
origSetEntry := SetEntry(key, value, tag)
|
||||
return func(node *yaml.RNode) error {
|
||||
if s.setValueCallback != nil {
|
||||
s.setValueCallback(key, value, tag, node)
|
||||
}
|
||||
return origSetEntry(node)
|
||||
}
|
||||
}
|
||||
6
api/filters/fsslice/doc.go
Normal file
6
api/filters/fsslice/doc.go
Normal file
@@ -0,0 +1,6 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package fsslice contains a yaml.Filter to modify a resource if
|
||||
// it matches one or more FieldSpec entries.
|
||||
package fsslice
|
||||
63
api/filters/fsslice/example_test.go
Normal file
63
api/filters/fsslice/example_test.go
Normal file
@@ -0,0 +1,63 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package fsslice_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"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"
|
||||
"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 := fsslice.Filter{
|
||||
CreateKind: yaml.ScalarNode,
|
||||
SetValue: filtersutil.SetScalar("green"),
|
||||
FsSlice: []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
|
||||
}
|
||||
47
api/filters/fsslice/fsslice.go
Normal file
47
api/filters/fsslice/fsslice.go
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
var _ yaml.Filter = Filter{}
|
||||
|
||||
// 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 filtersutil.SetFn
|
||||
|
||||
// CreateKind is used to create fields that do not exist
|
||||
CreateKind yaml.Kind
|
||||
|
||||
// CreateTag is used to set the tag if encountering a null field
|
||||
CreateTag string
|
||||
}
|
||||
|
||||
func (fltr Filter) Filter(obj *yaml.RNode) (*yaml.RNode, error) {
|
||||
for i := range fltr.FsSlice {
|
||||
// apply this FieldSpec
|
||||
// create a new filter for each iteration because they
|
||||
// store internal state about the field paths
|
||||
_, err := (&fieldspec.Filter{
|
||||
FieldSpec: fltr.FsSlice[i],
|
||||
SetValue: fltr.SetValue,
|
||||
CreateKind: fltr.CreateKind,
|
||||
CreateTag: fltr.CreateTag,
|
||||
}).Filter(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return obj, nil
|
||||
}
|
||||
121
api/filters/fsslice/fsslice_test.go
Normal file
121
api/filters/fsslice/fsslice_test.go
Normal file
@@ -0,0 +1,121 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package fsslice_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"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 {
|
||||
input string
|
||||
expected string
|
||||
filter Filter
|
||||
fsSlice string
|
||||
error string
|
||||
}
|
||||
|
||||
var tests = map[string]TestCase{
|
||||
"empty": {
|
||||
fsSlice: `
|
||||
`,
|
||||
input: `
|
||||
apiVersion: foo/v1
|
||||
kind: Bar
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: foo/v1
|
||||
kind: Bar
|
||||
`,
|
||||
filter: Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
},
|
||||
},
|
||||
"two": {
|
||||
fsSlice: `
|
||||
- path: a/b
|
||||
group: foo
|
||||
version: v1
|
||||
create: true
|
||||
kind: Bar
|
||||
- path: q/r[]/s/t
|
||||
group: foo
|
||||
version: v1
|
||||
create: true
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
apiVersion: foo/v1
|
||||
kind: Bar
|
||||
q:
|
||||
r:
|
||||
- s: {}
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: foo/v1
|
||||
kind: Bar
|
||||
q:
|
||||
r:
|
||||
- s: {t: e}
|
||||
a:
|
||||
b: e
|
||||
`,
|
||||
filter: Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
out := &bytes.Buffer{}
|
||||
rw := &kio.ByteReadWriter{
|
||||
Reader: bytes.NewBufferString(test.input),
|
||||
Writer: out,
|
||||
OmitReaderAnnotations: true,
|
||||
}
|
||||
|
||||
// run the filter
|
||||
err = kio.Pipeline{
|
||||
Inputs: []kio.Reader{rw},
|
||||
Filters: []kio.Filter{kio.FilterAll(test.filter)},
|
||||
Outputs: []kio.Writer{rw},
|
||||
}.Execute()
|
||||
if test.error != "" {
|
||||
if !assert.EqualError(t, err, test.error) {
|
||||
t.FailNow()
|
||||
}
|
||||
// stop rest of test
|
||||
return
|
||||
}
|
||||
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
// check results
|
||||
if !assert.Equal(t,
|
||||
strings.TrimSpace(test.expected),
|
||||
strings.TrimSpace(out.String())) {
|
||||
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()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
12
api/filters/imagetag/doc.go
Normal file
12
api/filters/imagetag/doc.go
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package imagetag contains two kio.Filter implementations to cover the
|
||||
// functionality of the kustomize imagetag transformer.
|
||||
//
|
||||
// Filter updates fields based on a FieldSpec and an ImageTag.
|
||||
//
|
||||
// LegacyFilter doesn't use a FieldSpec, and instead only updates image
|
||||
// references if the field is name image and it is underneath a field called
|
||||
// either containers or initContainers.
|
||||
package imagetag
|
||||
126
api/filters/imagetag/example_test.go
Normal file
126
api/filters/imagetag/example_test.go
Normal file
@@ -0,0 +1,126 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package imagetag
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
)
|
||||
|
||||
func ExampleFilter() {
|
||||
err := kio.Pipeline{
|
||||
Inputs: []kio.Reader{&kio.ByteReader{Reader: bytes.NewBufferString(`
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
spec:
|
||||
containers:
|
||||
- name: FooBar
|
||||
image: nginx
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
spec:
|
||||
containers:
|
||||
- name: BarFoo
|
||||
image: nginx:1.2.1
|
||||
`)}},
|
||||
Filters: []kio.Filter{Filter{
|
||||
ImageTag: types.Image{
|
||||
Name: "nginx",
|
||||
NewName: "apache",
|
||||
Digest: "12345",
|
||||
},
|
||||
FsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "spec/containers[]/image",
|
||||
},
|
||||
},
|
||||
}},
|
||||
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
|
||||
// spec:
|
||||
// containers:
|
||||
// - name: FooBar
|
||||
// image: apache@12345
|
||||
// ---
|
||||
// apiVersion: example.com/v1
|
||||
// kind: Bar
|
||||
// metadata:
|
||||
// name: instance
|
||||
// spec:
|
||||
// containers:
|
||||
// - name: BarFoo
|
||||
// image: apache@12345
|
||||
}
|
||||
|
||||
func ExampleLegacyFilter() {
|
||||
err := kio.Pipeline{
|
||||
Inputs: []kio.Reader{&kio.ByteReader{Reader: bytes.NewBufferString(`
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
spec:
|
||||
containers:
|
||||
- name: FooBar
|
||||
image: nginx
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
spec:
|
||||
containers:
|
||||
- name: BarFoo
|
||||
image: nginx:1.2.1
|
||||
`)}},
|
||||
Filters: []kio.Filter{LegacyFilter{
|
||||
ImageTag: types.Image{
|
||||
Name: "nginx",
|
||||
NewName: "apache",
|
||||
Digest: "12345",
|
||||
},
|
||||
}},
|
||||
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
|
||||
// spec:
|
||||
// containers:
|
||||
// - name: FooBar
|
||||
// image: apache@12345
|
||||
// ---
|
||||
// apiVersion: example.com/v1
|
||||
// kind: Bar
|
||||
// metadata:
|
||||
// name: instance
|
||||
// spec:
|
||||
// containers:
|
||||
// - name: BarFoo
|
||||
// image: apache@12345
|
||||
}
|
||||
72
api/filters/imagetag/imagetag.go
Normal file
72
api/filters/imagetag/imagetag.go
Normal file
@@ -0,0 +1,72 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
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 an image field,
|
||||
// e.g. Path: "spec/myContainers[]/image"
|
||||
FsSlice types.FsSlice `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
|
||||
|
||||
trackableSetter filtersutil.TrackableSetter
|
||||
}
|
||||
|
||||
var _ kio.Filter = Filter{}
|
||||
var _ kio.TrackableFilter = &Filter{}
|
||||
|
||||
// WithMutationTracker registers a callback which will be invoked each time a field is mutated
|
||||
func (f *Filter) WithMutationTracker(callback func(key, value, tag string, node *yaml.RNode)) {
|
||||
f.trackableSetter.WithMutationTracker(callback)
|
||||
}
|
||||
|
||||
func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||
_, err := kio.FilterAll(yaml.FilterFunc(f.filter)).Filter(nodes)
|
||||
return nodes, err
|
||||
}
|
||||
|
||||
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: imageTagUpdater{
|
||||
ImageTag: f.ImageTag,
|
||||
trackableSetter: f.trackableSetter,
|
||||
}.SetImageValue,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
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`
|
||||
}
|
||||
769
api/filters/imagetag/imagetag_test.go
Normal file
769
api/filters/imagetag/imagetag_test.go
Normal file
@@ -0,0 +1,769 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package imagetag
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
filtertest "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
func TestImageTagUpdater_Filter(t *testing.T) {
|
||||
mutationTrackerStub := filtertest.MutationTrackerStub{}
|
||||
testCases := map[string]struct {
|
||||
input string
|
||||
expectedOutput string
|
||||
filter Filter
|
||||
fsSlice types.FsSlice
|
||||
setValueCallback func(key, value, tag string, node *yaml.RNode)
|
||||
expectedSetValueArgs []filtertest.SetValueArg
|
||||
}{
|
||||
"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
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
spec:
|
||||
image: nginx:1.2.1
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
spec:
|
||||
image: apache@12345
|
||||
`,
|
||||
filter: Filter{
|
||||
ImageTag: types.Image{
|
||||
Name: "nginx",
|
||||
NewName: "apache",
|
||||
Digest: "12345",
|
||||
},
|
||||
},
|
||||
fsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "spec/image",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
"multiple matches in sequence": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx:1.2.1
|
||||
- image: not_nginx@54321
|
||||
- image: nginx:1.2.1
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
spec:
|
||||
containers:
|
||||
- image: apache:3.2.1
|
||||
- image: not_nginx@54321
|
||||
- image: apache:3.2.1
|
||||
`,
|
||||
filter: Filter{
|
||||
ImageTag: types.Image{
|
||||
Name: "nginx",
|
||||
NewName: "apache",
|
||||
NewTag: "3.2.1",
|
||||
},
|
||||
},
|
||||
fsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "spec/containers/image",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
"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",
|
||||
},
|
||||
},
|
||||
},
|
||||
"mutation tracker": {
|
||||
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",
|
||||
},
|
||||
},
|
||||
setValueCallback: mutationTrackerStub.MutationTracker,
|
||||
expectedSetValueArgs: []filtertest.SetValueArg{
|
||||
{
|
||||
Value: "busybox:v3",
|
||||
NodePath: []string{"spec", "template", "spec", "containers", "image"},
|
||||
},
|
||||
{
|
||||
Value: "busybox:v3",
|
||||
NodePath: []string{"spec", "template", "spec", "containers", "image"},
|
||||
},
|
||||
{
|
||||
Value: "busybox:v3",
|
||||
NodePath: []string{"spec", "template", "spec", "initContainers", "image"},
|
||||
},
|
||||
{
|
||||
Value: "busybox:v3",
|
||||
NodePath: []string{"spec", "template", "spec", "initContainers", "image"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for tn, tc := range testCases {
|
||||
mutationTrackerStub.Reset()
|
||||
t.Run(tn, func(t *testing.T) {
|
||||
filter := tc.filter
|
||||
filter.WithMutationTracker(tc.setValueCallback)
|
||||
filter.FsSlice = tc.fsSlice
|
||||
if !assert.Equal(t,
|
||||
strings.TrimSpace(tc.expectedOutput),
|
||||
strings.TrimSpace(filtertest.RunFilter(t, tc.input, filter))) {
|
||||
t.FailNow()
|
||||
}
|
||||
assert.Equal(t, tc.expectedSetValueArgs, mutationTrackerStub.SetValueArgs())
|
||||
})
|
||||
}
|
||||
}
|
||||
105
api/filters/imagetag/legacy.go
Normal file
105
api/filters/imagetag/legacy.go
Normal file
@@ -0,0 +1,105 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
// LegacyFilter is an implementation of the kio.Filter interface
|
||||
// that scans through the provided kyaml data structure and updates
|
||||
// any values of any image fields that is inside a sequence under
|
||||
// a field called either containers or initContainers. The field is only
|
||||
// update if it has a value that matches and image reference and the name
|
||||
// of the image is a match with the provided ImageTag.
|
||||
type LegacyFilter struct {
|
||||
ImageTag types.Image `json:"imageTag,omitempty" yaml:"imageTag,omitempty"`
|
||||
}
|
||||
|
||||
var _ kio.Filter = LegacyFilter{}
|
||||
|
||||
func (lf LegacyFilter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||
return kio.FilterAll(yaml.FilterFunc(lf.filter)).Filter(nodes)
|
||||
}
|
||||
|
||||
func (lf LegacyFilter) filter(node *yaml.RNode) (*yaml.RNode, error) {
|
||||
meta, err := node.GetMeta()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We do not make any changes if the type of the resource
|
||||
// is CustomResourceDefinition.
|
||||
if meta.Kind == `CustomResourceDefinition` {
|
||||
return node, nil
|
||||
}
|
||||
|
||||
fff := findFieldsFilter{
|
||||
fields: []string{"containers", "initContainers"},
|
||||
fieldCallback: checkImageTagsFn(lf.ImageTag),
|
||||
}
|
||||
if err := node.PipeE(fff); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
type fieldCallback func(node *yaml.RNode) error
|
||||
|
||||
// findFieldsFilter is an implementation of the kio.Filter
|
||||
// interface. It will walk the data structure and look for fields
|
||||
// that matches the provided list of field names. For each match,
|
||||
// the value of the field will be passed in as a parameter to the
|
||||
// provided fieldCallback.
|
||||
// TODO: move this to kyaml/filterutils
|
||||
type findFieldsFilter struct {
|
||||
fields []string
|
||||
|
||||
fieldCallback fieldCallback
|
||||
}
|
||||
|
||||
func (f findFieldsFilter) Filter(obj *yaml.RNode) (*yaml.RNode, error) {
|
||||
return obj, f.walk(obj)
|
||||
}
|
||||
|
||||
func (f findFieldsFilter) walk(node *yaml.RNode) error {
|
||||
switch node.YNode().Kind {
|
||||
case yaml.MappingNode:
|
||||
return node.VisitFields(func(n *yaml.MapNode) error {
|
||||
err := f.walk(n.Value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key := n.Key.YNode().Value
|
||||
if utils.StringSliceContains(f.fields, key) {
|
||||
return f.fieldCallback(n.Value)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
case yaml.SequenceNode:
|
||||
return node.VisitElements(func(n *yaml.RNode) error {
|
||||
return f.walk(n)
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkImageTagsFn(imageTag types.Image) fieldCallback {
|
||||
return func(node *yaml.RNode) error {
|
||||
if node.YNode().Kind != yaml.SequenceNode {
|
||||
return nil
|
||||
}
|
||||
|
||||
return node.VisitElements(func(n *yaml.RNode) error {
|
||||
// Look up any fields on the provided node that is named
|
||||
// image.
|
||||
return n.PipeE(yaml.Get("image"), imageTagUpdater{
|
||||
ImageTag: imageTag,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
136
api/filters/imagetag/legacy_test.go
Normal file
136
api/filters/imagetag/legacy_test.go
Normal file
@@ -0,0 +1,136 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package imagetag
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
filtertest "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
)
|
||||
|
||||
func TestLegacyImageTag_Filter(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
input string
|
||||
expectedOutput string
|
||||
filter LegacyFilter
|
||||
}{
|
||||
"updates multiple images inside 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: LegacyFilter{
|
||||
ImageTag: types.Image{
|
||||
Name: "nginx",
|
||||
NewName: "apache",
|
||||
Digest: "12345",
|
||||
},
|
||||
},
|
||||
},
|
||||
"updates inside 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: LegacyFilter{
|
||||
ImageTag: types.Image{
|
||||
Name: "nginx",
|
||||
NewName: "apache",
|
||||
NewTag: "3.2.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
"updates on 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: LegacyFilter{
|
||||
ImageTag: types.Image{
|
||||
Name: "nginx",
|
||||
NewName: "apache",
|
||||
NewTag: "3.2.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for tn, tc := range testCases {
|
||||
t.Run(tn, func(t *testing.T) {
|
||||
filter := tc.filter
|
||||
if !assert.Equal(t,
|
||||
strings.TrimSpace(tc.expectedOutput),
|
||||
strings.TrimSpace(filtertest.RunFilter(t, tc.input, filter))) {
|
||||
t.FailNow()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
52
api/filters/imagetag/updater.go
Normal file
52
api/filters/imagetag/updater.go
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package imagetag
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/api/filters/filtersutil"
|
||||
"sigs.k8s.io/kustomize/api/image"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
// imageTagUpdater is an implementation of the kio.Filter interface
|
||||
// that will update the value of the yaml node based on the provided
|
||||
// ImageTag if the current value matches the format of an image reference.
|
||||
type imageTagUpdater struct {
|
||||
Kind string `yaml:"kind,omitempty"`
|
||||
ImageTag types.Image `yaml:"imageTag,omitempty"`
|
||||
trackableSetter filtersutil.TrackableSetter
|
||||
}
|
||||
|
||||
func (u imageTagUpdater) SetImageValue(rn *yaml.RNode) error {
|
||||
if err := yaml.ErrorIfInvalid(rn, yaml.ScalarNode); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
value := rn.YNode().Value
|
||||
|
||||
if !image.IsImageMatched(value, u.ImageTag.Name) {
|
||||
return nil
|
||||
}
|
||||
|
||||
name, tag := image.Split(value)
|
||||
if u.ImageTag.NewName != "" {
|
||||
name = u.ImageTag.NewName
|
||||
}
|
||||
if u.ImageTag.NewTag != "" {
|
||||
tag = ":" + u.ImageTag.NewTag
|
||||
}
|
||||
if u.ImageTag.Digest != "" {
|
||||
tag = "@" + u.ImageTag.Digest
|
||||
}
|
||||
|
||||
return u.trackableSetter.SetScalar(name + tag)(rn)
|
||||
}
|
||||
|
||||
func (u imageTagUpdater) Filter(rn *yaml.RNode) (*yaml.RNode, error) {
|
||||
if err := u.SetImageValue(rn); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rn, nil
|
||||
}
|
||||
6
api/filters/labels/doc.go
Normal file
6
api/filters/labels/doc.go
Normal file
@@ -0,0 +1,6 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package labels contains a kio.Filter implementation of the kustomize
|
||||
// labels transformer.
|
||||
package labels
|
||||
55
api/filters/labels/example_test.go
Normal file
55
api/filters/labels/example_test.go
Normal file
@@ -0,0 +1,55 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package labels
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
)
|
||||
|
||||
func ExampleFilter() {
|
||||
fss := builtinconfig.MakeDefaultConfig().CommonLabels
|
||||
err := kio.Pipeline{
|
||||
Inputs: []kio.Reader{&kio.ByteReader{Reader: bytes.NewBufferString(`
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
`)}},
|
||||
Filters: []kio.Filter{Filter{
|
||||
Labels: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
FsSlice: fss,
|
||||
}},
|
||||
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
|
||||
// labels:
|
||||
// foo: bar
|
||||
// ---
|
||||
// apiVersion: example.com/v1
|
||||
// kind: Bar
|
||||
// metadata:
|
||||
// name: instance
|
||||
// labels:
|
||||
// foo: bar
|
||||
}
|
||||
53
api/filters/labels/labels.go
Normal file
53
api/filters/labels/labels.go
Normal file
@@ -0,0 +1,53 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package labels
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
type labelMap map[string]string
|
||||
|
||||
// Filter sets labels.
|
||||
type Filter struct {
|
||||
// Labels is the set of labels to apply to the inputs
|
||||
Labels labelMap `yaml:"labels,omitempty"`
|
||||
|
||||
// FsSlice identifies the label fields.
|
||||
FsSlice types.FsSlice
|
||||
|
||||
trackableSetter filtersutil.TrackableSetter
|
||||
}
|
||||
|
||||
var _ kio.Filter = Filter{}
|
||||
var _ kio.TrackableFilter = &Filter{}
|
||||
|
||||
// WithMutationTracker registers a callback which will be invoked each time a field is mutated
|
||||
func (f *Filter) WithMutationTracker(callback func(key, value, tag string, node *yaml.RNode)) {
|
||||
f.trackableSetter.WithMutationTracker(callback)
|
||||
}
|
||||
|
||||
func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||
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: f.trackableSetter.SetEntry(
|
||||
k, f.Labels[k], yaml.NodeTagString),
|
||||
CreateKind: yaml.MappingNode, // Labels are MappingNodes.
|
||||
CreateTag: yaml.NodeTagMap,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return node, nil
|
||||
})).Filter(nodes)
|
||||
return nodes, err
|
||||
}
|
||||
476
api/filters/labels/labels_test.go
Normal file
476
api/filters/labels/labels_test.go
Normal file
@@ -0,0 +1,476 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package labels
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
filtertest_test "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/resid"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
func TestLabels_Filter(t *testing.T) {
|
||||
mutationTrackerStub := filtertest_test.MutationTrackerStub{}
|
||||
testCases := map[string]struct {
|
||||
input string
|
||||
expectedOutput string
|
||||
filter Filter
|
||||
setEntryCallback func(key, value, tag string, node *yaml.RNode)
|
||||
expectedSetEntryArgs []filtertest_test.SetValueArg
|
||||
}{
|
||||
"add": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
labels:
|
||||
hero: batman
|
||||
fiend: riddler
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
labels:
|
||||
hero: batman
|
||||
fiend: riddler
|
||||
auto: ford
|
||||
bean: cannellini
|
||||
clown: emmett kelley
|
||||
dragon: smaug
|
||||
`,
|
||||
filter: Filter{
|
||||
Labels: labelMap{
|
||||
"clown": "emmett kelley",
|
||||
"auto": "ford",
|
||||
"dragon": "smaug",
|
||||
"bean": "cannellini",
|
||||
},
|
||||
FsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "metadata/labels",
|
||||
CreateIfNotPresent: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"update": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
labels:
|
||||
hero: batman
|
||||
fiend: riddler
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
labels:
|
||||
hero: superman
|
||||
fiend: luthor
|
||||
bean: cannellini
|
||||
clown: emmett kelley
|
||||
`,
|
||||
filter: Filter{
|
||||
Labels: labelMap{
|
||||
"clown": "emmett kelley",
|
||||
"hero": "superman",
|
||||
"fiend": "luthor",
|
||||
"bean": "cannellini",
|
||||
}, FsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "metadata/labels",
|
||||
CreateIfNotPresent: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
"data-fieldspecs": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
labels:
|
||||
sleater: kinney
|
||||
a:
|
||||
b:
|
||||
sleater: kinney
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
labels:
|
||||
sleater: kinney
|
||||
a:
|
||||
b:
|
||||
sleater: kinney
|
||||
`,
|
||||
filter: Filter{
|
||||
Labels: labelMap{
|
||||
"sleater": "kinney",
|
||||
},
|
||||
FsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "metadata/labels",
|
||||
CreateIfNotPresent: true,
|
||||
},
|
||||
{
|
||||
Path: "a/b",
|
||||
CreateIfNotPresent: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
"fieldSpecWithKind": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
---
|
||||
apiVersion: example.com/v2
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
labels:
|
||||
cheese: cheddar
|
||||
---
|
||||
apiVersion: example.com/v2
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
labels:
|
||||
cheese: cheddar
|
||||
a:
|
||||
b:
|
||||
cheese: cheddar
|
||||
`,
|
||||
filter: Filter{
|
||||
Labels: labelMap{
|
||||
"cheese": "cheddar",
|
||||
},
|
||||
FsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "metadata/labels",
|
||||
CreateIfNotPresent: true,
|
||||
},
|
||||
{
|
||||
Gvk: resid.Gvk{
|
||||
Kind: "Bar",
|
||||
},
|
||||
Path: "a/b",
|
||||
CreateIfNotPresent: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
"fieldSpecWithVersion": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
---
|
||||
apiVersion: example.com/v2
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
labels:
|
||||
cheese: cheddar
|
||||
a:
|
||||
b:
|
||||
cheese: cheddar
|
||||
---
|
||||
apiVersion: example.com/v2
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
labels:
|
||||
cheese: cheddar
|
||||
`,
|
||||
filter: Filter{
|
||||
Labels: labelMap{
|
||||
"cheese": "cheddar",
|
||||
},
|
||||
FsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "metadata/labels",
|
||||
CreateIfNotPresent: true,
|
||||
},
|
||||
{
|
||||
Gvk: resid.Gvk{
|
||||
Version: "v1",
|
||||
},
|
||||
Path: "a/b",
|
||||
CreateIfNotPresent: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"fieldSpecWithVersionInConfigButNoGroupInData": {
|
||||
input: `
|
||||
apiVersion: v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
---
|
||||
apiVersion: v2
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
labels:
|
||||
cheese: cheddar
|
||||
a:
|
||||
b:
|
||||
cheese: cheddar
|
||||
---
|
||||
apiVersion: v2
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
labels:
|
||||
cheese: cheddar
|
||||
`,
|
||||
filter: Filter{
|
||||
Labels: labelMap{
|
||||
"cheese": "cheddar",
|
||||
},
|
||||
FsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "metadata/labels",
|
||||
CreateIfNotPresent: true,
|
||||
},
|
||||
{
|
||||
Gvk: resid.Gvk{
|
||||
Version: "v1",
|
||||
},
|
||||
Path: "a/b",
|
||||
CreateIfNotPresent: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
"number": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
labels:
|
||||
hero: batman
|
||||
fiend: riddler
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
labels:
|
||||
hero: batman
|
||||
fiend: riddler
|
||||
1: emmett kelley
|
||||
auto: "2"
|
||||
`,
|
||||
filter: Filter{
|
||||
Labels: labelMap{
|
||||
"1": "emmett kelley",
|
||||
"auto": "2",
|
||||
},
|
||||
FsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "metadata/labels",
|
||||
CreateIfNotPresent: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// test quoting of values which are not considered strings in yaml 1.1
|
||||
"yaml_1_1_compatibility": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
labels:
|
||||
hero: batman
|
||||
fiend: riddler
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
labels:
|
||||
hero: batman
|
||||
fiend: riddler
|
||||
a: "y"
|
||||
b: y1
|
||||
c: "yes"
|
||||
d: yes1
|
||||
e: "true"
|
||||
f: true1
|
||||
`,
|
||||
filter: Filter{
|
||||
Labels: labelMap{
|
||||
"a": "y",
|
||||
"b": "y1",
|
||||
"c": "yes",
|
||||
"d": "yes1",
|
||||
"e": "true",
|
||||
"f": "true1",
|
||||
},
|
||||
FsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "metadata/labels",
|
||||
CreateIfNotPresent: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
"null_labels": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
labels: null
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
labels:
|
||||
a: a1
|
||||
`,
|
||||
filter: Filter{
|
||||
Labels: labelMap{
|
||||
"a": "a1",
|
||||
},
|
||||
FsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "metadata/labels",
|
||||
CreateIfNotPresent: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// test usage of SetEntryCallback
|
||||
"set_entry_callback": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
labels:
|
||||
witcher: geralt
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
labels:
|
||||
witcher: geralt
|
||||
mage: yennefer
|
||||
a:
|
||||
b:
|
||||
mage: yennefer
|
||||
`,
|
||||
filter: Filter{
|
||||
Labels: labelMap{
|
||||
"mage": "yennefer",
|
||||
},
|
||||
FsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "metadata/labels",
|
||||
CreateIfNotPresent: true,
|
||||
},
|
||||
{
|
||||
Path: "a/b",
|
||||
CreateIfNotPresent: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
setEntryCallback: mutationTrackerStub.MutationTracker,
|
||||
expectedSetEntryArgs: []filtertest_test.SetValueArg{
|
||||
{
|
||||
Key: "mage",
|
||||
Value: "yennefer",
|
||||
Tag: "!!str",
|
||||
NodePath: []string{"metadata", "labels"},
|
||||
},
|
||||
{
|
||||
Key: "mage",
|
||||
Value: "yennefer",
|
||||
Tag: "!!str",
|
||||
NodePath: []string{"a", "b"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for tn, tc := range testCases {
|
||||
mutationTrackerStub.Reset()
|
||||
t.Run(tn, func(t *testing.T) {
|
||||
tc.filter.WithMutationTracker(tc.setEntryCallback)
|
||||
if !assert.Equal(t,
|
||||
strings.TrimSpace(tc.expectedOutput),
|
||||
strings.TrimSpace(filtertest_test.RunFilter(t, tc.input, tc.filter))) {
|
||||
t.FailNow()
|
||||
}
|
||||
if !assert.Equal(t, tc.expectedSetEntryArgs, mutationTrackerStub.SetValueArgs()) {
|
||||
t.FailNow()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
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")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
9
api/filters/namespace/doc.go
Normal file
9
api/filters/namespace/doc.go
Normal file
@@ -0,0 +1,9 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package namespace contains a kio.Filter implementation of the kustomize
|
||||
// namespace transformer.
|
||||
//
|
||||
// Special cases for known Kubernetes resources have been hardcoded in addition
|
||||
// to those defined by the FsSlice.
|
||||
package namespace
|
||||
50
api/filters/namespace/example_test.go
Normal file
50
api/filters/namespace/example_test.go
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package namespace_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/filters/namespace"
|
||||
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
)
|
||||
|
||||
func ExampleFilter() {
|
||||
fss := builtinconfig.MakeDefaultConfig().NameSpace
|
||||
err := kio.Pipeline{
|
||||
Inputs: []kio.Reader{&kio.ByteReader{Reader: bytes.NewBufferString(`
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
namespace: bar
|
||||
`)}},
|
||||
Filters: []kio.Filter{namespace.Filter{Namespace: "app", FsSlice: fss}},
|
||||
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
|
||||
// namespace: app
|
||||
// ---
|
||||
// apiVersion: example.com/v1
|
||||
// kind: Bar
|
||||
// metadata:
|
||||
// name: instance
|
||||
// namespace: app
|
||||
}
|
||||
174
api/filters/namespace/namespace.go
Normal file
174
api/filters/namespace/namespace.go
Normal file
@@ -0,0 +1,174 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
type Filter struct {
|
||||
// Namespace is the namespace to apply to the inputs
|
||||
Namespace string `yaml:"namespace,omitempty"`
|
||||
|
||||
// FsSlice contains the FieldSpecs to locate the namespace field
|
||||
FsSlice types.FsSlice `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
|
||||
|
||||
trackableSetter filtersutil.TrackableSetter
|
||||
}
|
||||
|
||||
var _ kio.Filter = Filter{}
|
||||
var _ kio.TrackableFilter = &Filter{}
|
||||
|
||||
// WithMutationTracker registers a callback which will be invoked each time a field is mutated
|
||||
func (ns *Filter) WithMutationTracker(callback func(key, value, tag string, node *yaml.RNode)) {
|
||||
ns.trackableSetter.WithMutationTracker(callback)
|
||||
}
|
||||
|
||||
func (ns Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||
return kio.FilterAll(yaml.FilterFunc(ns.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) {
|
||||
// hacks for hardcoded types -- :(
|
||||
if err := ns.hacks(node); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Remove the fieldspecs that are for hardcoded fields. The fieldspecs
|
||||
// exist for backwards compatibility with other implementations
|
||||
// of this transformation.
|
||||
// This implementation of the namespace transformation
|
||||
// Does not use the fieldspecs for implementing cases which
|
||||
// require hardcoded logic.
|
||||
ns.FsSlice = ns.removeFieldSpecsForHacks(ns.FsSlice)
|
||||
|
||||
// transformations based on data -- :)
|
||||
err := node.PipeE(fsslice.Filter{
|
||||
FsSlice: ns.FsSlice,
|
||||
SetValue: ns.trackableSetter.SetScalar(ns.Namespace),
|
||||
CreateKind: yaml.ScalarNode, // Namespace is a ScalarNode
|
||||
CreateTag: yaml.NodeTagString,
|
||||
})
|
||||
return node, err
|
||||
}
|
||||
|
||||
// hacks applies the namespace transforms that are hardcoded rather
|
||||
// than specified through FieldSpecs.
|
||||
func (ns Filter) hacks(obj *yaml.RNode) error {
|
||||
gvk := resid.GvkFromNode(obj)
|
||||
if err := ns.metaNamespaceHack(obj, gvk); err != nil {
|
||||
return err
|
||||
}
|
||||
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 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, gvk resid.Gvk) error {
|
||||
if gvk.IsClusterScoped() {
|
||||
return nil
|
||||
}
|
||||
f := fsslice.Filter{
|
||||
FsSlice: []types.FieldSpec{
|
||||
{Path: types.MetadataNamespacePath, CreateIfNotPresent: true},
|
||||
},
|
||||
SetValue: ns.trackableSetter.SetScalar(ns.Namespace),
|
||||
CreateKind: yaml.ScalarNode, // Namespace is a ScalarNode
|
||||
}
|
||||
_, err := f.Filter(obj)
|
||||
return err
|
||||
}
|
||||
|
||||
// roleBindingHack is a hack for implementing the namespace transform
|
||||
// for RoleBinding and ClusterRoleBinding resource types.
|
||||
// RoleBinding and ClusterRoleBinding have namespace set on
|
||||
// elements of the "subjects" field if and only if the subject elements
|
||||
// "name" is "default". Otherwise the namespace is not set.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// kind: RoleBinding
|
||||
// subjects:
|
||||
// - name: "default" # this will have the namespace set
|
||||
// ...
|
||||
// - name: "something-else" # this will not have the namespace set
|
||||
// ...
|
||||
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.IsMissingOrNull(obj) {
|
||||
return err
|
||||
}
|
||||
|
||||
// add the namespace to each "subject" with name: default
|
||||
err = obj.VisitElements(func(o *yaml.RNode) error {
|
||||
// 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.IsMissingOrNull(name) {
|
||||
return err
|
||||
}
|
||||
|
||||
// set the namespace for the default account
|
||||
node, err := o.Pipe(
|
||||
yaml.LookupCreate(yaml.ScalarNode, "namespace"),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ns.trackableSetter.SetScalar(ns.Namespace)(node)
|
||||
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// removeFieldSpecsForHacks removes from the list fieldspecs that
|
||||
// have hardcoded implementations
|
||||
func (ns Filter) removeFieldSpecsForHacks(fs types.FsSlice) types.FsSlice {
|
||||
var val types.FsSlice
|
||||
for i := range fs {
|
||||
// implemented by metaNamespaceHack
|
||||
if fs[i].Path == types.MetadataNamespacePath {
|
||||
continue
|
||||
}
|
||||
// implemented by roleBindingHack
|
||||
if fs[i].Kind == roleBindingKind && fs[i].Path == subjectsField {
|
||||
continue
|
||||
}
|
||||
// implemented by roleBindingHack
|
||||
if fs[i].Kind == clusterRoleBindingKind && fs[i].Path == subjectsField {
|
||||
continue
|
||||
}
|
||||
val = append(val, fs[i])
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
const (
|
||||
subjectsField = "subjects"
|
||||
roleBindingKind = "RoleBinding"
|
||||
clusterRoleBindingKind = "ClusterRoleBinding"
|
||||
)
|
||||
385
api/filters/namespace/namespace_test.go
Normal file
385
api/filters/namespace/namespace_test.go
Normal file
@@ -0,0 +1,385 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package namespace_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"sigs.k8s.io/kustomize/api/filters/namespace"
|
||||
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
||||
filtertest_test "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
var mutationTrackerStub = filtertest_test.MutationTrackerStub{}
|
||||
|
||||
var tests = []TestCase{
|
||||
{
|
||||
name: "add",
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
namespace: foo
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
namespace: foo
|
||||
`,
|
||||
filter: namespace.Filter{Namespace: "foo"},
|
||||
},
|
||||
|
||||
{
|
||||
name: "null_ns",
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
namespace: null
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
namespace: null
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
namespace: foo
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
namespace: foo
|
||||
`,
|
||||
filter: namespace.Filter{Namespace: "foo"},
|
||||
},
|
||||
|
||||
{
|
||||
name: "add-recurse",
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
namespace: foo
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
namespace: foo
|
||||
`,
|
||||
filter: namespace.Filter{Namespace: "foo"},
|
||||
},
|
||||
|
||||
{
|
||||
name: "update",
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
# update this namespace
|
||||
namespace: bar
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
namespace: bar
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
# update this namespace
|
||||
namespace: foo
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
namespace: foo
|
||||
`,
|
||||
filter: namespace.Filter{Namespace: "foo"},
|
||||
},
|
||||
|
||||
{
|
||||
name: "update-rolebinding",
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: RoleBinding
|
||||
subjects:
|
||||
- name: default
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: RoleBinding
|
||||
subjects:
|
||||
- name: default
|
||||
namespace: foo
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: RoleBinding
|
||||
subjects:
|
||||
- name: something
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: RoleBinding
|
||||
subjects:
|
||||
- name: something
|
||||
namespace: foo
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: example.com/v1
|
||||
kind: RoleBinding
|
||||
subjects:
|
||||
- name: default
|
||||
namespace: bar
|
||||
metadata:
|
||||
namespace: bar
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: RoleBinding
|
||||
subjects:
|
||||
- name: default
|
||||
namespace: bar
|
||||
metadata:
|
||||
namespace: bar
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: RoleBinding
|
||||
subjects:
|
||||
- name: something
|
||||
metadata:
|
||||
namespace: bar
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: RoleBinding
|
||||
subjects:
|
||||
- name: something
|
||||
namespace: foo
|
||||
metadata:
|
||||
namespace: bar
|
||||
`,
|
||||
filter: namespace.Filter{Namespace: "bar"},
|
||||
},
|
||||
|
||||
{
|
||||
name: "update-clusterrolebinding",
|
||||
input: `
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
subjects:
|
||||
- name: default
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
subjects:
|
||||
- name: default
|
||||
namespace: foo
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
subjects:
|
||||
- name: something
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
subjects:
|
||||
- name: something
|
||||
namespace: foo
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
subjects:
|
||||
- name: default
|
||||
namespace: bar
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
subjects:
|
||||
- name: default
|
||||
namespace: bar
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
subjects:
|
||||
- name: something
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
subjects:
|
||||
- name: something
|
||||
namespace: foo
|
||||
`,
|
||||
filter: namespace.Filter{Namespace: "bar"},
|
||||
},
|
||||
|
||||
{
|
||||
name: "data-fieldspecs",
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
namespace: foo
|
||||
a:
|
||||
b:
|
||||
c: foo
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
namespace: foo
|
||||
a:
|
||||
b:
|
||||
c: foo
|
||||
`,
|
||||
filter: namespace.Filter{Namespace: "foo"},
|
||||
fsslice: []types.FieldSpec{
|
||||
{
|
||||
Path: "a/b/c",
|
||||
CreateIfNotPresent: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "mutation tracker",
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: RoleBinding
|
||||
subjects:
|
||||
- name: default
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
namespace: bar
|
||||
a:
|
||||
b:
|
||||
c: bar
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: RoleBinding
|
||||
subjects:
|
||||
- name: default
|
||||
namespace: bar
|
||||
metadata:
|
||||
namespace: bar
|
||||
a:
|
||||
b:
|
||||
c: bar
|
||||
`,
|
||||
filter: namespace.Filter{Namespace: "bar"},
|
||||
fsslice: []types.FieldSpec{
|
||||
{
|
||||
Path: "a/b/c",
|
||||
CreateIfNotPresent: true,
|
||||
},
|
||||
},
|
||||
mutationTracker: mutationTrackerStub.MutationTracker,
|
||||
expectedSetValueArgs: []filtertest_test.SetValueArg{
|
||||
{
|
||||
Value: "bar",
|
||||
NodePath: []string{"metadata", "namespace"},
|
||||
},
|
||||
{
|
||||
Value: "bar",
|
||||
NodePath: []string{"a", "b", "c"},
|
||||
},
|
||||
{
|
||||
Value: "bar",
|
||||
NodePath: []string{"metadata", "namespace"},
|
||||
},
|
||||
{
|
||||
Value: "bar",
|
||||
NodePath: []string{"namespace"},
|
||||
},
|
||||
{
|
||||
Value: "bar",
|
||||
NodePath: []string{"a", "b", "c"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
type TestCase struct {
|
||||
name string
|
||||
input string
|
||||
expected string
|
||||
filter namespace.Filter
|
||||
fsslice types.FsSlice
|
||||
mutationTracker func(key, value, tag string, node *yaml.RNode)
|
||||
expectedSetValueArgs []filtertest_test.SetValueArg
|
||||
}
|
||||
|
||||
var config = builtinconfig.MakeDefaultConfig()
|
||||
|
||||
func TestNamespace_Filter(t *testing.T) {
|
||||
for i := range tests {
|
||||
mutationTrackerStub.Reset()
|
||||
test := tests[i]
|
||||
test.filter.WithMutationTracker(test.mutationTracker)
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
test.filter.FsSlice = append(config.NameSpace, test.fsslice...)
|
||||
if !assert.Equal(t,
|
||||
strings.TrimSpace(test.expected),
|
||||
strings.TrimSpace(
|
||||
filtertest_test.RunFilter(t, test.input, test.filter))) {
|
||||
t.FailNow()
|
||||
}
|
||||
assert.Equal(t, test.expectedSetValueArgs, mutationTrackerStub.SetValueArgs())
|
||||
})
|
||||
}
|
||||
}
|
||||
6
api/filters/patchjson6902/doc.go
Normal file
6
api/filters/patchjson6902/doc.go
Normal file
@@ -0,0 +1,6 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package namespace contains a kio.Filter implementation of the kustomize
|
||||
// patchjson6902 transformer
|
||||
package patchjson6902
|
||||
55
api/filters/patchjson6902/example_test.go
Normal file
55
api/filters/patchjson6902/example_test.go
Normal file
@@ -0,0 +1,55 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package patchjson6902
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
)
|
||||
|
||||
func ExampleFilter() {
|
||||
err := kio.Pipeline{
|
||||
Inputs: []kio.Reader{&kio.ByteReader{Reader: bytes.NewBufferString(`
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
namespace: bar
|
||||
`)}},
|
||||
Filters: []kio.Filter{
|
||||
Filter{
|
||||
Patch: `
|
||||
- op: replace
|
||||
path: /metadata/namespace
|
||||
value: "ns"
|
||||
`,
|
||||
},
|
||||
},
|
||||
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
|
||||
// namespace: ns
|
||||
// ---
|
||||
// apiVersion: example.com/v1
|
||||
// kind: Bar
|
||||
// metadata:
|
||||
// name: instance
|
||||
// namespace: ns
|
||||
}
|
||||
65
api/filters/patchjson6902/patchjson6902.go
Normal file
65
api/filters/patchjson6902/patchjson6902.go
Normal file
@@ -0,0 +1,65 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package patchjson6902
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
jsonpatch "github.com/evanphx/json-patch"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
k8syaml "sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
type Filter struct {
|
||||
Patch string
|
||||
|
||||
decodedPatch jsonpatch.Patch
|
||||
}
|
||||
|
||||
var _ kio.Filter = Filter{}
|
||||
|
||||
func (pf Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||
decodedPatch, err := pf.decodePatch()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pf.decodedPatch = decodedPatch
|
||||
return kio.FilterAll(yaml.FilterFunc(pf.run)).Filter(nodes)
|
||||
}
|
||||
|
||||
func (pf Filter) decodePatch() (jsonpatch.Patch, error) {
|
||||
patch := pf.Patch
|
||||
// If the patch doesn't look like a JSON6902 patch, we
|
||||
// try to parse it to json.
|
||||
if !strings.HasPrefix(pf.Patch, "[") {
|
||||
p, err := k8syaml.YAMLToJSON([]byte(patch))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
patch = string(p)
|
||||
}
|
||||
decodedPatch, err := jsonpatch.DecodePatch([]byte(patch))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return decodedPatch, nil
|
||||
}
|
||||
|
||||
func (pf Filter) run(node *yaml.RNode) (*yaml.RNode, error) {
|
||||
// We don't actually use the kyaml library for manipulating the
|
||||
// yaml here. We just marshal it to json and rely on the
|
||||
// jsonpatch library to take care of applying the patch.
|
||||
// This means ordering might not be preserved with this filter.
|
||||
b, err := node.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := pf.decodedPatch.Apply(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = node.UnmarshalJSON(res)
|
||||
return node, err
|
||||
}
|
||||
173
api/filters/patchjson6902/patchjson6902_test.go
Normal file
173
api/filters/patchjson6902/patchjson6902_test.go
Normal file
@@ -0,0 +1,173 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package patchjson6902
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
filtertest "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
||||
)
|
||||
|
||||
const input = `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
replica: 2
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
old-label: old-value
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx
|
||||
name: nginx
|
||||
`
|
||||
|
||||
func TestSomething(t *testing.T) {
|
||||
testCases := []struct {
|
||||
testName string
|
||||
input string
|
||||
filter Filter
|
||||
expectedOutput string
|
||||
}{
|
||||
{
|
||||
testName: "single operation, json",
|
||||
input: input,
|
||||
filter: Filter{
|
||||
Patch: `[
|
||||
{"op": "replace", "path": "/spec/replica", "value": 5}
|
||||
]`,
|
||||
},
|
||||
expectedOutput: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
replica: 5
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
old-label: old-value
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx
|
||||
name: nginx
|
||||
`,
|
||||
},
|
||||
{
|
||||
testName: "multiple operations, json",
|
||||
input: input,
|
||||
filter: Filter{
|
||||
Patch: `[
|
||||
{"op": "replace", "path": "/spec/template/spec/containers/0/name", "value": "my-nginx"},
|
||||
{"op": "add", "path": "/spec/replica", "value": 999},
|
||||
{"op": "add", "path": "/spec/template/spec/containers/0/command", "value": ["arg1", "arg2", "arg3"]}
|
||||
]`,
|
||||
},
|
||||
expectedOutput: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
replica: 999
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
old-label: old-value
|
||||
spec:
|
||||
containers:
|
||||
- command:
|
||||
- arg1
|
||||
- arg2
|
||||
- arg3
|
||||
image: nginx
|
||||
name: my-nginx
|
||||
`,
|
||||
},
|
||||
{
|
||||
testName: "single operation, yaml",
|
||||
input: input,
|
||||
filter: Filter{
|
||||
Patch: `
|
||||
- op: replace
|
||||
path: /spec/replica
|
||||
value: 5
|
||||
`,
|
||||
},
|
||||
expectedOutput: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
replica: 5
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
old-label: old-value
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx
|
||||
name: nginx
|
||||
`,
|
||||
},
|
||||
{
|
||||
testName: "multiple operations, yaml",
|
||||
input: input,
|
||||
filter: Filter{
|
||||
Patch: `
|
||||
- op: replace
|
||||
path: /spec/template/spec/containers/0/name
|
||||
value: my-nginx
|
||||
- op: add
|
||||
path: /spec/replica
|
||||
value: 999
|
||||
- op: add
|
||||
path: /spec/template/spec/containers/0/command
|
||||
value:
|
||||
- arg1
|
||||
- arg2
|
||||
- arg3
|
||||
`,
|
||||
},
|
||||
expectedOutput: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
replica: 999
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
old-label: old-value
|
||||
spec:
|
||||
containers:
|
||||
- command:
|
||||
- arg1
|
||||
- arg2
|
||||
- arg3
|
||||
image: nginx
|
||||
name: my-nginx
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
if !assert.Equal(t,
|
||||
strings.TrimSpace(tc.expectedOutput),
|
||||
strings.TrimSpace(
|
||||
filtertest.RunFilter(t, tc.input, tc.filter))) {
|
||||
t.FailNow()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
6
api/filters/patchstrategicmerge/doc.go
Normal file
6
api/filters/patchstrategicmerge/doc.go
Normal file
@@ -0,0 +1,6 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package patchstrategicmerge contains a kio.Filter implementation of the
|
||||
// kustomize strategic merge patch transformer.
|
||||
package patchstrategicmerge
|
||||
49
api/filters/patchstrategicmerge/example_test.go
Normal file
49
api/filters/patchstrategicmerge/example_test.go
Normal file
@@ -0,0 +1,49 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package patchstrategicmerge
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
func ExampleFilter() {
|
||||
err := kio.Pipeline{
|
||||
Inputs: []kio.Reader{&kio.ByteReader{Reader: bytes.NewBufferString(`
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
spec:
|
||||
replicas: 3
|
||||
`)}},
|
||||
Filters: []kio.Filter{Filter{
|
||||
Patch: yaml.MustParse(`
|
||||
spec:
|
||||
template:
|
||||
containers:
|
||||
- image: nginx
|
||||
`),
|
||||
}},
|
||||
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
|
||||
// spec:
|
||||
// replicas: 3
|
||||
// template:
|
||||
// containers:
|
||||
// - image: nginx
|
||||
}
|
||||
36
api/filters/patchstrategicmerge/patchstrategicmerge.go
Normal file
36
api/filters/patchstrategicmerge/patchstrategicmerge.go
Normal file
@@ -0,0 +1,36 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package patchstrategicmerge
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml/merge2"
|
||||
)
|
||||
|
||||
type Filter struct {
|
||||
Patch *yaml.RNode
|
||||
}
|
||||
|
||||
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],
|
||||
yaml.MergeOptions{
|
||||
ListIncreaseDirection: yaml.MergeOptionsListPrepend,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if r != nil {
|
||||
result = append(result, r)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
749
api/filters/patchstrategicmerge/patchstrategicmerge_test.go
Normal file
749
api/filters/patchstrategicmerge/patchstrategicmerge_test.go
Normal file
@@ -0,0 +1,749 @@
|
||||
package patchstrategicmerge
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
filtertest "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
func TestFilter(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
input string
|
||||
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
|
||||
metadata:
|
||||
name: myDeploy
|
||||
kind: Deployment
|
||||
`,
|
||||
patch: yaml.MustParse(`
|
||||
metadata:
|
||||
name: yourDeploy
|
||||
`),
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
metadata:
|
||||
name: yourDeploy
|
||||
kind: Deployment
|
||||
`,
|
||||
},
|
||||
"container patch": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
metadata:
|
||||
name: myDeploy
|
||||
kind: Deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: foo1
|
||||
- name: foo2
|
||||
- name: foo3
|
||||
`,
|
||||
patch: yaml.MustParse(`
|
||||
apiVersion: apps/v1
|
||||
metadata:
|
||||
name: myDeploy
|
||||
kind: Deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: foo0
|
||||
`),
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
metadata:
|
||||
name: myDeploy
|
||||
kind: Deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: foo0
|
||||
- name: foo1
|
||||
- name: foo2
|
||||
- name: foo3
|
||||
`,
|
||||
},
|
||||
"volumes patch": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
metadata:
|
||||
name: myDeploy
|
||||
kind: Deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
volumes:
|
||||
- name: foo1
|
||||
- name: foo2
|
||||
- name: foo3
|
||||
`,
|
||||
patch: yaml.MustParse(`
|
||||
apiVersion: apps/v1
|
||||
metadata:
|
||||
name: myDeploy
|
||||
kind: Deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
volumes:
|
||||
- name: foo0
|
||||
`),
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
metadata:
|
||||
name: myDeploy
|
||||
kind: Deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
volumes:
|
||||
- name: foo0
|
||||
- name: foo1
|
||||
- name: foo2
|
||||
- name: foo3
|
||||
`,
|
||||
},
|
||||
"nested patch": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
metadata:
|
||||
name: myDeploy
|
||||
kind: Deployment
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
args:
|
||||
- abc
|
||||
`,
|
||||
patch: yaml.MustParse(`
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
args:
|
||||
- def
|
||||
`),
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
metadata:
|
||||
name: myDeploy
|
||||
kind: Deployment
|
||||
spec:
|
||||
containers:
|
||||
- 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"
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for tn, tc := range testCases {
|
||||
t.Run(tn, func(t *testing.T) {
|
||||
f := Filter{
|
||||
Patch: tc.patch,
|
||||
}
|
||||
if !assert.Equal(t,
|
||||
strings.TrimSpace(tc.expected),
|
||||
strings.TrimSpace(
|
||||
filtertest.RunFilter(t, tc.input, f))) {
|
||||
t.FailNow()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
6
api/filters/prefix/doc.go
Normal file
6
api/filters/prefix/doc.go
Normal file
@@ -0,0 +1,6 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package prefix contains a kio.Filter implementation of the kustomize
|
||||
// PrefixTransformer.
|
||||
package prefix
|
||||
47
api/filters/prefix/example_test.go
Normal file
47
api/filters/prefix/example_test.go
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package prefix_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/filters/prefix"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
)
|
||||
|
||||
func ExampleFilter() {
|
||||
err := kio.Pipeline{
|
||||
Inputs: []kio.Reader{&kio.ByteReader{Reader: bytes.NewBufferString(`
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
`)}},
|
||||
Filters: []kio.Filter{prefix.Filter{
|
||||
Prefix: "baz-", FieldSpec: types.FieldSpec{Path: "metadata/name"}}},
|
||||
Outputs: []kio.Writer{kio.ByteWriter{Writer: os.Stdout}},
|
||||
}.Execute()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// apiVersion: example.com/v1
|
||||
// kind: Foo
|
||||
// metadata:
|
||||
// name: baz-instance
|
||||
// ---
|
||||
// apiVersion: example.com/v1
|
||||
// kind: Bar
|
||||
// metadata:
|
||||
// name: baz-instance
|
||||
}
|
||||
50
api/filters/prefix/prefix.go
Normal file
50
api/filters/prefix/prefix.go
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package prefix
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// Filter applies resource name prefix's using the fieldSpecs
|
||||
type Filter struct {
|
||||
Prefix string `json:"prefix,omitempty" yaml:"prefix,omitempty"`
|
||||
|
||||
FieldSpec types.FieldSpec `json:"fieldSpec,omitempty" yaml:"fieldSpec,omitempty"`
|
||||
|
||||
trackableSetter filtersutil.TrackableSetter
|
||||
}
|
||||
|
||||
var _ kio.Filter = Filter{}
|
||||
var _ kio.TrackableFilter = &Filter{}
|
||||
|
||||
// WithMutationTracker registers a callback which will be invoked each time a field is mutated
|
||||
func (f *Filter) WithMutationTracker(callback func(key, value, tag string, node *yaml.RNode)) {
|
||||
f.trackableSetter.WithMutationTracker(callback)
|
||||
}
|
||||
|
||||
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.evaluateField,
|
||||
CreateKind: yaml.ScalarNode, // Name is a ScalarNode
|
||||
CreateTag: yaml.NodeTagString,
|
||||
})
|
||||
return node, err
|
||||
}
|
||||
|
||||
func (f Filter) evaluateField(node *yaml.RNode) error {
|
||||
return f.trackableSetter.SetScalar(fmt.Sprintf(
|
||||
"%s%s", f.Prefix, node.YNode().Value))(node)
|
||||
}
|
||||
154
api/filters/prefix/prefix_test.go
Normal file
154
api/filters/prefix/prefix_test.go
Normal file
@@ -0,0 +1,154 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package prefix_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"sigs.k8s.io/kustomize/api/filters/prefix"
|
||||
filtertest_test "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
var mutationTrackerStub = filtertest_test.MutationTrackerStub{}
|
||||
|
||||
var tests = map[string]TestCase{
|
||||
"prefix": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: foo-instance
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: foo-instance
|
||||
`,
|
||||
filter: prefix.Filter{
|
||||
Prefix: "foo-",
|
||||
FieldSpec: types.FieldSpec{Path: "metadata/name"},
|
||||
},
|
||||
},
|
||||
|
||||
"data-fieldspecs": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
a:
|
||||
b:
|
||||
c: d
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
a:
|
||||
b:
|
||||
c: d
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
a:
|
||||
b:
|
||||
c: foo-d
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
a:
|
||||
b:
|
||||
c: foo-d
|
||||
`,
|
||||
filter: prefix.Filter{
|
||||
Prefix: "foo-",
|
||||
FieldSpec: types.FieldSpec{Path: "a/b/c"},
|
||||
},
|
||||
},
|
||||
|
||||
"mutation tracker": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: foo-instance
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: foo-instance
|
||||
`,
|
||||
filter: prefix.Filter{
|
||||
Prefix: "foo-",
|
||||
FieldSpec: types.FieldSpec{Path: "metadata/name"},
|
||||
},
|
||||
mutationTracker: mutationTrackerStub.MutationTracker,
|
||||
expectedSetValueArgs: []filtertest_test.SetValueArg{
|
||||
{
|
||||
Value: "foo-instance",
|
||||
NodePath: []string{"metadata", "name"},
|
||||
},
|
||||
{
|
||||
Value: "foo-instance",
|
||||
NodePath: []string{"metadata", "name"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
type TestCase struct {
|
||||
input string
|
||||
expected string
|
||||
filter prefix.Filter
|
||||
mutationTracker func(key, value, tag string, node *yaml.RNode)
|
||||
expectedSetValueArgs []filtertest_test.SetValueArg
|
||||
}
|
||||
|
||||
func TestFilter(t *testing.T) {
|
||||
for name := range tests {
|
||||
mutationTrackerStub.Reset()
|
||||
test := tests[name]
|
||||
test.filter.WithMutationTracker(test.mutationTracker)
|
||||
t.Run(name, func(t *testing.T) {
|
||||
if !assert.Equal(t,
|
||||
strings.TrimSpace(test.expected),
|
||||
strings.TrimSpace(
|
||||
filtertest_test.RunFilter(t, test.input, test.filter))) {
|
||||
t.FailNow()
|
||||
}
|
||||
assert.Equal(t, test.expectedSetValueArgs, mutationTrackerStub.SetValueArgs())
|
||||
})
|
||||
}
|
||||
}
|
||||
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
|
||||
147
api/filters/refvar/expand.go
Normal file
147
api/filters/refvar/expand.go
Normal file
@@ -0,0 +1,147 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package refvar
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
operator = '$'
|
||||
referenceOpener = '('
|
||||
referenceCloser = ')'
|
||||
)
|
||||
|
||||
// syntaxWrap returns the input string wrapped by the expansion syntax.
|
||||
func syntaxWrap(input string) string {
|
||||
var sb strings.Builder
|
||||
sb.WriteByte(operator)
|
||||
sb.WriteByte(referenceOpener)
|
||||
sb.WriteString(input)
|
||||
sb.WriteByte(referenceCloser)
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// Copy the portion of the input string since the last
|
||||
// checkpoint into the buffer
|
||||
buf.WriteString(input[checkpoint:cursor])
|
||||
|
||||
// Attempt to read the variable name as defined by the
|
||||
// syntax from the input string
|
||||
read, isVar, advance := tryReadVariableName(input[cursor+1:])
|
||||
|
||||
if isVar {
|
||||
// We were able to read a variable name correctly;
|
||||
// apply the mapping to the variable name and copy the
|
||||
// bytes into the buffer
|
||||
mapped := mapping(read)
|
||||
if input == syntaxWrap(read) {
|
||||
// Preserve the type of variable
|
||||
return mapped
|
||||
}
|
||||
|
||||
// Variable is used in a middle of a string
|
||||
buf.WriteString(fmt.Sprintf("%v", mapped))
|
||||
} else {
|
||||
// Not a variable name; copy the read bytes into the buffer
|
||||
buf.WriteString(read)
|
||||
}
|
||||
|
||||
// Advance the cursor in the input string to account for
|
||||
// bytes consumed to read the variable name expression
|
||||
cursor += advance
|
||||
|
||||
// Advance the checkpoint in the input string
|
||||
checkpoint = cursor + 1
|
||||
}
|
||||
}
|
||||
|
||||
// Return the buffer and any remaining unwritten bytes in the
|
||||
// input string.
|
||||
return buf.String() + input[checkpoint:]
|
||||
}
|
||||
|
||||
// tryReadVariableName attempts to read a variable name from the input
|
||||
// string and returns the content read from the input, whether that content
|
||||
// represents a variable name to perform mapping on, and the number of bytes
|
||||
// consumed in the input string.
|
||||
//
|
||||
// The input string is assumed not to contain the initial operator.
|
||||
func tryReadVariableName(input string) (string, bool, int) {
|
||||
switch input[0] {
|
||||
case operator:
|
||||
// Escaped operator; return it.
|
||||
return input[0:1], false, 1
|
||||
case referenceOpener:
|
||||
// Scan to expression closer
|
||||
for i := 1; i < len(input); i++ {
|
||||
if input[i] == referenceCloser {
|
||||
return input[1:i], true, i + 1
|
||||
}
|
||||
}
|
||||
|
||||
// Incomplete reference; return it.
|
||||
return string(operator) + string(referenceOpener), false, 1
|
||||
default:
|
||||
// Not the beginning of an expression, ie, an operator
|
||||
// that doesn't begin an expression. Return the operator
|
||||
// and the first rune in the string.
|
||||
return string(operator) + string(input[0]), false, 1
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,14 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package expansion_test
|
||||
package refvar_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
. "sigs.k8s.io/kustomize/v3/pkg/expansion"
|
||||
"github.com/stretchr/testify/assert"
|
||||
. "sigs.k8s.io/kustomize/api/filters/refvar"
|
||||
)
|
||||
|
||||
type expected struct {
|
||||
@@ -28,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
|
||||
@@ -64,7 +94,7 @@ func TestMapReference(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
declaredEnv := map[string]interface{}{
|
||||
varMap := map[string]interface{}{
|
||||
"FOO": "bar",
|
||||
"ZOO": "$(FOO)-1",
|
||||
"BLU": "$(ZOO)-2",
|
||||
@@ -74,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{
|
||||
@@ -92,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
|
||||
@@ -346,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)
|
||||
}
|
||||
@@ -360,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
|
||||
}
|
||||
280
api/filters/refvar/refvar_test.go
Normal file
280
api/filters/refvar/refvar_test.go
Normal file
@@ -0,0 +1,280 @@
|
||||
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 Deployment.v1.apps/dep.[noNs]: 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 Deployment.v1.apps/dep.[noNs]: 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.ResId)
|
||||
}
|
||||
|
||||
return getRefinedValue(r.Source.Options, rn)
|
||||
}
|
||||
|
||||
func getRefinedValue(options *types.FieldOptions, rn *yaml.RNode) (*yaml.RNode, error) {
|
||||
if options == nil || options.Delimiter == "" {
|
||||
return rn, nil
|
||||
}
|
||||
if rn.YNode().Kind != yaml.ScalarNode {
|
||||
return nil, fmt.Errorf("delimiter option can only be used with scalar nodes")
|
||||
}
|
||||
value := strings.Split(yaml.GetValue(rn), options.Delimiter)
|
||||
if options.Index >= len(value) || options.Index < 0 {
|
||||
return nil, fmt.Errorf("options.index %d is out of bounds for value %s", options.Index, yaml.GetValue(rn))
|
||||
}
|
||||
n := rn.Copy()
|
||||
n.YNode().Value = value[options.Index]
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// selectSourceNode finds the node that matches the selector, returning
|
||||
// an error if multiple or none are found
|
||||
func selectSourceNode(nodes []*yaml.RNode, selector *types.SourceSelector) (*yaml.RNode, error) {
|
||||
var matches []*yaml.RNode
|
||||
for _, n := range nodes {
|
||||
ids, err := utils.MakeResIds(n)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, id := range ids {
|
||||
if id.IsSelectedBy(selector.ResId) {
|
||||
if len(matches) > 0 {
|
||||
return nil, fmt.Errorf(
|
||||
"multiple matches for selector %s", selector)
|
||||
}
|
||||
matches = append(matches, n)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(matches) == 0 {
|
||||
return nil, fmt.Errorf("nothing selected by %s", selector)
|
||||
}
|
||||
return matches[0], nil
|
||||
}
|
||||
1839
api/filters/replacement/replacement_test.go
Normal file
1839
api/filters/replacement/replacement_test.go
Normal file
File diff suppressed because it is too large
Load Diff
6
api/filters/replicacount/doc.go
Normal file
6
api/filters/replicacount/doc.go
Normal file
@@ -0,0 +1,6 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package replicacount contains a kio.Filter implementation of the kustomize
|
||||
// ReplicaCountTransformer.
|
||||
package replicacount
|
||||
62
api/filters/replicacount/example_test.go
Normal file
62
api/filters/replicacount/example_test.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package replicacount
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
)
|
||||
|
||||
func ExampleFilter() {
|
||||
err := kio.Pipeline{
|
||||
Inputs: []kio.Reader{&kio.ByteReader{Reader: bytes.NewBufferString(`
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
spec:
|
||||
template:
|
||||
replicas: 5
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
spec:
|
||||
template:
|
||||
replicas: 5
|
||||
`)}},
|
||||
Filters: []kio.Filter{Filter{
|
||||
Replica: types.Replica{
|
||||
Count: 42,
|
||||
Name: "instance",
|
||||
},
|
||||
FieldSpec: types.FieldSpec{
|
||||
Path: "spec/template/replicas",
|
||||
},
|
||||
}},
|
||||
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
|
||||
// spec:
|
||||
// template:
|
||||
// replicas: 42
|
||||
// ---
|
||||
// apiVersion: example.com/v1
|
||||
// kind: Bar
|
||||
// metadata:
|
||||
// name: instance
|
||||
// spec:
|
||||
// template:
|
||||
// replicas: 42
|
||||
}
|
||||
45
api/filters/replicacount/replicacount.go
Normal file
45
api/filters/replicacount/replicacount.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package replicacount
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// Filter updates/sets replicas fields using the fieldSpecs
|
||||
type Filter struct {
|
||||
Replica types.Replica `json:"replica,omitempty" yaml:"replica,omitempty"`
|
||||
FieldSpec types.FieldSpec `json:"fieldSpec,omitempty" yaml:"fieldSpec,omitempty"`
|
||||
|
||||
trackableSetter filtersutil.TrackableSetter
|
||||
}
|
||||
|
||||
var _ kio.Filter = Filter{}
|
||||
var _ kio.TrackableFilter = &Filter{}
|
||||
|
||||
// WithMutationTracker registers a callback which will be invoked each time a field is mutated
|
||||
func (rc *Filter) WithMutationTracker(callback func(key, value, tag string, node *yaml.RNode)) {
|
||||
rc.trackableSetter.WithMutationTracker(callback)
|
||||
}
|
||||
|
||||
func (rc Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||
return kio.FilterAll(yaml.FilterFunc(rc.run)).Filter(nodes)
|
||||
}
|
||||
|
||||
func (rc Filter) run(node *yaml.RNode) (*yaml.RNode, error) {
|
||||
err := node.PipeE(fieldspec.Filter{
|
||||
FieldSpec: rc.FieldSpec,
|
||||
SetValue: rc.set,
|
||||
CreateKind: yaml.ScalarNode, // replicas is a ScalarNode
|
||||
CreateTag: yaml.NodeTagInt,
|
||||
})
|
||||
return node, err
|
||||
}
|
||||
|
||||
func (rc Filter) set(node *yaml.RNode) error {
|
||||
return rc.trackableSetter.SetScalar(strconv.FormatInt(rc.Replica.Count, 10))(node)
|
||||
}
|
||||
214
api/filters/replicacount/replicacount_test.go
Normal file
214
api/filters/replicacount/replicacount_test.go
Normal file
@@ -0,0 +1,214 @@
|
||||
package replicacount
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
filtertest_test "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
func TestFilter(t *testing.T) {
|
||||
mutationTrackerStub := filtertest_test.MutationTrackerStub{}
|
||||
testCases := map[string]struct {
|
||||
input string
|
||||
expected string
|
||||
filter Filter
|
||||
mutationTracker func(key, value, tag string, node *yaml.RNode)
|
||||
expectedSetValueArgs []filtertest_test.SetValueArg
|
||||
}{
|
||||
"update field": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
spec:
|
||||
replicas: 5
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
spec:
|
||||
replicas: 42
|
||||
`,
|
||||
filter: Filter{
|
||||
Replica: types.Replica{
|
||||
Name: "dep",
|
||||
Count: 42,
|
||||
},
|
||||
FieldSpec: types.FieldSpec{Path: "spec/replicas"},
|
||||
},
|
||||
},
|
||||
"add field": {
|
||||
input: `
|
||||
apiVersion: custom/v1
|
||||
kind: Custom
|
||||
metadata:
|
||||
name: cus
|
||||
spec:
|
||||
template:
|
||||
other: something
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: custom/v1
|
||||
kind: Custom
|
||||
metadata:
|
||||
name: cus
|
||||
spec:
|
||||
template:
|
||||
other: something
|
||||
replicas: 42
|
||||
`,
|
||||
filter: Filter{
|
||||
Replica: types.Replica{
|
||||
Name: "cus",
|
||||
Count: 42,
|
||||
},
|
||||
FieldSpec: types.FieldSpec{
|
||||
Path: "spec/template/replicas",
|
||||
CreateIfNotPresent: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
"add_field_null": {
|
||||
input: `
|
||||
apiVersion: custom/v1
|
||||
kind: Custom
|
||||
metadata:
|
||||
name: cus
|
||||
spec:
|
||||
template:
|
||||
other: something
|
||||
replicas: null
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: custom/v1
|
||||
kind: Custom
|
||||
metadata:
|
||||
name: cus
|
||||
spec:
|
||||
template:
|
||||
other: something
|
||||
replicas: 42
|
||||
`,
|
||||
filter: Filter{
|
||||
Replica: types.Replica{
|
||||
Name: "cus",
|
||||
Count: 42,
|
||||
},
|
||||
FieldSpec: types.FieldSpec{
|
||||
Path: "spec/template/replicas",
|
||||
CreateIfNotPresent: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
"no update if CreateIfNotPresent is false": {
|
||||
input: `
|
||||
apiVersion: custom/v1
|
||||
kind: Custom
|
||||
metadata:
|
||||
name: cus
|
||||
spec:
|
||||
template:
|
||||
other: something
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: custom/v1
|
||||
kind: Custom
|
||||
metadata:
|
||||
name: cus
|
||||
spec:
|
||||
template:
|
||||
other: something
|
||||
`,
|
||||
filter: Filter{
|
||||
Replica: types.Replica{
|
||||
Name: "cus",
|
||||
Count: 42,
|
||||
},
|
||||
FieldSpec: types.FieldSpec{
|
||||
Path: "spec/template/replicas",
|
||||
},
|
||||
},
|
||||
},
|
||||
"update multiple fields": {
|
||||
input: `
|
||||
apiVersion: custom/v1
|
||||
kind: Custom
|
||||
metadata:
|
||||
name: cus
|
||||
spec:
|
||||
template:
|
||||
replicas: 5
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: custom/v1
|
||||
kind: Custom
|
||||
metadata:
|
||||
name: cus
|
||||
spec:
|
||||
template:
|
||||
replicas: 42
|
||||
`,
|
||||
filter: Filter{
|
||||
Replica: types.Replica{
|
||||
Name: "cus",
|
||||
Count: 42,
|
||||
},
|
||||
FieldSpec: types.FieldSpec{Path: "spec/template/replicas"},
|
||||
},
|
||||
},
|
||||
"mutation tracker": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
spec:
|
||||
replicas: 5
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
spec:
|
||||
replicas: 42
|
||||
`,
|
||||
filter: Filter{
|
||||
Replica: types.Replica{
|
||||
Name: "dep",
|
||||
Count: 42,
|
||||
},
|
||||
FieldSpec: types.FieldSpec{Path: "spec/replicas"},
|
||||
},
|
||||
mutationTracker: mutationTrackerStub.MutationTracker,
|
||||
expectedSetValueArgs: []filtertest_test.SetValueArg{
|
||||
{
|
||||
Value: "42",
|
||||
NodePath: []string{"spec", "replicas"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for tn, tc := range testCases {
|
||||
mutationTrackerStub.Reset()
|
||||
tc.filter.WithMutationTracker(tc.mutationTracker)
|
||||
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()
|
||||
}
|
||||
assert.Equal(t, tc.expectedSetValueArgs, mutationTrackerStub.SetValueArgs())
|
||||
})
|
||||
}
|
||||
}
|
||||
6
api/filters/suffix/doc.go
Normal file
6
api/filters/suffix/doc.go
Normal file
@@ -0,0 +1,6 @@
|
||||
// Copyright 2021 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package suffix contains a kio.Filter implementation of the kustomize
|
||||
// SuffixTransformer.
|
||||
package suffix
|
||||
47
api/filters/suffix/example_test.go
Normal file
47
api/filters/suffix/example_test.go
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright 2021 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package suffix_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/filters/suffix"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
)
|
||||
|
||||
func ExampleFilter() {
|
||||
err := kio.Pipeline{
|
||||
Inputs: []kio.Reader{&kio.ByteReader{Reader: bytes.NewBufferString(`
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
`)}},
|
||||
Filters: []kio.Filter{suffix.Filter{
|
||||
Suffix: "-baz", FieldSpec: types.FieldSpec{Path: "metadata/name"}}},
|
||||
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-baz
|
||||
// ---
|
||||
// apiVersion: example.com/v1
|
||||
// kind: Bar
|
||||
// metadata:
|
||||
// name: instance-baz
|
||||
}
|
||||
50
api/filters/suffix/suffix.go
Normal file
50
api/filters/suffix/suffix.go
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright 2021 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package suffix
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// Filter applies resource name suffix's using the fieldSpecs
|
||||
type Filter struct {
|
||||
Suffix string `json:"suffix,omitempty" yaml:"suffix,omitempty"`
|
||||
|
||||
FieldSpec types.FieldSpec `json:"fieldSpec,omitempty" yaml:"fieldSpec,omitempty"`
|
||||
|
||||
trackableSetter filtersutil.TrackableSetter
|
||||
}
|
||||
|
||||
var _ kio.Filter = Filter{}
|
||||
var _ kio.TrackableFilter = &Filter{}
|
||||
|
||||
// WithMutationTracker registers a callback which will be invoked each time a field is mutated
|
||||
func (f *Filter) WithMutationTracker(callback func(key, value, tag string, node *yaml.RNode)) {
|
||||
f.trackableSetter.WithMutationTracker(callback)
|
||||
}
|
||||
|
||||
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.evaluateField,
|
||||
CreateKind: yaml.ScalarNode, // Name is a ScalarNode
|
||||
CreateTag: yaml.NodeTagString,
|
||||
})
|
||||
return node, err
|
||||
}
|
||||
|
||||
func (f Filter) evaluateField(node *yaml.RNode) error {
|
||||
return f.trackableSetter.SetScalar(fmt.Sprintf(
|
||||
"%s%s", node.YNode().Value, f.Suffix))(node)
|
||||
}
|
||||
154
api/filters/suffix/suffix_test.go
Normal file
154
api/filters/suffix/suffix_test.go
Normal file
@@ -0,0 +1,154 @@
|
||||
// Copyright 2021 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package suffix_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"sigs.k8s.io/kustomize/api/filters/suffix"
|
||||
filtertest_test "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
var mutationTrackerStub = filtertest_test.MutationTrackerStub{}
|
||||
|
||||
var tests = map[string]TestCase{
|
||||
"suffix": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance-foo
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance-foo
|
||||
`,
|
||||
filter: suffix.Filter{
|
||||
Suffix: "-foo",
|
||||
FieldSpec: types.FieldSpec{Path: "metadata/name"},
|
||||
},
|
||||
},
|
||||
|
||||
"data-fieldspecs": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
a:
|
||||
b:
|
||||
c: d
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
a:
|
||||
b:
|
||||
c: d
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
a:
|
||||
b:
|
||||
c: d-foo
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
a:
|
||||
b:
|
||||
c: d-foo
|
||||
`,
|
||||
filter: suffix.Filter{
|
||||
Suffix: "-foo",
|
||||
FieldSpec: types.FieldSpec{Path: "a/b/c"},
|
||||
},
|
||||
},
|
||||
|
||||
"mutation tracker": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance-foo
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance-foo
|
||||
`,
|
||||
filter: suffix.Filter{
|
||||
Suffix: "-foo",
|
||||
FieldSpec: types.FieldSpec{Path: "metadata/name"},
|
||||
},
|
||||
mutationTracker: mutationTrackerStub.MutationTracker,
|
||||
expectedSetValueArgs: []filtertest_test.SetValueArg{
|
||||
{
|
||||
Value: "instance-foo",
|
||||
NodePath: []string{"metadata", "name"},
|
||||
},
|
||||
{
|
||||
Value: "instance-foo",
|
||||
NodePath: []string{"metadata", "name"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
type TestCase struct {
|
||||
input string
|
||||
expected string
|
||||
filter suffix.Filter
|
||||
mutationTracker func(key, value, tag string, node *yaml.RNode)
|
||||
expectedSetValueArgs []filtertest_test.SetValueArg
|
||||
}
|
||||
|
||||
func TestFilter(t *testing.T) {
|
||||
for name := range tests {
|
||||
mutationTrackerStub.Reset()
|
||||
test := tests[name]
|
||||
test.filter.WithMutationTracker(test.mutationTracker)
|
||||
t.Run(name, func(t *testing.T) {
|
||||
if !assert.Equal(t,
|
||||
strings.TrimSpace(test.expected),
|
||||
strings.TrimSpace(
|
||||
filtertest_test.RunFilter(t, test.input, test.filter))) {
|
||||
t.FailNow()
|
||||
}
|
||||
assert.Equal(t, test.expectedSetValueArgs, mutationTrackerStub.SetValueArgs())
|
||||
})
|
||||
}
|
||||
}
|
||||
134
api/filters/valueadd/valueadd.go
Normal file
134
api/filters/valueadd/valueadd.go
Normal file
@@ -0,0 +1,134 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package valueadd
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"sigs.k8s.io/kustomize/kyaml/filesys"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
// An 'Add' operation aspiring to IETF RFC 6902 JSON.
|
||||
//
|
||||
// The filter tries to add a value to a node at a particular field path.
|
||||
//
|
||||
// Kinds of target fields:
|
||||
//
|
||||
// - Non-existent target field.
|
||||
//
|
||||
// The field will be added and the value inserted.
|
||||
//
|
||||
// - Existing field, scalar or map.
|
||||
//
|
||||
// E.g. 'spec/template/spec/containers/[name:nginx]/image'
|
||||
//
|
||||
// This behaves like an IETF RFC 6902 Replace operation would;
|
||||
// the existing value is replaced without complaint, even though
|
||||
// this is an Add operation. In contrast, a Replace operation
|
||||
// must fail (report an error) if the field doesn't exist.
|
||||
//
|
||||
// - Existing field, list (array)
|
||||
// Not supported yet.
|
||||
// TODO: Honor fields with RFC-6902-style array indices
|
||||
// TODO: like 'spec/template/spec/containers/2'
|
||||
// TODO: Modify kyaml/yaml/PathGetter to allow this.
|
||||
// The value will be inserted into the array at the given position,
|
||||
// shifting other contents. To instead replace an array entry, use
|
||||
// an implementation of an IETF RFC 6902 Replace operation.
|
||||
//
|
||||
// For the common case of a filepath in the field value, and a desire
|
||||
// to add the value to the filepath (rather than replace the filepath),
|
||||
// use a non-zero value of FilePathPosition (see below).
|
||||
type Filter struct {
|
||||
// Value is the value to add.
|
||||
//
|
||||
// Empty values are disallowed, i.e. this filter isn't intended
|
||||
// for use in erasing or removing fields. For that, use a filter
|
||||
// more aligned with the IETF RFC 6902 JSON Remove operation.
|
||||
//
|
||||
// At the time of writing, Value's value should be a simple string,
|
||||
// not a JSON document. This particular filter focuses on easing
|
||||
// injection of a single-sourced cloud project and/or cluster name
|
||||
// into various fields, especially namespace and various filepath
|
||||
// specifications.
|
||||
Value string
|
||||
|
||||
// FieldPath is a JSON-style path to the field intended to hold the value.
|
||||
FieldPath string
|
||||
|
||||
// FilePathPosition is a filepath field index.
|
||||
//
|
||||
// Call the value of this field _i_.
|
||||
//
|
||||
// If _i_ is zero, negative or unspecified, this field has no effect.
|
||||
//
|
||||
// If _i_ is > 0, then it's assumed that
|
||||
// - 'Value' is a string that can work as a directory or file name,
|
||||
// - the field value intended for replacement holds a filepath.
|
||||
//
|
||||
// The filepath is split into a string slice, the value is inserted
|
||||
// at position [i-1], shifting the rest of the path to the right.
|
||||
// A value of i==1 puts the new value at the start of the path.
|
||||
// This change never converts an absolute path to a relative path,
|
||||
// meaning adding a new field at position i==1 will preserve a
|
||||
// leading slash. E.g. if Value == 'PEACH'
|
||||
//
|
||||
// OLD : NEW : FilePathPosition
|
||||
// --------------------------------------------------------
|
||||
// {empty} : PEACH : irrelevant
|
||||
// / : /PEACH : irrelevant
|
||||
// pie : PEACH/pie : 1 (or less to prefix)
|
||||
// /pie : /PEACH/pie : 1 (or less to prefix)
|
||||
// raw : raw/PEACH : 2 (or more to postfix)
|
||||
// /raw : /raw/PEACH : 2 (or more to postfix)
|
||||
// a/nice/warm/pie : a/nice/warm/PEACH/pie : 4
|
||||
// /a/nice/warm/pie : /a/nice/warm/PEACH/pie : 4
|
||||
//
|
||||
// For robustness (liberal input, conservative output) FilePathPosition
|
||||
// values that that are too large to index the split filepath result in a
|
||||
// postfix rather than an error. So use 1 to prefix, 9999 to postfix.
|
||||
FilePathPosition int `json:"filePathPosition,omitempty" yaml:"filePathPosition,omitempty"`
|
||||
}
|
||||
|
||||
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) {
|
||||
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),
|
||||
// and if found, take it as a signal that this should be a
|
||||
// SequenceNode instead of a ScalarNode, and insert the value
|
||||
// into the proper slot, shifting every over.
|
||||
n, err := node.Pipe(yaml.LookupCreate(yaml.ScalarNode, fields...))
|
||||
if err != nil {
|
||||
return node, err
|
||||
}
|
||||
// TODO: allow more kinds
|
||||
if err := yaml.ErrorIfInvalid(n, yaml.ScalarNode); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newValue := f.Value
|
||||
if f.FilePathPosition > 0 {
|
||||
newValue = filesys.InsertPathPart(
|
||||
n.YNode().Value, f.FilePathPosition-1, newValue)
|
||||
}
|
||||
return n.Pipe(yaml.FieldSetter{StringValue: newValue})
|
||||
})).Filter(nodes)
|
||||
return nodes, err
|
||||
}
|
||||
123
api/filters/valueadd/valueadd_test.go
Normal file
123
api/filters/valueadd/valueadd_test.go
Normal file
@@ -0,0 +1,123 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package valueadd
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
filtertest_test "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
||||
)
|
||||
|
||||
const someResource = `
|
||||
kind: SomeKind
|
||||
spec:
|
||||
resourceRef:
|
||||
external: projects/whatever
|
||||
`
|
||||
|
||||
func TestValueAddFilter(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
input string
|
||||
expectedOutput string
|
||||
filter Filter
|
||||
}{
|
||||
"simpleAdd": {
|
||||
input: `
|
||||
kind: SomeKind
|
||||
`,
|
||||
expectedOutput: `
|
||||
kind: SomeKind
|
||||
spec:
|
||||
resourceRef:
|
||||
external: valueAdded
|
||||
`,
|
||||
filter: Filter{
|
||||
Value: "valueAdded",
|
||||
FieldPath: "spec/resourceRef/external",
|
||||
},
|
||||
},
|
||||
"replaceExisting": {
|
||||
input: someResource,
|
||||
expectedOutput: `
|
||||
kind: SomeKind
|
||||
spec:
|
||||
resourceRef:
|
||||
external: valueAdded
|
||||
`,
|
||||
filter: Filter{
|
||||
Value: "valueAdded",
|
||||
FieldPath: "spec/resourceRef/external",
|
||||
},
|
||||
},
|
||||
"prefixExisting": {
|
||||
input: someResource,
|
||||
expectedOutput: `
|
||||
kind: SomeKind
|
||||
spec:
|
||||
resourceRef:
|
||||
external: valueAdded/projects/whatever
|
||||
`,
|
||||
filter: Filter{
|
||||
Value: "valueAdded",
|
||||
FieldPath: "spec/resourceRef/external",
|
||||
FilePathPosition: 1,
|
||||
},
|
||||
},
|
||||
"postfixExisting": {
|
||||
input: someResource,
|
||||
expectedOutput: `
|
||||
kind: SomeKind
|
||||
spec:
|
||||
resourceRef:
|
||||
external: projects/whatever/valueAdded
|
||||
`,
|
||||
filter: Filter{
|
||||
Value: "valueAdded",
|
||||
FieldPath: "spec/resourceRef/external",
|
||||
FilePathPosition: 99,
|
||||
},
|
||||
},
|
||||
"placeInMiddleOfExisting": {
|
||||
input: someResource,
|
||||
expectedOutput: `
|
||||
kind: SomeKind
|
||||
spec:
|
||||
resourceRef:
|
||||
external: projects/valueAdded/whatever
|
||||
`,
|
||||
filter: Filter{
|
||||
Value: "valueAdded",
|
||||
FieldPath: "spec/resourceRef/external",
|
||||
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 {
|
||||
t.Run(tn, func(t *testing.T) {
|
||||
filter := tc.filter
|
||||
if !assert.Equal(t,
|
||||
strings.TrimSpace(tc.expectedOutput),
|
||||
strings.TrimSpace(filtertest_test.RunFilter(t, tc.input, filter))) {
|
||||
t.FailNow()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
16
api/go.mod
Normal file
16
api/go.mod
Normal file
@@ -0,0 +1,16 @@
|
||||
module sigs.k8s.io/kustomize/api
|
||||
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
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/imdario/mergo v0.3.5
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/stretchr/testify v1.7.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e
|
||||
sigs.k8s.io/kustomize/kyaml v0.13.2
|
||||
sigs.k8s.io/yaml v1.2.0
|
||||
)
|
||||
641
api/go.sum
Normal file
641
api/go.sum
Normal file
@@ -0,0 +1,641 @@
|
||||
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=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
||||
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
|
||||
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
|
||||
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
|
||||
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
||||
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-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/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
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=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
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/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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
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/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
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-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
|
||||
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.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o=
|
||||
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
|
||||
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/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/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/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
||||
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/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
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.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/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/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.2/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/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/gnostic v0.5.1 h1:A8Yhf6EtqTv9RMsU6MQTyrtV1TjWlR6xU9BsZIwuTCM=
|
||||
github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
||||
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||
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/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
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/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
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/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
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/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
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/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
||||
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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
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/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
|
||||
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.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
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/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
||||
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||
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.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
|
||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
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/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
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-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/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-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
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/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
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/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/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-20181026203630-95b1ffbd15a5/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/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-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
||||
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
||||
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
||||
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
||||
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
|
||||
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
|
||||
google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8=
|
||||
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/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
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/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
|
||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
||||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
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.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
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/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
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.3/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/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
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-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
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=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
sigs.k8s.io/kustomize/kyaml v0.13.2 h1:R3uST+eWe4sSj6STEAJWRJPM6BJc3vdFPl7rKtHacps=
|
||||
sigs.k8s.io/kustomize/kyaml v0.13.2/go.mod h1:/ya3Gk4diiQzlE4mBh7wykyLRFZNvqlbh+JnwQ9Vhrc=
|
||||
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=
|
||||
155
api/hasher/hasher.go
Normal file
155
api/hasher/hasher.go
Normal file
@@ -0,0 +1,155 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package hasher
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
// SortArrayAndComputeHash sorts a string array and
|
||||
// returns a hash for it
|
||||
func SortArrayAndComputeHash(s []string) (string, error) {
|
||||
sort.Strings(s)
|
||||
data, err := json.Marshal(s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return encode(hex256(string(data)))
|
||||
}
|
||||
|
||||
// Copied from https://github.com/kubernetes/kubernetes
|
||||
// /blob/master/pkg/kubectl/util/hash/hash.go
|
||||
func encode(hex string) (string, error) {
|
||||
if len(hex) < 10 {
|
||||
return "", fmt.Errorf(
|
||||
"input length must be at least 10")
|
||||
}
|
||||
enc := []rune(hex[:10])
|
||||
for i := range enc {
|
||||
switch enc[i] {
|
||||
case '0':
|
||||
enc[i] = 'g'
|
||||
case '1':
|
||||
enc[i] = 'h'
|
||||
case '3':
|
||||
enc[i] = 'k'
|
||||
case 'a':
|
||||
enc[i] = 'm'
|
||||
case 'e':
|
||||
enc[i] = 't'
|
||||
}
|
||||
}
|
||||
return string(enc), nil
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
356
api/hasher/hasher_test.go
Normal file
356
api/hasher/hasher_test.go
Normal file
@@ -0,0 +1,356 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package hasher
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
func TestSortArrayAndComputeHash(t *testing.T) {
|
||||
array1 := []string{"a", "b", "c", "d"}
|
||||
array2 := []string{"c", "b", "d", "a"}
|
||||
h1, err := SortArrayAndComputeHash(array1)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error %v", err)
|
||||
}
|
||||
if h1 == "" {
|
||||
t.Errorf("failed to hash %v", array1)
|
||||
}
|
||||
h2, err := SortArrayAndComputeHash(array2)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error %v", err)
|
||||
}
|
||||
if h2 == "" {
|
||||
t.Errorf("failed to hash %v", array2)
|
||||
}
|
||||
if h1 != h2 {
|
||||
t.Errorf("hash is not consistent with reordered list: %s %s", h1, h2)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_hex256(t *testing.T) {
|
||||
// hash the empty string to be sure that sha256 is being used
|
||||
expect := "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
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
|
||||
}
|
||||
48
api/ifc/ifc.go
Normal file
48
api/ifc/ifc.go
Normal file
@@ -0,0 +1,48 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package ifc holds miscellaneous interfaces used by kustomize.
|
||||
package ifc
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
// Validator provides functions to validate annotations and labels
|
||||
type Validator interface {
|
||||
MakeAnnotationValidator() func(map[string]string) error
|
||||
MakeAnnotationNameValidator() func([]string) error
|
||||
MakeLabelValidator() func(map[string]string) error
|
||||
MakeLabelNameValidator() func([]string) error
|
||||
ValidateNamespace(string) []string
|
||||
ErrIfInvalidKey(string) error
|
||||
IsEnvVarName(k string) error
|
||||
}
|
||||
|
||||
// KvLoader reads and validates KV pairs.
|
||||
type KvLoader interface {
|
||||
Validator() Validator
|
||||
Load(args types.KvPairSources) (all []types.Pair, err error)
|
||||
}
|
||||
|
||||
// Loader interface exposes methods to read bytes.
|
||||
type Loader interface {
|
||||
// Root returns the root location for this Loader.
|
||||
Root() string
|
||||
// New returns Loader located at newRoot.
|
||||
New(newRoot string) (Loader, error)
|
||||
// Load returns the bytes read from the location or an error.
|
||||
Load(location string) ([]byte, error)
|
||||
// Cleanup cleans the loader
|
||||
Cleanup() error
|
||||
}
|
||||
|
||||
// KustHasher returns a hash of the argument
|
||||
// or an error.
|
||||
type KustHasher interface {
|
||||
Hash(*yaml.RNode) (string, error)
|
||||
}
|
||||
|
||||
// See core.v1.SecretTypeOpaque
|
||||
const SecretTypeOpaque = "Opaque"
|
||||
50
api/image/image.go
Normal file
50
api/image/image.go
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package image
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// IsImageMatched returns true if the value of t is identical to the
|
||||
// image name in the full image name and tag as given by s.
|
||||
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
|
||||
}
|
||||
80
api/image/image_test.go
Normal file
80
api/image/image_test.go
Normal file
@@ -0,0 +1,80 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package image
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIsImageMatched(t *testing.T) {
|
||||
testCases := []struct {
|
||||
testName string
|
||||
value string
|
||||
name string
|
||||
isMatched bool
|
||||
}{
|
||||
{
|
||||
testName: "identical",
|
||||
value: "nginx",
|
||||
name: "nginx",
|
||||
isMatched: true,
|
||||
},
|
||||
{
|
||||
testName: "name is match",
|
||||
value: "nginx:12345",
|
||||
name: "nginx",
|
||||
isMatched: true,
|
||||
},
|
||||
{
|
||||
testName: "name is not a match",
|
||||
value: "apache:12345",
|
||||
name: "nginx",
|
||||
isMatched: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
assert.Equal(t, tc.isMatched, IsImageMatched(tc.value, tc.name))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplit(t *testing.T) {
|
||||
testCases := []struct {
|
||||
testName string
|
||||
value string
|
||||
name string
|
||||
tag string
|
||||
}{
|
||||
{
|
||||
testName: "no tag",
|
||||
value: "nginx",
|
||||
name: "nginx",
|
||||
tag: "",
|
||||
},
|
||||
{
|
||||
testName: "with tag",
|
||||
value: "nginx:1.2.3",
|
||||
name: "nginx",
|
||||
tag: ":1.2.3",
|
||||
},
|
||||
{
|
||||
testName: "with digest",
|
||||
value: "nginx@12345",
|
||||
name: "nginx",
|
||||
tag: "@12345",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
name, tag := Split(tc.value)
|
||||
assert.Equal(t, tc.name, name)
|
||||
assert.Equal(t, tc.tag, tag)
|
||||
})
|
||||
}
|
||||
}
|
||||
198
api/internal/accumulator/loadconfigfromcrds.go
Normal file
198
api/internal/accumulator/loadconfigfromcrds.go
Normal file
@@ -0,0 +1,198 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package accumulator
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"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/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/filesys"
|
||||
"sigs.k8s.io/kustomize/kyaml/resid"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
// 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(
|
||||
ldr ifc.Loader, paths []string) (*builtinconfig.TransformerConfig, error) {
|
||||
tc := builtinconfig.MakeEmptyConfig()
|
||||
for _, path := range paths {
|
||||
content, err := ldr.Load(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m, err := makeNameToApiMap(content)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "unable to parse open API definition from '%s'", path)
|
||||
}
|
||||
otherTc, err := makeConfigFromApiMap(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tc, err = tc.Merge(otherTc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return tc, nil
|
||||
}
|
||||
|
||||
func makeNameToApiMap(content []byte) (result nameToApiMap, err error) {
|
||||
if content[0] == '{' {
|
||||
err = json.Unmarshal(content, &result)
|
||||
} else {
|
||||
err = yaml.Unmarshal(content, &result)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func makeConfigFromApiMap(m nameToApiMap) (*builtinconfig.TransformerConfig, error) {
|
||||
result := builtinconfig.MakeEmptyConfig()
|
||||
for name, api := range m {
|
||||
if !looksLikeAk8sType(api.Schema.SchemaProps.Properties) {
|
||||
continue
|
||||
}
|
||||
tc := builtinconfig.MakeEmptyConfig()
|
||||
err := loadCrdIntoConfig(
|
||||
tc, makeGvkFromTypeName(name), m, name, []string{})
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
result, err = result.Merge(tc)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// TODO: Get Group and Version for CRD from the
|
||||
// openAPI definition once
|
||||
// "x-kubernetes-group-version-kind" is available in CRD
|
||||
func makeGvkFromTypeName(n string) resid.Gvk {
|
||||
names := strings.Split(n, filesys.SelfDir)
|
||||
kind := names[len(names)-1]
|
||||
return resid.Gvk{Kind: kind}
|
||||
}
|
||||
|
||||
func looksLikeAk8sType(properties myProperties) bool {
|
||||
_, ok := properties["kind"]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
_, ok = properties["apiVersion"]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
_, ok = properties["metadata"]
|
||||
return ok
|
||||
}
|
||||
|
||||
const (
|
||||
// "x-kubernetes-annotation": ""
|
||||
xAnnotation = "x-kubernetes-annotation"
|
||||
|
||||
// "x-kubernetes-label-selector": ""
|
||||
xLabelSelector = "x-kubernetes-label-selector"
|
||||
|
||||
// "x-kubernetes-identity": ""
|
||||
xIdentity = "x-kubernetes-identity"
|
||||
|
||||
// "x-kubernetes-object-ref-api-version": <apiVersion name>
|
||||
xVersion = "x-kubernetes-object-ref-api-version"
|
||||
|
||||
// "x-kubernetes-object-ref-kind": <kind name>
|
||||
xKind = "x-kubernetes-object-ref-kind"
|
||||
|
||||
// "x-kubernetes-object-ref-name-key": "name"
|
||||
// default is "name"
|
||||
xNameKey = "x-kubernetes-object-ref-name-key"
|
||||
)
|
||||
|
||||
// loadCrdIntoConfig loads a CRD spec into a TransformerConfig
|
||||
func loadCrdIntoConfig(
|
||||
theConfig *builtinconfig.TransformerConfig, theGvk resid.Gvk, theMap nameToApiMap,
|
||||
typeName string, path []string) (err error) {
|
||||
api, ok := theMap[typeName]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
for propName, property := range api.Schema.SchemaProps.Properties {
|
||||
_, annotate := property.Extensions.GetString(xAnnotation)
|
||||
if annotate {
|
||||
err = theConfig.AddAnnotationFieldSpec(
|
||||
makeFs(theGvk, append(path, propName)))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
_, label := property.Extensions.GetString(xLabelSelector)
|
||||
if label {
|
||||
err = theConfig.AddLabelFieldSpec(
|
||||
makeFs(theGvk, append(path, propName)))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
_, identity := property.Extensions.GetString(xIdentity)
|
||||
if identity {
|
||||
err = theConfig.AddPrefixFieldSpec(
|
||||
makeFs(theGvk, append(path, propName)))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
version, ok := property.Extensions.GetString(xVersion)
|
||||
if ok {
|
||||
kind, ok := property.Extensions.GetString(xKind)
|
||||
if ok {
|
||||
nameKey, ok := property.Extensions.GetString(xNameKey)
|
||||
if !ok {
|
||||
nameKey = "name"
|
||||
}
|
||||
err = theConfig.AddNamereferenceFieldSpec(
|
||||
builtinconfig.NameBackReferences{
|
||||
Gvk: resid.Gvk{Kind: kind, Version: version},
|
||||
Referrers: []types.FieldSpec{
|
||||
makeFs(theGvk, append(path, propName, nameKey))},
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
if property.Ref.GetURL() != nil {
|
||||
err = loadCrdIntoConfig(
|
||||
theConfig, theGvk, theMap,
|
||||
property.Ref.String(), append(path, propName))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeFs(in resid.Gvk, path []string) types.FieldSpec {
|
||||
return types.FieldSpec{
|
||||
CreateIfNotPresent: false,
|
||||
Gvk: in,
|
||||
Path: strings.Join(path, "/"),
|
||||
}
|
||||
}
|
||||
176
api/internal/accumulator/loadconfigfromcrds_test.go
Normal file
176
api/internal/accumulator/loadconfigfromcrds_test.go
Normal file
@@ -0,0 +1,176 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package accumulator_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"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/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.
|
||||
//
|
||||
// Bee is boring, it's spec has no dependencies.
|
||||
//
|
||||
// MyKind, however, has a spec that contains
|
||||
// a Bee and a (k8s native) Secret.
|
||||
const (
|
||||
crdContent = `
|
||||
{
|
||||
"github.com/example/pkg/apis/jingfang/v1beta1.Bee": {
|
||||
"Schema": {
|
||||
"description": "Bee",
|
||||
"properties": {
|
||||
"apiVersion": {
|
||||
"description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert
|
||||
recognized schemas to the latest internal value, and may reject unrecognized values.
|
||||
More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources",
|
||||
"type": "string"
|
||||
},
|
||||
"kind": {
|
||||
"description": "Kind is a string value representing the REST resource this object represents. Servers may infer
|
||||
this from the endpoint the client submits requests to. Cannot be updated. In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds",
|
||||
"type": "string"
|
||||
},
|
||||
"metadata": {
|
||||
"$ref": "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"
|
||||
},
|
||||
"spec": {
|
||||
"$ref": "github.com/example/pkg/apis/jingfang/v1beta1.BeeSpec"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "github.com/example/pkg/apis/jingfang/v1beta1.BeeStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Dependencies": [
|
||||
"github.com/example/pkg/apis/jingfang/v1beta1.BeeSpec",
|
||||
"github.com/example/pkg/apis/jingfang/v1beta1.BeeStatus",
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"
|
||||
]
|
||||
},
|
||||
"github.com/example/pkg/apis/jingfang/v1beta1.BeeSpec": {
|
||||
"Schema": {
|
||||
"description": "BeeSpec defines the desired state of Bee"
|
||||
},
|
||||
"Dependencies": []
|
||||
},
|
||||
"github.com/example/pkg/apis/jingfang/v1beta1.BeeStatus": {
|
||||
"Schema": {
|
||||
"description": "BeeStatus defines the observed state of Bee"
|
||||
},
|
||||
"Dependencies": []
|
||||
},
|
||||
"github.com/example/pkg/apis/jingfang/v1beta1.MyKind": {
|
||||
"Schema": {
|
||||
"description": "MyKind",
|
||||
"properties": {
|
||||
"apiVersion": {
|
||||
"description": "APIVersion defines the versioned schema of this representation of an object.
|
||||
Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values.
|
||||
More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources",
|
||||
"type": "string"
|
||||
},
|
||||
"kind": {
|
||||
"description": "Kind is a string value representing the REST resource this object represents.
|
||||
Servers may infer this from the endpoint the client submits requests to. Cannot be updated.
|
||||
In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds",
|
||||
"type": "string"
|
||||
},
|
||||
"metadata": {
|
||||
"$ref": "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"
|
||||
},
|
||||
"spec": {
|
||||
"$ref": "github.com/example/pkg/apis/jingfang/v1beta1.MyKindSpec"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "github.com/example/pkg/apis/jingfang/v1beta1.MyKindStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Dependencies": [
|
||||
"github.com/example/pkg/apis/jingfang/v1beta1.MyKindSpec",
|
||||
"github.com/example/pkg/apis/jingfang/v1beta1.MyKindStatus",
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"
|
||||
]
|
||||
},
|
||||
"github.com/example/pkg/apis/jingfang/v1beta1.MyKindSpec": {
|
||||
"Schema": {
|
||||
"description": "MyKindSpec defines the desired state of MyKind",
|
||||
"properties": {
|
||||
"beeRef": {
|
||||
"x-kubernetes-object-ref-api-version": "v1beta1",
|
||||
"x-kubernetes-object-ref-kind": "Bee",
|
||||
"$ref": "github.com/example/pkg/apis/jingfang/v1beta1.Bee"
|
||||
},
|
||||
"secretRef": {
|
||||
"description": "If defined, we use this secret for configuring the MYSQL_ROOT_PASSWORD
|
||||
If it is not set we generate a secret dynamically",
|
||||
"x-kubernetes-object-ref-api-version": "v1",
|
||||
"x-kubernetes-object-ref-kind": "Secret",
|
||||
"$ref": "k8s.io/api/core/v1.LocalObjectReference"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Dependencies": [
|
||||
"github.com/example/pkg/apis/jingfang/v1beta1.Bee",
|
||||
"k8s.io/api/core/v1.LocalObjectReference"
|
||||
]
|
||||
},
|
||||
"github.com/example/pkg/apis/jingfang/v1beta1.MyKindStatus": {
|
||||
"Schema": {
|
||||
"description": "MyKindStatus defines the observed state of MyKind"
|
||||
},
|
||||
"Dependencies": []
|
||||
}
|
||||
}
|
||||
`
|
||||
)
|
||||
|
||||
func TestLoadCRDs(t *testing.T) {
|
||||
nbrs := []builtinconfig.NameBackReferences{
|
||||
{
|
||||
Gvk: resid.Gvk{Kind: "Secret", Version: "v1"},
|
||||
Referrers: []types.FieldSpec{
|
||||
{
|
||||
Gvk: resid.Gvk{Kind: "MyKind"},
|
||||
Path: "spec/secretRef/name",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Gvk: resid.Gvk{Kind: "Bee", Version: "v1beta1"},
|
||||
Referrers: []types.FieldSpec{
|
||||
{
|
||||
Gvk: resid.Gvk{Kind: "MyKind"},
|
||||
Path: "spec/beeRef/name",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expectedTc := &builtinconfig.TransformerConfig{
|
||||
NameReference: nbrs,
|
||||
}
|
||||
|
||||
fSys := filesys.MakeFsInMemory()
|
||||
err := fSys.WriteFile("/testpath/crd.json", []byte(crdContent))
|
||||
require.NoError(t, err)
|
||||
ldr, err := loader.NewLoader(loader.RestrictionRootOnly, "/testpath", fSys)
|
||||
require.NoError(t, err)
|
||||
|
||||
actualTc, err := LoadConfigFromCRDs(ldr, []string{"crd.json"})
|
||||
require.NoError(t, err)
|
||||
if !reflect.DeepEqual(actualTc, expectedTc) {
|
||||
t.Fatalf("expected\n %v\n but got\n %v\n", expectedTc, actualTc)
|
||||
}
|
||||
}
|
||||
165
api/internal/accumulator/namereferencetransformer.go
Normal file
165
api/internal/accumulator/namereferencetransformer.go
Normal file
@@ -0,0 +1,165 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package accumulator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/filters/nameref"
|
||||
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/resource"
|
||||
"sigs.k8s.io/kustomize/kyaml/resid"
|
||||
)
|
||||
|
||||
type nameReferenceTransformer struct {
|
||||
backRefs []builtinconfig.NameBackReferences
|
||||
}
|
||||
|
||||
const doDebug = false
|
||||
|
||||
var _ resmap.Transformer = &nameReferenceTransformer{}
|
||||
|
||||
type filterMap map[*resource.Resource][]nameref.Filter
|
||||
|
||||
// newNameReferenceTransformer constructs a nameReferenceTransformer
|
||||
// with a given slice of NameBackReferences.
|
||||
func newNameReferenceTransformer(
|
||||
br []builtinconfig.NameBackReferences) resmap.Transformer {
|
||||
if br == nil {
|
||||
log.Fatal("backrefs not expected to be nil")
|
||||
}
|
||||
return &nameReferenceTransformer{backRefs: br}
|
||||
}
|
||||
|
||||
// Transform updates name references in resource A that
|
||||
// refer to resource B, given that B's name may have
|
||||
// changed.
|
||||
//
|
||||
// For example, a HorizontalPodAutoscaler (HPA)
|
||||
// necessarily refers to a Deployment, the thing that
|
||||
// an HPA scales. In this case:
|
||||
//
|
||||
// - 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, err := m.SubsetThatCouldBeReferencedByResource(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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:
|
||||
// - kind: HorizontalPodAutoscaler
|
||||
// path: spec/scaleTargetRef/name
|
||||
//
|
||||
// This entry says that an HPA, via its
|
||||
// 'spec/scaleTargetRef/name' field, may refer to a
|
||||
// Deployment.
|
||||
//
|
||||
// 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 fMap
|
||||
}
|
||||
|
||||
// TODO: check res for field existence here to avoid extra work.
|
||||
// res.GetFieldValue, which uses yaml.Lookup under the hood, doesn't know
|
||||
// how to parse fieldspec-style paths that make no distinction
|
||||
// between maps and sequences. This means it cannot lookup commonly
|
||||
// used "indeterminate" paths like
|
||||
// spec/containers/env/valueFrom/configMapKeyRef/name
|
||||
// ('containers' is a list, not a map).
|
||||
// However, the fieldspec filter does know how to handle this;
|
||||
// extract that code and call it here?
|
||||
func resHasField(res *resource.Resource, path string) bool {
|
||||
return true
|
||||
// fld := strings.Join(utils.PathSplitter(path), ".")
|
||||
// _, e := res.GetFieldValue(fld)
|
||||
// return e == nil
|
||||
}
|
||||
1063
api/internal/accumulator/namereferencetransformer_test.go
Normal file
1063
api/internal/accumulator/namereferencetransformer_test.go
Normal file
File diff suppressed because it is too large
Load Diff
57
api/internal/accumulator/refvartransformer.go
Normal file
57
api/internal/accumulator/refvartransformer.go
Normal file
@@ -0,0 +1,57 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package accumulator
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/api/filters/refvar"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
)
|
||||
|
||||
type refVarTransformer struct {
|
||||
varMap map[string]interface{}
|
||||
replacementCounts map[string]int
|
||||
fieldSpecs []types.FieldSpec
|
||||
}
|
||||
|
||||
// newRefVarTransformer returns a new refVarTransformer
|
||||
// that replaces $(VAR) style variables with values.
|
||||
// The fieldSpecs are the places to look for occurrences of $(VAR).
|
||||
func newRefVarTransformer(
|
||||
varMap map[string]interface{}, fs []types.FieldSpec) *refVarTransformer {
|
||||
return &refVarTransformer{
|
||||
varMap: varMap,
|
||||
fieldSpecs: fs,
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if _, ok := rv.replacementCounts[k]; !ok {
|
||||
unused = append(unused, k)
|
||||
}
|
||||
}
|
||||
return unused
|
||||
}
|
||||
|
||||
// Transform replaces $(VAR) style variables with values.
|
||||
func (rv *refVarTransformer) Transform(m resmap.ResMap) error {
|
||||
rv.replacementCounts = make(map[string]int)
|
||||
mf := refvar.MakePrimitiveReplacer(rv.replacementCounts, rv.varMap)
|
||||
for _, res := range m.Resources() {
|
||||
for _, fieldSpec := range rv.fieldSpecs {
|
||||
err := res.ApplyFilter(refvar.Filter{
|
||||
MappingFunc: mf,
|
||||
FieldSpec: fieldSpec,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
189
api/internal/accumulator/refvartransformer_test.go
Normal file
189
api/internal/accumulator/refvartransformer_test.go
Normal file
@@ -0,0 +1,189 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package accumulator
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
resmaptest_test "sigs.k8s.io/kustomize/api/testutils/resmaptest"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/resid"
|
||||
)
|
||||
|
||||
func TestRefVarTransformer(t *testing.T) {
|
||||
type given struct {
|
||||
varMap map[string]interface{}
|
||||
fs []types.FieldSpec
|
||||
res resmap.ResMap
|
||||
}
|
||||
type expected struct {
|
||||
res resmap.ResMap
|
||||
unused []string
|
||||
}
|
||||
testCases := map[string]struct {
|
||||
given given
|
||||
expected expected
|
||||
errMessage string
|
||||
}{
|
||||
"var replacement in map[string]": {
|
||||
given: given{
|
||||
varMap: map[string]interface{}{
|
||||
"FOO": "replacementForFoo",
|
||||
"BAR": "replacementForBar",
|
||||
"BAZ": int64(5),
|
||||
"BOO": true,
|
||||
},
|
||||
fs: []types.FieldSpec{
|
||||
{Gvk: resid.Gvk{Version: "v1", Kind: "ConfigMap"}, Path: "data/map"},
|
||||
{Gvk: resid.Gvk{Version: "v1", Kind: "ConfigMap"}, Path: "data/slice"},
|
||||
{Gvk: resid.Gvk{Version: "v1", Kind: "ConfigMap"}, Path: "data/interface"},
|
||||
{Gvk: resid.Gvk{Version: "v1", Kind: "ConfigMap"}, Path: "data/num"},
|
||||
},
|
||||
res: resmaptest_test.NewRmBuilderDefault(t).
|
||||
Add(map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "cm1",
|
||||
},
|
||||
"data": map[string]interface{}{
|
||||
"map": map[string]interface{}{
|
||||
"item1": "$(FOO)",
|
||||
"item2": "bla",
|
||||
"item3": "$(BAZ)",
|
||||
"item4": "$(BAZ)+$(BAZ)",
|
||||
"item5": "$(BOO)",
|
||||
"item6": "if $(BOO)",
|
||||
"item7": int64(2019),
|
||||
},
|
||||
"slice": []interface{}{
|
||||
"$(FOO)",
|
||||
"bla",
|
||||
"$(BAZ)",
|
||||
"$(BAZ)+$(BAZ)",
|
||||
"$(BOO)",
|
||||
"if $(BOO)",
|
||||
},
|
||||
"interface": "$(FOO)",
|
||||
"num": int64(2019),
|
||||
}}).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{}{
|
||||
"map": map[string]interface{}{
|
||||
"item1": "replacementForFoo",
|
||||
"item2": "bla",
|
||||
"item3": int64(5),
|
||||
"item4": "5+5",
|
||||
"item5": true,
|
||||
"item6": "if true",
|
||||
"item7": int64(2019),
|
||||
},
|
||||
"slice": []interface{}{
|
||||
"replacementForFoo",
|
||||
"bla",
|
||||
int64(5),
|
||||
"5+5",
|
||||
true,
|
||||
"if true",
|
||||
},
|
||||
"interface": "replacementForFoo",
|
||||
"num": int64(2019),
|
||||
}}).ResMap(),
|
||||
unused: []string{"BAR"},
|
||||
},
|
||||
},
|
||||
"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.NewRmBuilderDefault(t).
|
||||
Add(map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "cm1",
|
||||
},
|
||||
"data": map[string]interface{}{
|
||||
"slice": []interface{}{5}, // noticeably *not* a []string
|
||||
}}).ResMap(),
|
||||
},
|
||||
errMessage: `considering field 'data/slice' of object ConfigMap.v1.[noGrp]/cm1.[noNs]: 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 tn, tc := range testCases {
|
||||
t.Run(tn, func(t *testing.T) {
|
||||
tr := newRefVarTransformer(tc.given.varMap, tc.given.fs)
|
||||
err := tr.Transform(tc.given.res)
|
||||
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:
|
||||
ACTUAL: %v
|
||||
EXPECTED: %v`,
|
||||
err.Error(), tc.errMessage)
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
a, e := tc.given.res, tc.expected.res
|
||||
if !reflect.DeepEqual(a, e) {
|
||||
err = e.ErrorIfNotEqualLists(a)
|
||||
t.Fatalf(`actual doesn't match expected:
|
||||
ACTUAL:
|
||||
%v
|
||||
EXPECTED:
|
||||
%v
|
||||
ERR: %v`,
|
||||
a, e, err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user