mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-29 01:30:51 +00:00
Compare commits
2137 Commits
api/v0.5.0
...
api/v0.11.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d46bb0a453 | ||
|
|
939545a007 | ||
|
|
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 | ||
|
|
94095a63ff | ||
|
|
c2ccfd72ad | ||
|
|
6d324d70c4 | ||
|
|
e32aa8ddb2 | ||
|
|
b92de5cb80 | ||
|
|
ecfa732a04 |
8
.dockerignore
Normal file
8
.dockerignore
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
.github
|
||||||
|
docs
|
||||||
|
examples
|
||||||
|
functions
|
||||||
|
hack
|
||||||
|
site
|
||||||
|
travis
|
||||||
|
*.md
|
||||||
68
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
68
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: ""
|
||||||
|
labels:
|
||||||
|
- kind/bug
|
||||||
|
assignees: ""
|
||||||
|
---
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Please read this page: https://kubernetes-sigs.github.io/kustomize/contributing/bugs/ before
|
||||||
|
filing a bug
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- Feel free to skip the sections if they are not applicable. -->
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
|
||||||
|
<!-- A clear and concise description of what the bug is. -->
|
||||||
|
|
||||||
|
**Files that can reproduce the issue**
|
||||||
|
|
||||||
|
<!--
|
||||||
|
We cannot figure out or fix the issue if we don't know how to reproduce. Please
|
||||||
|
provide a minimum set of files that can reproduce the issue. You can paste the
|
||||||
|
file contents here or provide a link to a tarball or git repo.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
kustomization.yaml
|
||||||
|
|
||||||
|
```
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
resources.yaml
|
||||||
|
|
||||||
|
```
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Deployment
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
...
|
||||||
|
-->
|
||||||
|
|
||||||
|
**Expected output**
|
||||||
|
|
||||||
|
<!-- What's the expected output? -->
|
||||||
|
|
||||||
|
**Actual output**
|
||||||
|
|
||||||
|
<!-- What's the actual output? -->
|
||||||
|
|
||||||
|
**Kustomize version**
|
||||||
|
|
||||||
|
<!-- Please use the latest version when it's possible. -->
|
||||||
|
|
||||||
|
**Platform**
|
||||||
|
|
||||||
|
<!-- Linux/macOS/Windows? -->
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
|
||||||
|
<!-- Add any other context about the problem here. -->
|
||||||
1
.github/ISSUE_TEMPLATE/config.yaml
vendored
Normal file
1
.github/ISSUE_TEMPLATE/config.yaml
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
blank_issues_enabled: true
|
||||||
26
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
26
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: ""
|
||||||
|
labels:
|
||||||
|
- kind/feature
|
||||||
|
assignees: ""
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- Feel free to skip the sections if they are not applicable. -->
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
|
||||||
|
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
|
||||||
|
<!-- A clear and concise description of what you want to happen. -->
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
|
||||||
|
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
|
||||||
|
<!-- Add any other context or screenshots about the feature request here. -->
|
||||||
9
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
9
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
name: Question
|
||||||
|
about: Ask a question about the kustomize
|
||||||
|
title: "[Question]"
|
||||||
|
labels: ""
|
||||||
|
assignees: ""
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- Please describe your question here -->
|
||||||
23
.github/workflows/go.yml
vendored
23
.github/workflows/go.yml
vendored
@@ -16,14 +16,14 @@ jobs:
|
|||||||
- name: Set up Go 1.x
|
- name: Set up Go 1.x
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: ^1.13
|
go-version: ^1.16
|
||||||
id: go
|
id: go
|
||||||
|
|
||||||
- name: Check out code into the Go module directory
|
- name: Check out code into the Go module directory
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Lint
|
- name: Lint
|
||||||
run: ./travis/kyaml-pre-commit.sh
|
run: ./hack/kyaml-pre-commit.sh
|
||||||
env:
|
env:
|
||||||
KUSTOMIZE_DOCKER_E2E: false # don't need to do e2e tests for linting
|
KUSTOMIZE_DOCKER_E2E: false # don't need to do e2e tests for linting
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ jobs:
|
|||||||
- name: Set up Go 1.x
|
- name: Set up Go 1.x
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: ^1.13
|
go-version: ^1.16
|
||||||
id: go
|
id: go
|
||||||
|
|
||||||
- name: Check out code into the Go module directory
|
- name: Check out code into the Go module directory
|
||||||
@@ -45,6 +45,10 @@ jobs:
|
|||||||
run: go test -cover ./...
|
run: go test -cover ./...
|
||||||
working-directory: ./kyaml
|
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
|
- name: Test cmd/config
|
||||||
run: go test -cover ./...
|
run: go test -cover ./...
|
||||||
working-directory: ./cmd/config
|
working-directory: ./cmd/config
|
||||||
@@ -59,7 +63,7 @@ jobs:
|
|||||||
- name: Set up Go 1.x
|
- name: Set up Go 1.x
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: ^1.13
|
go-version: ^1.16
|
||||||
id: go
|
id: go
|
||||||
|
|
||||||
- name: Check out code into the Go module directory
|
- name: Check out code into the Go module directory
|
||||||
@@ -69,6 +73,10 @@ jobs:
|
|||||||
run: go test -cover ./...
|
run: go test -cover ./...
|
||||||
working-directory: ./kyaml
|
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
|
- name: Test cmd/config
|
||||||
run: go test -cover ./...
|
run: go test -cover ./...
|
||||||
working-directory: ./cmd/config
|
working-directory: ./cmd/config
|
||||||
@@ -83,7 +91,7 @@ jobs:
|
|||||||
- name: Set up Go 1.x
|
- name: Set up Go 1.x
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: ^1.13
|
go-version: ^1.16
|
||||||
id: go
|
id: go
|
||||||
|
|
||||||
- name: Check out code into the Go module directory
|
- name: Check out code into the Go module directory
|
||||||
@@ -93,6 +101,11 @@ jobs:
|
|||||||
run: go test -cover ./...
|
run: go test -cover ./...
|
||||||
working-directory: ./kyaml
|
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
|
- name: Test cmd/config
|
||||||
run: go test -cover ./...
|
run: go test -cover ./...
|
||||||
working-directory: ./cmd/config
|
working-directory: ./cmd/config
|
||||||
|
|||||||
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,6 +1,25 @@
|
|||||||
|
[SIG-CLI]: https://github.com/kubernetes/community/tree/master/sig-cli
|
||||||
|
[Slack channel]: https://kubernetes.slack.com/messages/kustomize
|
||||||
|
[Mailing list]: https://groups.google.com/forum/#!forum/kubernetes-sig-cli
|
||||||
|
|
||||||
|
[OWNERS file spec]: https://github.com/kubernetes/community/blob/master/contributors/guide/owners.md
|
||||||
|
[Kustomize OWNERS_ALIASES]: https://github.com/kubernetes-sigs/kustomize/blob/8049f7b1af52e8a7ec26faf6cf714f560d0043c5/OWNERS_ALIASES
|
||||||
|
[SIG-CLI Teams]: https://github.com/kubernetes/org/blob/main/config/kubernetes-sigs/sig-cli/teams.yaml
|
||||||
|
[Github permissions]: https://docs.github.com/en/organizations/managing-access-to-your-organizations-repositories/repository-permission-levels-for-an-organization#repository-access-for-each-permission-level
|
||||||
|
|
||||||
|
[Contributor License Agreement]: https://git.k8s.io/community/CLA.md
|
||||||
|
[Kubernetes Contributor Guide]: http://git.k8s.io/community/contributors/guide
|
||||||
|
[Contributor Cheat Sheet]: https://git.k8s.io/community/contributors/guide/contributor-cheatsheet/README.md
|
||||||
|
[CNCF Code of Conduct]: https://github.com/cncf/foundation/blob/master/code-of-conduct.md
|
||||||
|
[Kubernetes Community Membership]: https://github.com/kubernetes/community/blob/master/community-membership.md
|
||||||
|
|
||||||
|
[Contribution Guide]: https://kubectl.docs.kubernetes.io/contributing/kustomize/
|
||||||
|
[MacOS Dev Guide]: https://kubectl.docs.kubernetes.io/contributing/kustomize/mac/
|
||||||
|
[Windows Dev Guide]: https://kubectl.docs.kubernetes.io/contributing/kustomize/windows/
|
||||||
|
|
||||||
# Contributing Guidelines
|
# 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._
|
_As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities._
|
||||||
|
|
||||||
@@ -8,19 +27,45 @@ _As contributors and maintainers of this project, and in the interest of fosteri
|
|||||||
|
|
||||||
Dev guides:
|
Dev guides:
|
||||||
|
|
||||||
- [Mac](docs/macDevGuide.md)
|
- [Contribution Guide]
|
||||||
|
- [MacOS Dev Guide]
|
||||||
|
- [Windows Dev Guide]
|
||||||
|
|
||||||
We have full documentation on how to get started contributing here:
|
General resources for contributors:
|
||||||
|
|
||||||
- [Contributor License Agreement](https://git.k8s.io/community/CLA.md) Kubernetes projects require that you sign a Contributor License Agreement (CLA) before we can accept your pull requests
|
- [Contributor License Agreement] - 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)
|
- [Kubernetes Contributor Guide] - Main contributor documentation.
|
||||||
- [Contributor Cheat Sheet](https://git.k8s.io/community/contributors/guide/contributor-cheatsheet/README.md) - Common resources for existing developers
|
- [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
|
## Mentorship
|
||||||
|
|
||||||
- [Mentoring Initiatives](https://git.k8s.io/community/mentoring) - We have a diverse set of mentorship programs available that are always looking for volunteers!
|
- [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
|
## Contact Information
|
||||||
|
|
||||||
- [Slack channel](https://kubernetes.slack.com/messages/sig-cli)
|
- [Slack channel]
|
||||||
- [Mailing list](https://groups.google.com/forum/#!forum/kubernetes-sig-cli)
|
- [Mailing list]
|
||||||
|
|||||||
187
Makefile
187
Makefile
@@ -3,77 +3,87 @@
|
|||||||
#
|
#
|
||||||
# Makefile for kustomize CLI and API.
|
# Makefile for kustomize CLI and API.
|
||||||
|
|
||||||
MYGOBIN := $(shell go env GOPATH)/bin
|
SHELL := /usr/bin/env bash
|
||||||
SHELL := /bin/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)
|
export PATH := $(MYGOBIN):$(PATH)
|
||||||
|
MODULES := '"cmd/config" "api/" "kustomize/" "kyaml/"'
|
||||||
|
LATEST_V4_RELEASE=v4.4.1
|
||||||
|
|
||||||
|
# Provide defaults for REPO_OWNER and REPO_NAME if not present.
|
||||||
|
# Typically these values would be provided by Prow.
|
||||||
|
ifndef REPO_OWNER
|
||||||
|
REPO_OWNER := "kubernetes-sigs"
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifndef REPO_NAME
|
||||||
|
REPO_NAME := "kustomize"
|
||||||
|
endif
|
||||||
|
|
||||||
.PHONY: all
|
.PHONY: all
|
||||||
all: verify-kustomize
|
all: install-tools verify-kustomize
|
||||||
|
|
||||||
.PHONY: verify-kustomize
|
.PHONY: verify-kustomize
|
||||||
verify-kustomize: \
|
verify-kustomize: \
|
||||||
lint-kustomize \
|
lint-kustomize \
|
||||||
test-unit-kustomize-all \
|
test-unit-kustomize-all \
|
||||||
test-examples-kustomize-against-HEAD \
|
test-examples-kustomize-against-HEAD \
|
||||||
test-examples-kustomize-against-3.7.0
|
test-examples-kustomize-against-v4-release
|
||||||
|
|
||||||
# The following target referenced by a file in
|
# The following target referenced by a file in
|
||||||
# https://github.com/kubernetes/test-infra/tree/master/config/jobs/kubernetes-sigs/kustomize
|
# https://github.com/kubernetes/test-infra/tree/master/config/jobs/kubernetes-sigs/kustomize
|
||||||
.PHONY: prow-presubmit-check
|
.PHONY: prow-presubmit-check
|
||||||
prow-presubmit-check: \
|
prow-presubmit-check: \
|
||||||
|
install-tools \
|
||||||
lint-kustomize \
|
lint-kustomize \
|
||||||
test-unit-kustomize-all \
|
test-unit-kustomize-all \
|
||||||
test-examples-kustomize-against-HEAD \
|
|
||||||
test-examples-kustomize-against-3.7.0 \
|
|
||||||
test-unit-cmd-all \
|
test-unit-cmd-all \
|
||||||
test-go-mod
|
test-go-mod \
|
||||||
|
test-examples-kustomize-against-HEAD \
|
||||||
|
test-examples-kustomize-against-v4-release
|
||||||
|
|
||||||
.PHONY: verify-kustomize-e2e
|
.PHONY: verify-kustomize-e2e
|
||||||
verify-kustomize-e2e: test-examples-e2e-kustomize
|
verify-kustomize-e2e: test-examples-e2e-kustomize
|
||||||
|
|
||||||
# Other builds in this repo might want a different linter version.
|
# Other builds in this repo might want a different linter version.
|
||||||
# Without one Makefile to rule them all, the different makes
|
# Without one Makefile to rule them all, the different makes
|
||||||
# cannot assume that golanci-lint is at the version they want
|
# cannot assume that golanci-lint is at the version they want.
|
||||||
# since everything uses the same implicit GOPATH.
|
# This installs what kustomize wants to use.
|
||||||
# This installs in a temp dir to avoid overwriting someone else's
|
|
||||||
# linter, then installs in MYGOBIN with a new name.
|
|
||||||
# Version pinned by hack/go.mod
|
|
||||||
$(MYGOBIN)/golangci-lint-kustomize:
|
$(MYGOBIN)/golangci-lint-kustomize:
|
||||||
( \
|
rm -f $(CURDIR)/hack/golangci-lint
|
||||||
set -e; \
|
GOBIN=$(CURDIR)/hack go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.23.8
|
||||||
cd hack; \
|
mv $(CURDIR)/hack/golangci-lint $(MYGOBIN)/golangci-lint-kustomize
|
||||||
GO111MODULE=on go build -tags=tools -o $(MYGOBIN)/golangci-lint-kustomize github.com/golangci/golangci-lint/cmd/golangci-lint; \
|
|
||||||
)
|
|
||||||
|
|
||||||
# Version pinned by api/go.mod
|
|
||||||
$(MYGOBIN)/mdrip:
|
$(MYGOBIN)/mdrip:
|
||||||
cd api; \
|
go install github.com/monopole/mdrip@v1.0.2
|
||||||
go install github.com/monopole/mdrip
|
|
||||||
|
|
||||||
# Version pinned by api/go.mod
|
|
||||||
$(MYGOBIN)/stringer:
|
$(MYGOBIN)/stringer:
|
||||||
cd api; \
|
go get golang.org/x/tools/cmd/stringer
|
||||||
go install golang.org/x/tools/cmd/stringer
|
|
||||||
|
|
||||||
# Version pinned by api/go.mod
|
|
||||||
$(MYGOBIN)/goimports:
|
$(MYGOBIN)/goimports:
|
||||||
cd api; \
|
go get golang.org/x/tools/cmd/goimports
|
||||||
go install golang.org/x/tools/cmd/goimports
|
|
||||||
|
|
||||||
# Install resource from whatever is checked out.
|
# Build from local source.
|
||||||
$(MYGOBIN)/resource:
|
$(MYGOBIN)/gorepomod:
|
||||||
cd cmd/resource; \
|
cd cmd/gorepomod; \
|
||||||
go install .
|
go install .
|
||||||
|
|
||||||
# To pin pluginator, use this recipe instead:
|
# Build from local source.
|
||||||
# cd api;
|
$(MYGOBIN)/k8scopy:
|
||||||
# go install sigs.k8s.io/kustomize/pluginator/v2
|
cd cmd/k8scopy; \
|
||||||
|
go install .
|
||||||
|
|
||||||
|
# Build from local source.
|
||||||
$(MYGOBIN)/pluginator:
|
$(MYGOBIN)/pluginator:
|
||||||
cd pluginator; \
|
cd cmd/pluginator; \
|
||||||
go install .
|
go install .
|
||||||
|
|
||||||
# Install kustomize from whatever is checked out.
|
# Build from local source.
|
||||||
$(MYGOBIN)/kustomize:
|
$(MYGOBIN)/kustomize: build-kustomize-api
|
||||||
cd kustomize; \
|
cd kustomize; \
|
||||||
go install .
|
go install .
|
||||||
|
|
||||||
@@ -81,6 +91,9 @@ $(MYGOBIN)/kustomize:
|
|||||||
install-tools: \
|
install-tools: \
|
||||||
$(MYGOBIN)/goimports \
|
$(MYGOBIN)/goimports \
|
||||||
$(MYGOBIN)/golangci-lint-kustomize \
|
$(MYGOBIN)/golangci-lint-kustomize \
|
||||||
|
$(MYGOBIN)/gorepomod \
|
||||||
|
$(MYGOBIN)/helmV3 \
|
||||||
|
$(MYGOBIN)/k8scopy \
|
||||||
$(MYGOBIN)/mdrip \
|
$(MYGOBIN)/mdrip \
|
||||||
$(MYGOBIN)/pluginator \
|
$(MYGOBIN)/pluginator \
|
||||||
$(MYGOBIN)/stringer
|
$(MYGOBIN)/stringer
|
||||||
@@ -107,13 +120,14 @@ install-tools: \
|
|||||||
# module (it's linked into the api).
|
# module (it's linked into the api).
|
||||||
|
|
||||||
# Where all generated builtin plugin code should go.
|
# Where all generated builtin plugin code should go.
|
||||||
pGen=api/builtins
|
pGen=api/internal/builtins
|
||||||
# Where the builtin Go plugin modules live.
|
# Where the builtin Go plugin modules live.
|
||||||
pSrc=plugin/builtin
|
pSrc=plugin/builtin
|
||||||
|
|
||||||
_builtinplugins = \
|
_builtinplugins = \
|
||||||
AnnotationsTransformer.go \
|
AnnotationsTransformer.go \
|
||||||
ConfigMapGenerator.go \
|
ConfigMapGenerator.go \
|
||||||
|
IAMPolicyGenerator.go \
|
||||||
HashTransformer.go \
|
HashTransformer.go \
|
||||||
ImageTagTransformer.go \
|
ImageTagTransformer.go \
|
||||||
LabelTransformer.go \
|
LabelTransformer.go \
|
||||||
@@ -122,10 +136,13 @@ _builtinplugins = \
|
|||||||
PatchJson6902Transformer.go \
|
PatchJson6902Transformer.go \
|
||||||
PatchStrategicMergeTransformer.go \
|
PatchStrategicMergeTransformer.go \
|
||||||
PatchTransformer.go \
|
PatchTransformer.go \
|
||||||
PrefixSuffixTransformer.go \
|
PrefixTransformer.go \
|
||||||
|
SuffixTransformer.go \
|
||||||
|
ReplacementTransformer.go \
|
||||||
ReplicaCountTransformer.go \
|
ReplicaCountTransformer.go \
|
||||||
SecretGenerator.go \
|
SecretGenerator.go \
|
||||||
ValueAddTransformer.go
|
ValueAddTransformer.go \
|
||||||
|
HelmChartInflationGenerator.go
|
||||||
|
|
||||||
# Maintaining this explicit list of generated files, and
|
# Maintaining this explicit list of generated files, and
|
||||||
# adding it as a dependency to a few targets, to assure
|
# adding it as a dependency to a few targets, to assure
|
||||||
@@ -139,6 +156,7 @@ builtinplugins = $(patsubst %,$(pGen)/%,$(_builtinplugins))
|
|||||||
# that file, will be recreated.
|
# that file, will be recreated.
|
||||||
$(pGen)/AnnotationsTransformer.go: $(pSrc)/annotationstransformer/AnnotationsTransformer.go
|
$(pGen)/AnnotationsTransformer.go: $(pSrc)/annotationstransformer/AnnotationsTransformer.go
|
||||||
$(pGen)/ConfigMapGenerator.go: $(pSrc)/configmapgenerator/ConfigMapGenerator.go
|
$(pGen)/ConfigMapGenerator.go: $(pSrc)/configmapgenerator/ConfigMapGenerator.go
|
||||||
|
$(pGen)/GkeSaGenerator.go: $(pSrc)/gkesagenerator/GkeSaGenerator.go
|
||||||
$(pGen)/HashTransformer.go: $(pSrc)/hashtransformer/HashTransformer.go
|
$(pGen)/HashTransformer.go: $(pSrc)/hashtransformer/HashTransformer.go
|
||||||
$(pGen)/ImageTagTransformer.go: $(pSrc)/imagetagtransformer/ImageTagTransformer.go
|
$(pGen)/ImageTagTransformer.go: $(pSrc)/imagetagtransformer/ImageTagTransformer.go
|
||||||
$(pGen)/LabelTransformer.go: $(pSrc)/labeltransformer/LabelTransformer.go
|
$(pGen)/LabelTransformer.go: $(pSrc)/labeltransformer/LabelTransformer.go
|
||||||
@@ -147,10 +165,13 @@ $(pGen)/NamespaceTransformer.go: $(pSrc)/namespacetransformer/NamespaceTransform
|
|||||||
$(pGen)/PatchJson6902Transformer.go: $(pSrc)/patchjson6902transformer/PatchJson6902Transformer.go
|
$(pGen)/PatchJson6902Transformer.go: $(pSrc)/patchjson6902transformer/PatchJson6902Transformer.go
|
||||||
$(pGen)/PatchStrategicMergeTransformer.go: $(pSrc)/patchstrategicmergetransformer/PatchStrategicMergeTransformer.go
|
$(pGen)/PatchStrategicMergeTransformer.go: $(pSrc)/patchstrategicmergetransformer/PatchStrategicMergeTransformer.go
|
||||||
$(pGen)/PatchTransformer.go: $(pSrc)/patchtransformer/PatchTransformer.go
|
$(pGen)/PatchTransformer.go: $(pSrc)/patchtransformer/PatchTransformer.go
|
||||||
$(pGen)/PrefixSuffixTransformer.go: $(pSrc)/prefixsuffixtransformer/PrefixSuffixTransformer.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)/ReplicaCountTransformer.go: $(pSrc)/replicacounttransformer/ReplicaCountTransformer.go
|
||||||
$(pGen)/SecretGenerator.go: $(pSrc)/secretgenerator/SecretGenerator.go
|
$(pGen)/SecretGenerator.go: $(pSrc)/secretgenerator/SecretGenerator.go
|
||||||
$(pGen)/ValueAddTransformer.go: $(pSrc)/valueaddtransformer/ValueAddTransformer.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.
|
# 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))))))))))))))))))))))))))
|
toLowerCase = $(subst A,a,$(subst B,b,$(subst C,c,$(subst D,d,$(subst E,e,$(subst F,f,$(subst G,g,$(subst H,h,$(subst I,i,$(subst J,j,$(subst K,k,$(subst L,l,$(subst M,m,$(subst N,n,$(subst O,o,$(subst P,p,$(subst Q,q,$(subst R,r,$(subst S,s,$(subst T,t,$(subst U,u,$(subst V,v,$(subst W,w,$(subst X,x,$(subst Y,y,$(subst Z,z,$1))))))))))))))))))))))))))
|
||||||
@@ -169,24 +190,27 @@ $(pGen)/%.go: $(MYGOBIN)/pluginator
|
|||||||
.PHONY: generate-kustomize-builtin-plugins
|
.PHONY: generate-kustomize-builtin-plugins
|
||||||
generate-kustomize-builtin-plugins: $(builtinplugins)
|
generate-kustomize-builtin-plugins: $(builtinplugins)
|
||||||
|
|
||||||
.PHONY: kustomize-external-go-plugin-build
|
.PHONY: build-kustomize-external-go-plugin
|
||||||
kustomize-external-go-plugin-build:
|
build-kustomize-external-go-plugin:
|
||||||
./hack/buildExternalGoPlugins.sh ./plugin
|
./hack/buildExternalGoPlugins.sh ./plugin
|
||||||
|
|
||||||
.PHONY: kustomize-external-go-plugin-clean
|
.PHONY: clean-kustomize-external-go-plugin
|
||||||
kustomize-external-go-plugin-clean:
|
clean-kustomize-external-go-plugin:
|
||||||
./hack/buildExternalGoPlugins.sh ./plugin clean
|
./hack/buildExternalGoPlugins.sh ./plugin clean
|
||||||
|
|
||||||
### End kustomize plugin rules.
|
### End kustomize plugin rules.
|
||||||
|
|
||||||
.PHONY: lint-kustomize
|
.PHONY: lint-kustomize
|
||||||
lint-kustomize: install-tools $(builtinplugins)
|
lint-kustomize: $(MYGOBIN)/golangci-lint-kustomize $(builtinplugins)
|
||||||
cd api; \
|
cd api; $(MYGOBIN)/golangci-lint-kustomize \
|
||||||
$(MYGOBIN)/golangci-lint-kustomize -c ../.golangci-kustomize.yml run ./...
|
-c ../.golangci-kustomize.yml \
|
||||||
cd kustomize; \
|
run ./...
|
||||||
$(MYGOBIN)/golangci-lint-kustomize -c ../.golangci-kustomize.yml run ./...
|
cd kustomize; $(MYGOBIN)/golangci-lint-kustomize \
|
||||||
cd pluginator; \
|
-c ../.golangci-kustomize.yml \
|
||||||
$(MYGOBIN)/golangci-lint-kustomize -c ../.golangci-kustomize.yml run ./...
|
run ./...
|
||||||
|
cd cmd/pluginator; $(MYGOBIN)/golangci-lint-kustomize \
|
||||||
|
-c ../../.golangci-kustomize.yml \
|
||||||
|
run ./...
|
||||||
|
|
||||||
# Used to add non-default compilation flags when experimenting with
|
# Used to add non-default compilation flags when experimenting with
|
||||||
# plugin-to-api compatibility checks.
|
# plugin-to-api compatibility checks.
|
||||||
@@ -194,9 +218,14 @@ lint-kustomize: install-tools $(builtinplugins)
|
|||||||
build-kustomize-api: $(builtinplugins)
|
build-kustomize-api: $(builtinplugins)
|
||||||
cd api; go build ./...
|
cd api; go build ./...
|
||||||
|
|
||||||
|
.PHONY: generate-kustomize-api
|
||||||
|
generate-kustomize-api: $(MYGOBIN)/k8scopy
|
||||||
|
cd api; go generate ./...
|
||||||
|
|
||||||
.PHONY: test-unit-kustomize-api
|
.PHONY: test-unit-kustomize-api
|
||||||
test-unit-kustomize-api: build-kustomize-api
|
test-unit-kustomize-api: build-kustomize-api
|
||||||
cd api; go test ./... -ldflags "-X sigs.k8s.io/kustomize/api/provenance.version=v444.333.222"
|
cd api; go test ./... -ldflags "-X sigs.k8s.io/kustomize/api/provenance.version=v444.333.222"
|
||||||
|
cd api/krusty; OPENAPI_TEST=true go test -run TestCustomOpenAPIFieldFromComponentWithOverlays
|
||||||
|
|
||||||
.PHONY: test-unit-kustomize-plugins
|
.PHONY: test-unit-kustomize-plugins
|
||||||
test-unit-kustomize-plugins:
|
test-unit-kustomize-plugins:
|
||||||
@@ -213,10 +242,10 @@ test-unit-kustomize-all: \
|
|||||||
test-unit-kustomize-plugins
|
test-unit-kustomize-plugins
|
||||||
|
|
||||||
test-unit-cmd-all:
|
test-unit-cmd-all:
|
||||||
./travis/kyaml-pre-commit.sh
|
./hack/kyaml-pre-commit.sh
|
||||||
|
|
||||||
test-go-mod:
|
test-go-mod:
|
||||||
./travis/check-go-mod.sh
|
./hack/check-go-mod.sh
|
||||||
|
|
||||||
.PHONY:
|
.PHONY:
|
||||||
test-examples-e2e-kustomize: $(MYGOBIN)/mdrip $(MYGOBIN)/kind
|
test-examples-e2e-kustomize: $(MYGOBIN)/mdrip $(MYGOBIN)/kind
|
||||||
@@ -233,17 +262,8 @@ test-examples-kustomize-against-HEAD: $(MYGOBIN)/kustomize $(MYGOBIN)/mdrip
|
|||||||
./hack/testExamplesAgainstKustomize.sh HEAD
|
./hack/testExamplesAgainstKustomize.sh HEAD
|
||||||
|
|
||||||
.PHONY:
|
.PHONY:
|
||||||
test-examples-kustomize-against-3.7.0: $(MYGOBIN)/mdrip
|
test-examples-kustomize-against-v4-release: $(MYGOBIN)/mdrip
|
||||||
( \
|
./hack/testExamplesAgainstKustomize.sh v4@$(LATEST_V4_RELEASE)
|
||||||
set -e; \
|
|
||||||
tag=v3.7.0; \
|
|
||||||
/bin/rm -f $(MYGOBIN)/kustomize; \
|
|
||||||
echo "Installing kustomize $$tag."; \
|
|
||||||
GO111MODULE=on go get sigs.k8s.io/kustomize/kustomize/v3@$${tag}; \
|
|
||||||
./hack/testExamplesAgainstKustomize.sh $$tag; \
|
|
||||||
echo "Reinstalling kustomize from HEAD."; \
|
|
||||||
cd kustomize; go install .; \
|
|
||||||
)
|
|
||||||
|
|
||||||
# linux only.
|
# linux only.
|
||||||
# This is for testing an example plugin that
|
# This is for testing an example plugin that
|
||||||
@@ -255,8 +275,8 @@ $(MYGOBIN)/kubeval:
|
|||||||
( \
|
( \
|
||||||
set -e; \
|
set -e; \
|
||||||
d=$(shell mktemp -d); cd $$d; \
|
d=$(shell mktemp -d); cd $$d; \
|
||||||
wget https://github.com/instrumenta/kubeval/releases/latest/download/kubeval-linux-amd64.tar.gz; \
|
wget https://github.com/instrumenta/kubeval/releases/latest/download/kubeval-$(GOOS)-$(GOARCH).tar.gz; \
|
||||||
tar xf kubeval-linux-amd64.tar.gz; \
|
tar xf kubeval-$(GOOS)-$(GOARCH).tar.gz; \
|
||||||
mv kubeval $(MYGOBIN); \
|
mv kubeval $(MYGOBIN); \
|
||||||
rm -rf $$d; \
|
rm -rf $$d; \
|
||||||
)
|
)
|
||||||
@@ -270,10 +290,10 @@ $(MYGOBIN)/helmV2:
|
|||||||
( \
|
( \
|
||||||
set -e; \
|
set -e; \
|
||||||
d=$(shell mktemp -d); cd $$d; \
|
d=$(shell mktemp -d); cd $$d; \
|
||||||
tgzFile=helm-v2.13.1-linux-amd64.tar.gz; \
|
tgzFile=helm-v2.13.1-$(GOOS)-$(GOARCH).tar.gz; \
|
||||||
wget https://storage.googleapis.com/kubernetes-helm/$$tgzFile; \
|
wget https://storage.googleapis.com/kubernetes-helm/$$tgzFile; \
|
||||||
tar -xvzf $$tgzFile; \
|
tar -xvzf $$tgzFile; \
|
||||||
mv linux-amd64/helm $(MYGOBIN)/helmV2; \
|
mv $(GOOS)-$(GOARCH)/helm $(MYGOBIN)/helmV2; \
|
||||||
rm -rf $$d \
|
rm -rf $$d \
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -283,34 +303,47 @@ $(MYGOBIN)/helmV3:
|
|||||||
( \
|
( \
|
||||||
set -e; \
|
set -e; \
|
||||||
d=$(shell mktemp -d); cd $$d; \
|
d=$(shell mktemp -d); cd $$d; \
|
||||||
tgzFile=helm-v3.2.0-rc.1-linux-amd64.tar.gz; \
|
tgzFile=helm-v3.6.3-$(GOOS)-$(GOARCH).tar.gz; \
|
||||||
wget https://get.helm.sh/$$tgzFile; \
|
wget https://get.helm.sh/$$tgzFile; \
|
||||||
tar -xvzf $$tgzFile; \
|
tar -xvzf $$tgzFile; \
|
||||||
mv linux-amd64/helm $(MYGOBIN)/helmV3; \
|
mv $(GOOS)-$(GOARCH)/helm $(MYGOBIN)/helmV3; \
|
||||||
rm -rf $$d \
|
rm -rf $$d \
|
||||||
)
|
)
|
||||||
|
|
||||||
# Default version of helm is v2 for the time being.
|
|
||||||
$(MYGOBIN)/helm: $(MYGOBIN)/helmV2
|
|
||||||
ln -s $(MYGOBIN)/helmV2 $(MYGOBIN)/helm
|
|
||||||
|
|
||||||
$(MYGOBIN)/kind:
|
$(MYGOBIN)/kind:
|
||||||
( \
|
( \
|
||||||
set -e; \
|
set -e; \
|
||||||
d=$(shell mktemp -d); cd $$d; \
|
d=$(shell mktemp -d); cd $$d; \
|
||||||
wget -O ./kind https://github.com/kubernetes-sigs/kind/releases/download/v0.7.0/kind-$(shell uname)-amd64; \
|
wget -O ./kind https://github.com/kubernetes-sigs/kind/releases/download/v0.7.0/kind-$(GOOS)-$(GOARCH); \
|
||||||
chmod +x ./kind; \
|
chmod +x ./kind; \
|
||||||
mv ./kind $(MYGOBIN); \
|
mv ./kind $(MYGOBIN); \
|
||||||
rm -rf $$d; \
|
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
|
.PHONY: clean
|
||||||
clean: kustomize-external-go-plugin-clean
|
clean: clean-kustomize-external-go-plugin
|
||||||
go clean --cache
|
go clean --cache
|
||||||
rm -f $(builtinplugins)
|
rm -f $(builtinplugins)
|
||||||
rm -f $(MYGOBIN)/pluginator
|
rm -f $(MYGOBIN)/goimports
|
||||||
rm -f $(MYGOBIN)/kustomize
|
|
||||||
rm -f $(MYGOBIN)/golangci-lint-kustomize
|
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.
|
# Nuke the site from orbit. It's the only way to be sure.
|
||||||
.PHONY: nuke
|
.PHONY: nuke
|
||||||
|
|||||||
6
OWNERS
6
OWNERS
@@ -1,4 +1,6 @@
|
|||||||
# See https://github.com/kubernetes/community/blob/master/community-membership.md
|
# See https://github.com/kubernetes/community/blob/master/community-membership.md
|
||||||
approvers:
|
approvers:
|
||||||
- kustomize-admins
|
- kustomize-approvers
|
||||||
- kustomize-maintainers
|
|
||||||
|
reviewers:
|
||||||
|
- kustomize-reviewers
|
||||||
|
|||||||
@@ -1,13 +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:
|
aliases:
|
||||||
kustomize-admins:
|
kustomize-owners:
|
||||||
- monopole
|
- knverey
|
||||||
- pwittrock
|
- natasha41575
|
||||||
kustomize-maintainers:
|
kustomize-approvers:
|
||||||
- droot
|
- knverey
|
||||||
- justinsb
|
- natasha41575
|
||||||
- liujingfang1
|
kustomize-reviewers:
|
||||||
|
- knverey
|
||||||
|
- natasha41575
|
||||||
|
- yuwenma
|
||||||
|
|
||||||
|
kyaml-approvers:
|
||||||
- mengqiy
|
- mengqiy
|
||||||
- monopole
|
|
||||||
- pwittrock
|
|
||||||
- mortent
|
- mortent
|
||||||
- phanimarupaka
|
- phanimarupaka
|
||||||
|
kyaml-reviewers:
|
||||||
|
- mengqiy
|
||||||
|
- mortent
|
||||||
|
- phanimarupaka
|
||||||
|
|
||||||
|
emeritus-approvers:
|
||||||
|
- liujingfang1
|
||||||
|
- Shell32-Natsu
|
||||||
|
- justinsb
|
||||||
|
- monopole
|
||||||
|
- pwittrock
|
||||||
|
|||||||
43
README.md
43
README.md
@@ -9,29 +9,38 @@ patch [kubernetes style] API objects. It's like
|
|||||||
[`make`], in that what it does is declared in a file,
|
[`make`], in that what it does is declared in a file,
|
||||||
and it's like [`sed`], in that it emits edited text.
|
and it's like [`sed`], in that it emits edited text.
|
||||||
|
|
||||||
This tool is sponsored by [sig-cli] ([KEP]), and
|
This tool is sponsored by [sig-cli] ([KEP]).
|
||||||
inspired by [DAM].
|
|
||||||
|
- [Installation instructions](https://kubernetes-sigs.github.io/kustomize/installation)
|
||||||
|
- [General documentation](https://kubernetes-sigs.github.io/kustomize)
|
||||||
|
- [Examples](examples)
|
||||||
|
|
||||||
[](https://prow.k8s.io/job-history/kubernetes-jenkins/pr-logs/directory/kustomize-presubmit-master)
|
[](https://prow.k8s.io/job-history/kubernetes-jenkins/pr-logs/directory/kustomize-presubmit-master)
|
||||||
[](https://goreportcard.com/report/github.com/kubernetes-sigs/kustomize)
|
[](https://goreportcard.com/report/github.com/kubernetes-sigs/kustomize)
|
||||||
|
|
||||||
Download a binary from the [release page], or see
|
|
||||||
these [instructions](https://kubernetes-sigs.github.io/kustomize/installation/).
|
|
||||||
|
|
||||||
Browse the [docs](https://kubernetes-sigs.github.io/kustomize/) or jump right into the
|
|
||||||
tested [examples](examples).
|
|
||||||
|
|
||||||
## kubectl integration
|
## 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 |
|
| Kubectl version | Kustomize version |
|
||||||
|---------|--------|
|
| --- | --- |
|
||||||
| v1.16.0 | [v2.0.3](/../../tree/v2.0.3) |
|
| < v1.14 | n/a |
|
||||||
| v1.15.x | [v2.0.3](/../../tree/v2.0.3) |
|
| v1.14-v1.20 | v2.0.3 |
|
||||||
| v1.14.x | [v2.0.3](/../../tree/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
|
## Usage
|
||||||
|
|
||||||
@@ -143,7 +152,7 @@ is governed by the [Kubernetes Code of Conduct].
|
|||||||
[`make`]: https://www.gnu.org/software/make
|
[`make`]: https://www.gnu.org/software/make
|
||||||
[`sed`]: https://www.gnu.org/software/sed
|
[`sed`]: https://www.gnu.org/software/sed
|
||||||
[DAM]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#declarative-application-management
|
[DAM]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#declarative-application-management
|
||||||
[KEP]: https://github.com/kubernetes/enhancements/blob/master/keps/sig-cli/0008-kustomize.md
|
[KEP]: https://github.com/kubernetes/enhancements/blob/master/keps/sig-cli/2377-Kustomize/README.md
|
||||||
[Kubernetes Code of Conduct]: code-of-conduct.md
|
[Kubernetes Code of Conduct]: code-of-conduct.md
|
||||||
[applied]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#apply
|
[applied]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#apply
|
||||||
[base]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#base
|
[base]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#base
|
||||||
@@ -151,7 +160,7 @@ is governed by the [Kubernetes Code of Conduct].
|
|||||||
[imageBase]: docs/images/base.jpg
|
[imageBase]: docs/images/base.jpg
|
||||||
[imageOverlay]: docs/images/overlay.jpg
|
[imageOverlay]: docs/images/overlay.jpg
|
||||||
[kubectl announcement]: https://kubernetes.io/blog/2019/03/25/kubernetes-1-14-release-announcement
|
[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 documentation]: https://kubernetes.io/docs/tasks/manage-kubernetes-objects/kustomization/
|
||||||
[kubernetes style]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#kubernetes-style-object
|
[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
|
[kustomization]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#kustomization
|
||||||
|
|||||||
@@ -1,187 +0,0 @@
|
|||||||
// Code generated by pluginator on ImageTagTransformer; DO NOT EDIT.
|
|
||||||
// pluginator {unknown 1970-01-01T00:00:00Z }
|
|
||||||
|
|
||||||
package builtins
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/transform"
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/resmap"
|
|
||||||
"sigs.k8s.io/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Find matching image declarations and replace
|
|
||||||
// the name, tag and/or digest.
|
|
||||||
type ImageTagTransformerPlugin struct {
|
|
||||||
ImageTag types.Image `json:"imageTag,omitempty" yaml:"imageTag,omitempty"`
|
|
||||||
FieldSpecs []types.FieldSpec `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ImageTagTransformerPlugin) Config(
|
|
||||||
_ *resmap.PluginHelpers, c []byte) (err error) {
|
|
||||||
p.ImageTag = types.Image{}
|
|
||||||
p.FieldSpecs = nil
|
|
||||||
return yaml.Unmarshal(c, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ImageTagTransformerPlugin) Transform(m resmap.ResMap) error {
|
|
||||||
for _, r := range m.Resources() {
|
|
||||||
for _, path := range p.FieldSpecs {
|
|
||||||
if !r.OrgId().IsSelected(&path.Gvk) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
err := transform.MutateField(
|
|
||||||
r.Map(), path.PathSlice(), false, p.mutateImage)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Kept for backward compatibility
|
|
||||||
if err := p.findAndReplaceImage(r.Map()); err != nil && r.OrgId().Kind != `CustomResourceDefinition` {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ImageTagTransformerPlugin) mutateImage(in interface{}) (interface{}, error) {
|
|
||||||
original, ok := in.(string)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("image path is not of type string but %T", in)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isImageMatched(original, p.ImageTag.Name) {
|
|
||||||
return original, nil
|
|
||||||
}
|
|
||||||
name, tag := split(original)
|
|
||||||
if p.ImageTag.NewName != "" {
|
|
||||||
name = p.ImageTag.NewName
|
|
||||||
}
|
|
||||||
if p.ImageTag.NewTag != "" {
|
|
||||||
tag = ":" + p.ImageTag.NewTag
|
|
||||||
}
|
|
||||||
if p.ImageTag.Digest != "" {
|
|
||||||
tag = "@" + p.ImageTag.Digest
|
|
||||||
}
|
|
||||||
return name + tag, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// findAndReplaceImage replaces the image name and
|
|
||||||
// tags inside one object.
|
|
||||||
// It searches the object for container session
|
|
||||||
// then loops though all images inside containers
|
|
||||||
// session, finds matched ones and update the
|
|
||||||
// image name and tag name
|
|
||||||
func (p *ImageTagTransformerPlugin) findAndReplaceImage(obj map[string]interface{}) error {
|
|
||||||
paths := []string{"containers", "initContainers"}
|
|
||||||
updated := false
|
|
||||||
for _, path := range paths {
|
|
||||||
containers, found := obj[path]
|
|
||||||
if found && containers != nil {
|
|
||||||
if _, err := p.updateContainers(containers); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
updated = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !updated {
|
|
||||||
return p.findContainers(obj)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ImageTagTransformerPlugin) updateContainers(in interface{}) (interface{}, error) {
|
|
||||||
containers, ok := in.([]interface{})
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"containers path is not of type []interface{} but %T", in)
|
|
||||||
}
|
|
||||||
for i := range containers {
|
|
||||||
container := containers[i].(map[string]interface{})
|
|
||||||
containerImage, found := container["image"]
|
|
||||||
if !found {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
imageName := containerImage.(string)
|
|
||||||
if isImageMatched(imageName, p.ImageTag.Name) {
|
|
||||||
newImage, err := p.mutateImage(imageName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
container["image"] = newImage
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return containers, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ImageTagTransformerPlugin) findContainers(obj map[string]interface{}) error {
|
|
||||||
for key := range obj {
|
|
||||||
switch typedV := obj[key].(type) {
|
|
||||||
case map[string]interface{}:
|
|
||||||
err := p.findAndReplaceImage(typedV)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case []interface{}:
|
|
||||||
for i := range typedV {
|
|
||||||
item := typedV[i]
|
|
||||||
typedItem, ok := item.(map[string]interface{})
|
|
||||||
if ok {
|
|
||||||
err := p.findAndReplaceImage(typedItem)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isImageMatched(s, t string) bool {
|
|
||||||
// Tag values are limited to [a-zA-Z0-9_.{}-].
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewImageTagTransformerPlugin() resmap.TransformerPlugin {
|
|
||||||
return &ImageTagTransformerPlugin{}
|
|
||||||
}
|
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
// Code generated by pluginator on NamespaceTransformer; DO NOT EDIT.
|
|
||||||
// pluginator {unknown 1970-01-01T00:00:00Z }
|
|
||||||
|
|
||||||
package builtins
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/filters/namespace"
|
|
||||||
"sigs.k8s.io/kustomize/api/resid"
|
|
||||||
"sigs.k8s.io/kustomize/api/resmap"
|
|
||||||
"sigs.k8s.io/kustomize/api/resource"
|
|
||||||
"sigs.k8s.io/kustomize/api/transform"
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/filtersutil"
|
|
||||||
"sigs.k8s.io/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Change or set the namespace of non-cluster level resources.
|
|
||||||
type NamespaceTransformerPlugin struct {
|
|
||||||
types.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
|
||||||
FieldSpecs []types.FieldSpec `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
|
|
||||||
|
|
||||||
// YAMLSupport can be set to true to use the kyaml filter instead of the
|
|
||||||
// kunstruct transformer.
|
|
||||||
// TODO: change the default to use kyaml when it is stable
|
|
||||||
YAMLSupport bool `json:"yamlSupport,omitempty" yaml:"yamlSupport,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *NamespaceTransformerPlugin) Config(
|
|
||||||
_ *resmap.PluginHelpers, c []byte) (err error) {
|
|
||||||
p.Namespace = ""
|
|
||||||
p.FieldSpecs = nil
|
|
||||||
if !strings.Contains(string(c), "yamlSupport") {
|
|
||||||
// If not explicitly denied,
|
|
||||||
// activate kyaml-based transformation.
|
|
||||||
p.YAMLSupport = true
|
|
||||||
}
|
|
||||||
return yaml.Unmarshal(c, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *NamespaceTransformerPlugin) Transform(m resmap.ResMap) error {
|
|
||||||
if len(p.Namespace) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
for _, r := range m.Resources() {
|
|
||||||
if len(r.Map()) == 0 {
|
|
||||||
// Don't mutate empty objects?
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
id := r.OrgId()
|
|
||||||
|
|
||||||
if p.YAMLSupport {
|
|
||||||
// use the new style transform
|
|
||||||
err := filtersutil.ApplyToJSON(namespace.Filter{
|
|
||||||
Namespace: p.Namespace,
|
|
||||||
FsSlice: p.FieldSpecs,
|
|
||||||
}, r)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// use the old style transform
|
|
||||||
applicableFs := p.applicableFieldSpecs(id)
|
|
||||||
for _, fs := range applicableFs {
|
|
||||||
err := transform.MutateField(
|
|
||||||
r.Map(), fs.PathSlice(), fs.CreateIfNotPresent,
|
|
||||||
p.changeNamespace(r))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
matches := m.GetMatchingResourcesByCurrentId(r.CurId().Equals)
|
|
||||||
if len(matches) != 1 {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"namespace transformation produces ID conflict: %+v", matches)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Special casing metadata.namespace since
|
|
||||||
// all objects have it, even "ClusterKind" objects
|
|
||||||
// that don't exist in a namespace (the Namespace
|
|
||||||
// object itself doesn't live in a namespace).
|
|
||||||
func (p *NamespaceTransformerPlugin) applicableFieldSpecs(id resid.ResId) []types.FieldSpec {
|
|
||||||
var res []types.FieldSpec
|
|
||||||
for _, fs := range p.FieldSpecs {
|
|
||||||
if id.IsSelected(&fs.Gvk) &&
|
|
||||||
(fs.Path != types.MetadataNamespacePath ||
|
|
||||||
(fs.Path == types.MetadataNamespacePath && id.IsNamespaceableKind())) {
|
|
||||||
res = append(res, fs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *NamespaceTransformerPlugin) changeNamespace(
|
|
||||||
_ *resource.Resource) func(in interface{}) (interface{}, error) {
|
|
||||||
return func(in interface{}) (interface{}, error) {
|
|
||||||
switch in.(type) {
|
|
||||||
case string:
|
|
||||||
// will happen when the metadata/namespace
|
|
||||||
// value is replaced
|
|
||||||
return p.Namespace, nil
|
|
||||||
case []interface{}:
|
|
||||||
l, _ := in.([]interface{})
|
|
||||||
for idx, item := range l {
|
|
||||||
switch item.(type) {
|
|
||||||
case map[string]interface{}:
|
|
||||||
// Will happen when mutating the subjects
|
|
||||||
// field of ClusterRoleBinding and RoleBinding
|
|
||||||
inMap, _ := item.(map[string]interface{})
|
|
||||||
if _, ok := inMap["name"]; !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
name, ok := inMap["name"].(string)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// The only case we need to force the namespace
|
|
||||||
// if for the "service account". "default" is
|
|
||||||
// kind of hardcoded here for right now.
|
|
||||||
if name != "default" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
inMap["namespace"] = p.Namespace
|
|
||||||
l[idx] = inMap
|
|
||||||
default:
|
|
||||||
// nothing to do for right now
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return in, nil
|
|
||||||
case map[string]interface{}:
|
|
||||||
// Will happen if the createField=true
|
|
||||||
// when the namespace is added to the
|
|
||||||
// object
|
|
||||||
inMap := in.(map[string]interface{})
|
|
||||||
if len(inMap) == 0 {
|
|
||||||
return p.Namespace, nil
|
|
||||||
} else {
|
|
||||||
return in, nil
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return in, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewNamespaceTransformerPlugin() resmap.TransformerPlugin {
|
|
||||||
return &NamespaceTransformerPlugin{}
|
|
||||||
}
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
// Code generated by pluginator on PatchStrategicMergeTransformer; DO NOT EDIT.
|
|
||||||
// pluginator {unknown 1970-01-01T00:00:00Z }
|
|
||||||
|
|
||||||
package builtins
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/filters/patchstrategicmerge"
|
|
||||||
"sigs.k8s.io/kustomize/api/resmap"
|
|
||||||
"sigs.k8s.io/kustomize/api/resource"
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/filtersutil"
|
|
||||||
"sigs.k8s.io/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PatchStrategicMergeTransformerPlugin struct {
|
|
||||||
h *resmap.PluginHelpers
|
|
||||||
loadedPatches []*resource.Resource
|
|
||||||
Paths []types.PatchStrategicMerge `json:"paths,omitempty" yaml:"paths,omitempty"`
|
|
||||||
Patches string `json:"patches,omitempty" yaml:"patches,omitempty"`
|
|
||||||
|
|
||||||
YAMLSupport bool `json:"yamlSupport,omitempty" yaml:"yamlSupport,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PatchStrategicMergeTransformerPlugin) Config(
|
|
||||||
h *resmap.PluginHelpers, c []byte) (err error) {
|
|
||||||
p.h = h
|
|
||||||
err = yaml.Unmarshal(c, p)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !strings.Contains(string(c), "yamlSupport") {
|
|
||||||
// If not explicitly denied,
|
|
||||||
// activate kyaml-based transformation.
|
|
||||||
p.YAMLSupport = true
|
|
||||||
}
|
|
||||||
if len(p.Paths) == 0 && p.Patches == "" {
|
|
||||||
return fmt.Errorf("empty file path and empty patch content")
|
|
||||||
}
|
|
||||||
if len(p.Paths) != 0 {
|
|
||||||
for _, onePath := range p.Paths {
|
|
||||||
res, err := p.h.ResmapFactory().RF().SliceFromBytes([]byte(onePath))
|
|
||||||
if err == nil {
|
|
||||||
p.loadedPatches = append(p.loadedPatches, res...)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
res, err = p.h.ResmapFactory().RF().SliceFromPatches(
|
|
||||||
p.h.Loader(), []types.PatchStrategicMerge{onePath})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
p.loadedPatches = append(p.loadedPatches, res...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if p.Patches != "" {
|
|
||||||
res, err := p.h.ResmapFactory().RF().SliceFromBytes([]byte(p.Patches))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
p.loadedPatches = append(p.loadedPatches, res...)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(p.loadedPatches) == 0 {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"patch appears to be empty; files=%v, Patch=%s", p.Paths, p.Patches)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PatchStrategicMergeTransformerPlugin) Transform(m resmap.ResMap) error {
|
|
||||||
patches, err := p.h.ResmapFactory().MergePatches(p.loadedPatches)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, patch := range patches.Resources() {
|
|
||||||
target, err := m.GetById(patch.OrgId())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !p.YAMLSupport {
|
|
||||||
err = target.Patch(patch.Copy())
|
|
||||||
} else {
|
|
||||||
patchCopy := patch.DeepCopy()
|
|
||||||
patchCopy.SetName(target.GetName())
|
|
||||||
patchCopy.SetNamespace(target.GetNamespace())
|
|
||||||
patchCopy.SetGvk(target.GetGvk())
|
|
||||||
node, err := filtersutil.GetRNode(patchCopy)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = filtersutil.ApplyToJSON(patchstrategicmerge.Filter{
|
|
||||||
Patch: node,
|
|
||||||
}, target)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(target.Map()) == 0 {
|
|
||||||
// This means all fields have been removed from the object.
|
|
||||||
// This can happen if a patch required deletion of the
|
|
||||||
// entire resource (not just a part of it). This means
|
|
||||||
// the overall resmap must shrink by one.
|
|
||||||
err = m.Remove(target.CurId())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPatchStrategicMergeTransformerPlugin() resmap.TransformerPlugin {
|
|
||||||
return &PatchStrategicMergeTransformerPlugin{}
|
|
||||||
}
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
// Code generated by pluginator on PrefixSuffixTransformer; DO NOT EDIT.
|
|
||||||
// pluginator {unknown 1970-01-01T00:00:00Z }
|
|
||||||
|
|
||||||
package builtins
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/transform"
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/resid"
|
|
||||||
"sigs.k8s.io/kustomize/api/resmap"
|
|
||||||
"sigs.k8s.io/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Add the given prefix and suffix to the field.
|
|
||||||
type PrefixSuffixTransformerPlugin struct {
|
|
||||||
Prefix string `json:"prefix,omitempty" yaml:"prefix,omitempty"`
|
|
||||||
Suffix string `json:"suffix,omitempty" yaml:"suffix,omitempty"`
|
|
||||||
FieldSpecs []types.FieldSpec `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not placed in a file yet due to lack of demand.
|
|
||||||
var prefixSuffixFieldSpecsToSkip = []types.FieldSpec{
|
|
||||||
{
|
|
||||||
Gvk: resid.Gvk{Kind: "CustomResourceDefinition"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Gvk: resid.Gvk{Group: "apiregistration.k8s.io", Kind: "APIService"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PrefixSuffixTransformerPlugin) Config(
|
|
||||||
_ *resmap.PluginHelpers, c []byte) (err error) {
|
|
||||||
p.Prefix = ""
|
|
||||||
p.Suffix = ""
|
|
||||||
p.FieldSpecs = nil
|
|
||||||
err = yaml.Unmarshal(c, p)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if p.FieldSpecs == nil {
|
|
||||||
return errors.New("fieldSpecs is not expected to be nil")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PrefixSuffixTransformerPlugin) Transform(m resmap.ResMap) error {
|
|
||||||
|
|
||||||
// Even if both the Prefix and Suffix are empty we want
|
|
||||||
// to proceed with the transformation. This allows to add contextual
|
|
||||||
// information to the resources (AddNamePrefix and AddNameSuffix).
|
|
||||||
|
|
||||||
for _, r := range m.Resources() {
|
|
||||||
if p.shouldSkip(r.OrgId()) {
|
|
||||||
// Don't change the actual definition
|
|
||||||
// of a CRD.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
id := r.OrgId()
|
|
||||||
// current default configuration contains
|
|
||||||
// only one entry: "metadata/name" with no GVK
|
|
||||||
for _, path := range p.FieldSpecs {
|
|
||||||
if !id.IsSelected(&path.Gvk) {
|
|
||||||
// With the currrent default configuration,
|
|
||||||
// because no Gvk is specified, so a wild
|
|
||||||
// card
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if smellsLikeANameChange(&path) {
|
|
||||||
// "metadata/name" is the only field.
|
|
||||||
// this will add a prefix and a suffix
|
|
||||||
// to the resource even if those are
|
|
||||||
// empty
|
|
||||||
r.AddNamePrefix(p.Prefix)
|
|
||||||
r.AddNameSuffix(p.Suffix)
|
|
||||||
}
|
|
||||||
|
|
||||||
// the addPrefixSuffix method will not
|
|
||||||
// change the name if both the prefix and suffix
|
|
||||||
// are empty.
|
|
||||||
err := transform.MutateField(
|
|
||||||
r.Map(),
|
|
||||||
path.PathSlice(),
|
|
||||||
path.CreateIfNotPresent,
|
|
||||||
p.addPrefixSuffix)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func smellsLikeANameChange(fs *types.FieldSpec) bool {
|
|
||||||
return fs.Path == "metadata/name"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PrefixSuffixTransformerPlugin) shouldSkip(
|
|
||||||
id resid.ResId) bool {
|
|
||||||
for _, path := range prefixSuffixFieldSpecsToSkip {
|
|
||||||
if id.IsSelected(&path.Gvk) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PrefixSuffixTransformerPlugin) addPrefixSuffix(
|
|
||||||
in interface{}) (interface{}, error) {
|
|
||||||
s, ok := in.(string)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("%#v is expected to be %T", in, s)
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s%s%s", p.Prefix, s, p.Suffix), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPrefixSuffixTransformerPlugin() resmap.TransformerPlugin {
|
|
||||||
return &PrefixSuffixTransformerPlugin{}
|
|
||||||
}
|
|
||||||
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)
|
||||||
|
}
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
// Copyright 2019 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
// Package filesys provides a file system abstraction layer.
|
|
||||||
package filesys
|
|
||||||
|
|
||||||
import (
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
Separator = string(filepath.Separator)
|
|
||||||
SelfDir = "."
|
|
||||||
ParentDir = ".."
|
|
||||||
)
|
|
||||||
|
|
||||||
// FileSystem groups basic os filesystem methods.
|
|
||||||
// It's supposed be functional subset of https://golang.org/pkg/os
|
|
||||||
type FileSystem interface {
|
|
||||||
// Create a file.
|
|
||||||
Create(path string) (File, error)
|
|
||||||
// MkDir makes a directory.
|
|
||||||
Mkdir(path string) error
|
|
||||||
// MkDirAll makes a directory path, creating intervening directories.
|
|
||||||
MkdirAll(path string) error
|
|
||||||
// RemoveAll removes path and any children it contains.
|
|
||||||
RemoveAll(path string) error
|
|
||||||
// Open opens the named file for reading.
|
|
||||||
Open(path string) (File, error)
|
|
||||||
// IsDir returns true if the path is a directory.
|
|
||||||
IsDir(path string) bool
|
|
||||||
// CleanedAbs converts the given path into a
|
|
||||||
// directory and a file name, where the directory
|
|
||||||
// is represented as a ConfirmedDir and all that implies.
|
|
||||||
// If the entire path is a directory, the file component
|
|
||||||
// is an empty string.
|
|
||||||
CleanedAbs(path string) (ConfirmedDir, string, error)
|
|
||||||
// Exists is true if the path exists in the file system.
|
|
||||||
Exists(path string) bool
|
|
||||||
// Glob returns the list of matching files,
|
|
||||||
// emulating https://golang.org/pkg/path/filepath/#Glob
|
|
||||||
Glob(pattern string) ([]string, error)
|
|
||||||
// ReadFile returns the contents of the file at the given path.
|
|
||||||
ReadFile(path string) ([]byte, error)
|
|
||||||
// WriteFile writes the data to a file at the given path,
|
|
||||||
// overwriting anything that's already there.
|
|
||||||
WriteFile(path string, data []byte) error
|
|
||||||
// Walk walks the file system with the given WalkFunc.
|
|
||||||
Walk(path string, walkFn filepath.WalkFunc) error
|
|
||||||
}
|
|
||||||
@@ -19,20 +19,29 @@ type Filter struct {
|
|||||||
|
|
||||||
// FsSlice contains the FieldSpecs to locate the namespace field
|
// FsSlice contains the FieldSpecs to locate the namespace field
|
||||||
FsSlice types.FsSlice
|
FsSlice types.FsSlice
|
||||||
|
|
||||||
|
trackableSetter filtersutil.TrackableSetter
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ kio.Filter = Filter{}
|
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) {
|
func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||||
keys := filtersutil.SortedMapKeys(f.Annotations)
|
keys := yaml.SortedMapKeys(f.Annotations)
|
||||||
_, err := kio.FilterAll(yaml.FilterFunc(
|
_, err := kio.FilterAll(yaml.FilterFunc(
|
||||||
func(node *yaml.RNode) (*yaml.RNode, error) {
|
func(node *yaml.RNode) (*yaml.RNode, error) {
|
||||||
for _, k := range keys {
|
for _, k := range keys {
|
||||||
if err := node.PipeE(fsslice.Filter{
|
if err := node.PipeE(fsslice.Filter{
|
||||||
FsSlice: f.FsSlice,
|
FsSlice: f.FsSlice,
|
||||||
SetValue: fsslice.SetEntry(k, f.Annotations[k], yaml.StringTag),
|
SetValue: f.trackableSetter.SetEntry(
|
||||||
|
k, f.Annotations[k], yaml.NodeTagString),
|
||||||
CreateKind: yaml.MappingNode, // Annotations are MappingNodes.
|
CreateKind: yaml.MappingNode, // Annotations are MappingNodes.
|
||||||
CreateTag: "!!map",
|
CreateTag: yaml.NodeTagMap,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,16 +11,20 @@ import (
|
|||||||
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
||||||
filtertest_test "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
filtertest_test "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
var annosFs = builtinconfig.MakeDefaultConfig().CommonAnnotations
|
var annosFs = builtinconfig.MakeDefaultConfig().CommonAnnotations
|
||||||
|
|
||||||
func TestAnnotations_Filter(t *testing.T) {
|
func TestAnnotations_Filter(t *testing.T) {
|
||||||
|
mutationTrackStub := filtertest_test.MutationTrackerStub{}
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
input string
|
input string
|
||||||
expectedOutput string
|
expectedOutput string
|
||||||
filter Filter
|
filter Filter
|
||||||
fsslice types.FsSlice
|
fsslice types.FsSlice
|
||||||
|
setEntryCallback func(key, value, tag string, node *yaml.RNode)
|
||||||
|
expectedSetEntryArgs []filtertest_test.SetValueArg
|
||||||
}{
|
}{
|
||||||
"add": {
|
"add": {
|
||||||
input: `
|
input: `
|
||||||
@@ -210,17 +214,86 @@ metadata:
|
|||||||
"b": "b1",
|
"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 {
|
for tn, tc := range testCases {
|
||||||
|
mutationTrackStub.Reset()
|
||||||
t.Run(tn, func(t *testing.T) {
|
t.Run(tn, func(t *testing.T) {
|
||||||
filter := tc.filter
|
filter := tc.filter
|
||||||
|
filter.WithMutationTracker(tc.setEntryCallback)
|
||||||
filter.FsSlice = append(annosFs, tc.fsslice...)
|
filter.FsSlice = append(annosFs, tc.fsslice...)
|
||||||
if !assert.Equal(t,
|
if !assert.Equal(t,
|
||||||
strings.TrimSpace(tc.expectedOutput),
|
strings.TrimSpace(tc.expectedOutput),
|
||||||
strings.TrimSpace(filtertest_test.RunFilter(t, tc.input, filter))) {
|
strings.TrimSpace(filtertest_test.RunFilter(t, tc.input, filter))) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
if !assert.Equal(t, tc.expectedSetEntryArgs, mutationTrackStub.SetValueArgs()) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,9 @@ metadata:
|
|||||||
`)}},
|
`)}},
|
||||||
Filters: []kio.Filter{Filter{
|
Filters: []kio.Filter{Filter{
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
|
"booleanValue": "true",
|
||||||
|
"numberValue": "42",
|
||||||
},
|
},
|
||||||
FsSlice: fss,
|
FsSlice: fss,
|
||||||
}},
|
}},
|
||||||
@@ -44,12 +46,16 @@ metadata:
|
|||||||
// metadata:
|
// metadata:
|
||||||
// name: instance
|
// name: instance
|
||||||
// annotations:
|
// annotations:
|
||||||
|
// booleanValue: "true"
|
||||||
// foo: bar
|
// foo: bar
|
||||||
|
// numberValue: "42"
|
||||||
// ---
|
// ---
|
||||||
// apiVersion: example.com/v1
|
// apiVersion: example.com/v1
|
||||||
// kind: Bar
|
// kind: Bar
|
||||||
// metadata:
|
// metadata:
|
||||||
// name: instance
|
// name: instance
|
||||||
// annotations:
|
// annotations:
|
||||||
|
// booleanValue: "true"
|
||||||
// foo: bar
|
// 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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
// Copyright 2019 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package filtersutil
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sort"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SortedMapKeys returns a sorted slice of keys to the given map.
|
|
||||||
// Writing this function never gets old.
|
|
||||||
func SortedMapKeys(m map[string]string) []string {
|
|
||||||
keys := make([]string, len(m))
|
|
||||||
i := 0
|
|
||||||
for k := range m {
|
|
||||||
keys[i] = k
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
sort.Strings(keys)
|
|
||||||
return keys
|
|
||||||
}
|
|
||||||
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
// Copyright 2020 The Kubernetes Authors.
|
// Copyright 2020 The Kubernetes Authors.
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
// Package fsslice contains a yaml.Filter to modify a resource using an
|
// Package fsslice contains a yaml.Filter to modify a resource if
|
||||||
// FsSlice to identify fields to be updated within the resource.
|
// it matches one or more FieldSpec entries.
|
||||||
package fsslice
|
package fsslice
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"sigs.k8s.io/kustomize/api/filters/filtersutil"
|
||||||
"sigs.k8s.io/kustomize/api/filters/fsslice"
|
"sigs.k8s.io/kustomize/api/filters/fsslice"
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||||
@@ -30,7 +31,7 @@ metadata:
|
|||||||
}
|
}
|
||||||
fltr := fsslice.Filter{
|
fltr := fsslice.Filter{
|
||||||
CreateKind: yaml.ScalarNode,
|
CreateKind: yaml.ScalarNode,
|
||||||
SetValue: fsslice.SetScalar("green"),
|
SetValue: filtersutil.SetScalar("green"),
|
||||||
FsSlice: []types.FieldSpec{
|
FsSlice: []types.FieldSpec{
|
||||||
{Path: "a/b", CreateIfNotPresent: true},
|
{Path: "a/b", CreateIfNotPresent: true},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,155 +0,0 @@
|
|||||||
// Copyright 2019 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package fsslice
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/errors"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
// fieldSpecFilter applies a single fieldSpec to a single object
|
|
||||||
// fieldSpecFilter stores internal state and should not be reused
|
|
||||||
type fieldSpecFilter struct {
|
|
||||||
// FieldSpec contains the path to the value to set.
|
|
||||||
FieldSpec types.FieldSpec `yaml:"fieldSpec"`
|
|
||||||
|
|
||||||
// Set the field using this function
|
|
||||||
SetValue SetFn
|
|
||||||
|
|
||||||
// CreateKind defines the type of node to create if the field is not found
|
|
||||||
CreateKind yaml.Kind
|
|
||||||
|
|
||||||
CreateTag string
|
|
||||||
|
|
||||||
// path keeps internal state about the current path
|
|
||||||
path []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fltr fieldSpecFilter) Filter(obj *yaml.RNode) (*yaml.RNode, error) {
|
|
||||||
// check if the FieldSpec applies to the object
|
|
||||||
if match, err := isMatchGVK(fltr.FieldSpec, obj); !match || err != nil {
|
|
||||||
return obj, errors.Wrap(err)
|
|
||||||
}
|
|
||||||
fltr.path = strings.Split(fltr.FieldSpec.Path, "/")
|
|
||||||
if err := fltr.filter(obj); err != nil {
|
|
||||||
s, _ := obj.String()
|
|
||||||
return nil, errors.WrapPrefixf(err,
|
|
||||||
"obj %v at path %v", s, fltr.FieldSpec.Path)
|
|
||||||
}
|
|
||||||
return obj, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fltr fieldSpecFilter) filter(obj *yaml.RNode) error {
|
|
||||||
if len(fltr.path) == 0 {
|
|
||||||
// found the field -- set its value
|
|
||||||
return fltr.SetValue(obj)
|
|
||||||
}
|
|
||||||
switch obj.YNode().Kind {
|
|
||||||
case yaml.SequenceNode:
|
|
||||||
return fltr.seq(obj)
|
|
||||||
case yaml.MappingNode:
|
|
||||||
return fltr.field(obj)
|
|
||||||
}
|
|
||||||
// not found -- this might be an error since the type doesn't match
|
|
||||||
|
|
||||||
return errors.Errorf("unsupported yaml node")
|
|
||||||
}
|
|
||||||
|
|
||||||
// field calls filter on the field matching the next path element
|
|
||||||
func (fltr fieldSpecFilter) field(obj *yaml.RNode) error {
|
|
||||||
fieldName, isSeq := isSequenceField(fltr.path[0])
|
|
||||||
|
|
||||||
// lookup the field matching the next path element
|
|
||||||
var lookupField yaml.Filter
|
|
||||||
var kind yaml.Kind
|
|
||||||
var tag string
|
|
||||||
switch {
|
|
||||||
case !fltr.FieldSpec.CreateIfNotPresent || fltr.CreateKind == 0 || isSeq:
|
|
||||||
// dont' create the field if we don't find it
|
|
||||||
lookupField = yaml.Lookup(fieldName)
|
|
||||||
case len(fltr.path) <= 1:
|
|
||||||
// create the field if it is missing: use the provided node kind
|
|
||||||
lookupField = yaml.LookupCreate(fltr.CreateKind, fieldName)
|
|
||||||
kind = fltr.CreateKind
|
|
||||||
tag = fltr.CreateTag
|
|
||||||
default:
|
|
||||||
// create the field if it is missing: must be a mapping node
|
|
||||||
lookupField = yaml.LookupCreate(yaml.MappingNode, fieldName)
|
|
||||||
kind = yaml.MappingNode
|
|
||||||
tag = "!!map"
|
|
||||||
}
|
|
||||||
|
|
||||||
// locate (or maybe create) the field
|
|
||||||
field, err := obj.Pipe(lookupField)
|
|
||||||
if err != nil || field == nil {
|
|
||||||
return errors.WrapPrefixf(err, "fieldName: %s", fieldName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the value exists, but is null, then change it to the creation type
|
|
||||||
// TODO: update yaml.LookupCreate to support this
|
|
||||||
if field.YNode().Tag == "!!null" {
|
|
||||||
field.YNode().Kind = kind
|
|
||||||
field.YNode().Tag = tag
|
|
||||||
}
|
|
||||||
|
|
||||||
// copy the current fltr and change the path on the copy
|
|
||||||
var next = fltr
|
|
||||||
// call filter for the next path element on the matching field
|
|
||||||
next.path = fltr.path[1:]
|
|
||||||
return next.filter(field)
|
|
||||||
}
|
|
||||||
|
|
||||||
// seq calls filter on all sequence elements
|
|
||||||
func (fltr fieldSpecFilter) seq(obj *yaml.RNode) error {
|
|
||||||
if err := obj.VisitElements(func(node *yaml.RNode) error {
|
|
||||||
// recurse on each element -- re-allocating a fieldSpecFilter is
|
|
||||||
// not strictly required, but is more consistent with field
|
|
||||||
// and less likely to have side effects
|
|
||||||
// keep the entire path -- it does not contain parts for sequences
|
|
||||||
return fltr.filter(node)
|
|
||||||
}); err != nil {
|
|
||||||
return errors.WrapPrefixf(err,
|
|
||||||
"visit traversal on path: %v", fltr.path)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// isSequenceField returns true if the path element is for a sequence field.
|
|
||||||
// isSequence also returns the path element with the '[]' suffix trimmed
|
|
||||||
func isSequenceField(name string) (string, bool) {
|
|
||||||
isSeq := strings.HasSuffix(name, "[]")
|
|
||||||
name = strings.TrimSuffix(name, "[]")
|
|
||||||
return name, isSeq
|
|
||||||
}
|
|
||||||
|
|
||||||
// isMatchGVK returns true if the fs.GVK matches the obj GVK.
|
|
||||||
func isMatchGVK(fs types.FieldSpec, obj *yaml.RNode) (bool, error) {
|
|
||||||
meta, err := obj.GetMeta()
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
if fs.Kind != "" && fs.Kind != meta.Kind {
|
|
||||||
// kind doesn't match
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse the group and version from the apiVersion field
|
|
||||||
group, version := parseGV(meta.APIVersion)
|
|
||||||
|
|
||||||
if fs.Group != "" && fs.Group != group {
|
|
||||||
// group doesn't match
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if fs.Version != "" && fs.Version != version {
|
|
||||||
// version doesn't match
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
@@ -4,48 +4,22 @@
|
|||||||
package fsslice
|
package fsslice
|
||||||
|
|
||||||
import (
|
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/api/types"
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SetFn sets a value
|
|
||||||
type SetFn func(*yaml.RNode) error
|
|
||||||
|
|
||||||
// SetScalar returns a SetFn to set a scalar value
|
|
||||||
func SetScalar(value string) SetFn {
|
|
||||||
return func(node *yaml.RNode) error {
|
|
||||||
return node.PipeE(yaml.FieldSetter{StringValue: value})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetEntry returns a SetFn to set an entry in a map
|
|
||||||
func SetEntry(key, value, tag string) SetFn {
|
|
||||||
n := &yaml.Node{
|
|
||||||
Kind: yaml.ScalarNode,
|
|
||||||
Value: value,
|
|
||||||
Tag: tag,
|
|
||||||
}
|
|
||||||
if tag == yaml.StringTag && yaml.IsYaml1_1NonString(n) {
|
|
||||||
n.Style = yaml.DoubleQuotedStyle
|
|
||||||
}
|
|
||||||
return func(node *yaml.RNode) error {
|
|
||||||
return node.PipeE(yaml.FieldSetter{
|
|
||||||
Name: key,
|
|
||||||
Value: yaml.NewRNode(n),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ yaml.Filter = Filter{}
|
var _ yaml.Filter = Filter{}
|
||||||
|
|
||||||
// Filter uses an FsSlice to modify fields on a single object
|
// Filter ranges over an FsSlice to modify fields on a single object.
|
||||||
|
// An FsSlice is a range of FieldSpecs. A FieldSpec is a GVK plus a path.
|
||||||
type Filter struct {
|
type Filter struct {
|
||||||
// FieldSpecList list of FieldSpecs to set
|
// FieldSpecList list of FieldSpecs to set
|
||||||
FsSlice types.FsSlice `yaml:"fsSlice"`
|
FsSlice types.FsSlice `yaml:"fsSlice"`
|
||||||
|
|
||||||
// SetValue is called on each field that matches one of the FieldSpecs
|
// SetValue is called on each field that matches one of the FieldSpecs
|
||||||
SetValue SetFn
|
SetValue filtersutil.SetFn
|
||||||
|
|
||||||
// CreateKind is used to create fields that do not exist
|
// CreateKind is used to create fields that do not exist
|
||||||
CreateKind yaml.Kind
|
CreateKind yaml.Kind
|
||||||
@@ -59,7 +33,7 @@ func (fltr Filter) Filter(obj *yaml.RNode) (*yaml.RNode, error) {
|
|||||||
// apply this FieldSpec
|
// apply this FieldSpec
|
||||||
// create a new filter for each iteration because they
|
// create a new filter for each iteration because they
|
||||||
// store internal state about the field paths
|
// store internal state about the field paths
|
||||||
_, err := (&fieldSpecFilter{
|
_, err := (&fieldspec.Filter{
|
||||||
FieldSpec: fltr.FsSlice[i],
|
FieldSpec: fltr.FsSlice[i],
|
||||||
SetValue: fltr.SetValue,
|
SetValue: fltr.SetValue,
|
||||||
CreateKind: fltr.CreateKind,
|
CreateKind: fltr.CreateKind,
|
||||||
|
|||||||
@@ -9,335 +9,77 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"sigs.k8s.io/kustomize/api/filters/fsslice"
|
"sigs.k8s.io/kustomize/api/filters/filtersutil"
|
||||||
|
. "sigs.k8s.io/kustomize/api/filters/fsslice"
|
||||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TestCase struct {
|
type TestCase struct {
|
||||||
name string
|
|
||||||
input string
|
input string
|
||||||
expected string
|
expected string
|
||||||
filter fsslice.Filter
|
filter Filter
|
||||||
fsSlice string
|
fsSlice string
|
||||||
error string
|
error string
|
||||||
}
|
}
|
||||||
|
|
||||||
var tests = []TestCase{
|
var tests = map[string]TestCase{
|
||||||
{
|
"empty": {
|
||||||
name: "update",
|
|
||||||
fsSlice: `
|
fsSlice: `
|
||||||
- path: a/b
|
|
||||||
group: foo
|
|
||||||
kind: Bar
|
|
||||||
`,
|
`,
|
||||||
input: `
|
input: `
|
||||||
apiVersion: foo/v1beta1
|
apiVersion: foo/v1
|
||||||
kind: Bar
|
kind: Bar
|
||||||
a:
|
|
||||||
b: c
|
|
||||||
`,
|
`,
|
||||||
expected: `
|
expected: `
|
||||||
apiVersion: foo/v1beta1
|
apiVersion: foo/v1
|
||||||
kind: Bar
|
kind: Bar
|
||||||
a:
|
|
||||||
b: e
|
|
||||||
`,
|
`,
|
||||||
filter: fsslice.Filter{
|
filter: Filter{
|
||||||
SetValue: fsslice.SetScalar("e"),
|
SetValue: filtersutil.SetScalar("e"),
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: "update-kind-not-match",
|
|
||||||
fsSlice: `
|
|
||||||
- path: a/b
|
|
||||||
group: foo
|
|
||||||
kind: Bar1
|
|
||||||
`,
|
|
||||||
input: `
|
|
||||||
apiVersion: foo/v1beta1
|
|
||||||
kind: Bar2
|
|
||||||
a:
|
|
||||||
b: c
|
|
||||||
`,
|
|
||||||
expected: `
|
|
||||||
apiVersion: foo/v1beta1
|
|
||||||
kind: Bar2
|
|
||||||
a:
|
|
||||||
b: c
|
|
||||||
`,
|
|
||||||
filter: fsslice.Filter{
|
|
||||||
SetValue: fsslice.SetScalar("e"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: "update-group-not-match",
|
|
||||||
fsSlice: `
|
|
||||||
- path: a/b
|
|
||||||
group: foo1
|
|
||||||
kind: Bar
|
|
||||||
`,
|
|
||||||
input: `
|
|
||||||
apiVersion: foo2/v1beta1
|
|
||||||
kind: Bar
|
|
||||||
a:
|
|
||||||
b: c
|
|
||||||
`,
|
|
||||||
expected: `
|
|
||||||
apiVersion: foo2/v1beta1
|
|
||||||
kind: Bar
|
|
||||||
a:
|
|
||||||
b: c
|
|
||||||
`,
|
|
||||||
filter: fsslice.Filter{
|
|
||||||
SetValue: fsslice.SetScalar("e"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: "update-version-not-match",
|
|
||||||
fsSlice: `
|
|
||||||
- path: a/b
|
|
||||||
group: foo
|
|
||||||
version: v1beta1
|
|
||||||
kind: Bar
|
|
||||||
`,
|
|
||||||
input: `
|
|
||||||
apiVersion: foo/v1beta2
|
|
||||||
kind: Bar
|
|
||||||
a:
|
|
||||||
b: c
|
|
||||||
`,
|
|
||||||
expected: `
|
|
||||||
apiVersion: foo/v1beta2
|
|
||||||
kind: Bar
|
|
||||||
a:
|
|
||||||
b: c
|
|
||||||
`,
|
|
||||||
filter: fsslice.Filter{
|
|
||||||
SetValue: fsslice.SetScalar("e"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: "bad-version",
|
|
||||||
fsSlice: `
|
|
||||||
- path: a/b
|
|
||||||
group: foo
|
|
||||||
version: v1beta1
|
|
||||||
kind: Bar
|
|
||||||
`,
|
|
||||||
input: `
|
|
||||||
apiVersion: foo/v1beta2/something
|
|
||||||
kind: Bar
|
|
||||||
a:
|
|
||||||
b: c
|
|
||||||
`,
|
|
||||||
expected: `
|
|
||||||
apiVersion: foo/v1beta2/something
|
|
||||||
kind: Bar
|
|
||||||
a:
|
|
||||||
b: c
|
|
||||||
`,
|
|
||||||
filter: fsslice.Filter{
|
|
||||||
SetValue: fsslice.SetScalar("e"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: "bad-meta",
|
|
||||||
fsSlice: `
|
|
||||||
- path: a/b
|
|
||||||
group: foo
|
|
||||||
version: v1beta1
|
|
||||||
kind: Bar
|
|
||||||
`,
|
|
||||||
input: `
|
|
||||||
a:
|
|
||||||
b: c
|
|
||||||
`,
|
|
||||||
filter: fsslice.Filter{
|
|
||||||
SetValue: fsslice.SetScalar("e"),
|
|
||||||
},
|
|
||||||
error: "missing Resource metadata",
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: "miss-match-type",
|
|
||||||
fsSlice: `
|
|
||||||
- path: a/b/c
|
|
||||||
kind: Bar
|
|
||||||
`,
|
|
||||||
input: `
|
|
||||||
kind: Bar
|
|
||||||
a:
|
|
||||||
b: a
|
|
||||||
`,
|
|
||||||
error: "obj kind: Bar\na:\n b: a\n at path a/b/c: unsupported yaml node",
|
|
||||||
filter: fsslice.Filter{
|
|
||||||
SetValue: fsslice.SetScalar("e"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: "add",
|
|
||||||
fsSlice: `
|
|
||||||
- path: a/b/c/d
|
|
||||||
group: foo
|
|
||||||
create: true
|
|
||||||
kind: Bar
|
|
||||||
`,
|
|
||||||
input: `
|
|
||||||
apiVersion: foo/v1beta1
|
|
||||||
kind: Bar
|
|
||||||
a: {}
|
|
||||||
`,
|
|
||||||
expected: `
|
|
||||||
apiVersion: foo/v1beta1
|
|
||||||
kind: Bar
|
|
||||||
a: {b: {c: {d: e}}}
|
|
||||||
`,
|
|
||||||
filter: fsslice.Filter{
|
|
||||||
SetValue: fsslice.SetScalar("e"),
|
|
||||||
CreateKind: yaml.ScalarNode,
|
CreateKind: yaml.ScalarNode,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"two": {
|
||||||
{
|
|
||||||
name: "update-in-sequence",
|
|
||||||
fsSlice: `
|
|
||||||
- path: a/b[]/c/d
|
|
||||||
group: foo
|
|
||||||
kind: Bar
|
|
||||||
`,
|
|
||||||
input: `
|
|
||||||
apiVersion: foo/v1beta1
|
|
||||||
kind: Bar
|
|
||||||
a:
|
|
||||||
b:
|
|
||||||
- c:
|
|
||||||
d: a
|
|
||||||
`,
|
|
||||||
expected: `
|
|
||||||
apiVersion: foo/v1beta1
|
|
||||||
kind: Bar
|
|
||||||
a:
|
|
||||||
b:
|
|
||||||
- c:
|
|
||||||
d: e
|
|
||||||
`,
|
|
||||||
filter: fsslice.Filter{
|
|
||||||
SetValue: fsslice.SetScalar("e"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
// Don't create a sequence
|
|
||||||
{
|
|
||||||
name: "empty-sequence-no-create",
|
|
||||||
fsSlice: `
|
|
||||||
- path: a/b[]/c/d
|
|
||||||
group: foo
|
|
||||||
create: true
|
|
||||||
kind: Bar
|
|
||||||
`,
|
|
||||||
input: `
|
|
||||||
apiVersion: foo/v1beta1
|
|
||||||
kind: Bar
|
|
||||||
a: {}
|
|
||||||
`,
|
|
||||||
expected: `
|
|
||||||
apiVersion: foo/v1beta1
|
|
||||||
kind: Bar
|
|
||||||
a: {}
|
|
||||||
`,
|
|
||||||
filter: fsslice.Filter{
|
|
||||||
SetValue: fsslice.SetScalar("e"),
|
|
||||||
CreateKind: yaml.ScalarNode,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
// Create a new field for an element in a sequence
|
|
||||||
{
|
|
||||||
name: "empty-sequence-create",
|
|
||||||
fsSlice: `
|
|
||||||
- path: a/b[]/c/d
|
|
||||||
group: foo
|
|
||||||
create: true
|
|
||||||
kind: Bar
|
|
||||||
`,
|
|
||||||
input: `
|
|
||||||
apiVersion: foo/v1beta1
|
|
||||||
kind: Bar
|
|
||||||
a:
|
|
||||||
b:
|
|
||||||
- c: {}
|
|
||||||
`,
|
|
||||||
expected: `
|
|
||||||
apiVersion: foo/v1beta1
|
|
||||||
kind: Bar
|
|
||||||
a:
|
|
||||||
b:
|
|
||||||
- c: {d: e}
|
|
||||||
`,
|
|
||||||
filter: fsslice.Filter{
|
|
||||||
SetValue: fsslice.SetScalar("e"),
|
|
||||||
CreateKind: yaml.ScalarNode,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: "group v1",
|
|
||||||
fsSlice: `
|
fsSlice: `
|
||||||
- path: a/b
|
- path: a/b
|
||||||
group: v1
|
group: foo
|
||||||
|
version: v1
|
||||||
create: true
|
create: true
|
||||||
kind: Bar
|
kind: Bar
|
||||||
`,
|
- path: q/r[]/s/t
|
||||||
input: `
|
group: foo
|
||||||
apiVersion: v1
|
|
||||||
kind: Bar
|
|
||||||
`,
|
|
||||||
expected: `
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Bar
|
|
||||||
`,
|
|
||||||
filter: fsslice.Filter{
|
|
||||||
SetValue: fsslice.SetScalar("e"),
|
|
||||||
CreateKind: yaml.ScalarNode,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: "version v1",
|
|
||||||
fsSlice: `
|
|
||||||
- path: a/b
|
|
||||||
version: v1
|
version: v1
|
||||||
create: true
|
create: true
|
||||||
kind: Bar
|
kind: Bar
|
||||||
`,
|
`,
|
||||||
input: `
|
input: `
|
||||||
apiVersion: v1
|
apiVersion: foo/v1
|
||||||
kind: Bar
|
kind: Bar
|
||||||
|
q:
|
||||||
|
r:
|
||||||
|
- s: {}
|
||||||
`,
|
`,
|
||||||
expected: `
|
expected: `
|
||||||
apiVersion: v1
|
apiVersion: foo/v1
|
||||||
kind: Bar
|
kind: Bar
|
||||||
|
q:
|
||||||
|
r:
|
||||||
|
- s: {t: e}
|
||||||
a:
|
a:
|
||||||
b: e
|
b: e
|
||||||
`,
|
`,
|
||||||
filter: fsslice.Filter{
|
filter: Filter{
|
||||||
SetValue: fsslice.SetScalar("e"),
|
SetValue: filtersutil.SetScalar("e"),
|
||||||
CreateKind: yaml.ScalarNode,
|
CreateKind: yaml.ScalarNode,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFilter_Filter(t *testing.T) {
|
func TestFilter(t *testing.T) {
|
||||||
for i := range tests {
|
for name := range tests {
|
||||||
test := tests[i]
|
test := tests[name]
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
err := yaml.Unmarshal([]byte(test.fsSlice), &test.filter.FsSlice)
|
err := yaml.Unmarshal([]byte(test.fsSlice), &test.filter.FsSlice)
|
||||||
if !assert.NoError(t, err) {
|
if !assert.NoError(t, err) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
// Copyright 2019 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
package fsslice
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/resid"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Return true for 'v' followed by a 1 or 2, and don't look at rest.
|
|
||||||
// I.e. 'v1', 'v1beta1', 'v2', would return true.
|
|
||||||
func looksLikeACoreApiVersion(s string) bool {
|
|
||||||
if len(s) < 2 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if s[0:1] != "v" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return s[1:2] == "1" || s[1:2] == "2"
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseGV parses apiVersion field into group and version.
|
|
||||||
func parseGV(apiVersion string) (group, version string) {
|
|
||||||
// parse the group and version from the apiVersion field
|
|
||||||
parts := strings.SplitN(apiVersion, "/", 2)
|
|
||||||
group = parts[0]
|
|
||||||
if len(parts) > 1 {
|
|
||||||
version = parts[1]
|
|
||||||
}
|
|
||||||
// Special case the original "apiVersion" of what
|
|
||||||
// we now call the "core" (empty) group.
|
|
||||||
if version == "" && looksLikeACoreApiVersion(group) {
|
|
||||||
version = group
|
|
||||||
group = ""
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetGVK parses the metadata into a GVK
|
|
||||||
func GetGVK(meta yaml.ResourceMeta) resid.Gvk {
|
|
||||||
group, version := parseGV(meta.APIVersion)
|
|
||||||
return resid.Gvk{
|
|
||||||
Group: group,
|
|
||||||
Version: version,
|
|
||||||
Kind: meta.Kind,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
// Copyright 2019 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
package fsslice
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"sigs.k8s.io/kustomize/api/resid"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestParseGV(t *testing.T) {
|
|
||||||
testCases := map[string]struct {
|
|
||||||
input string
|
|
||||||
expectedGroup string
|
|
||||||
expectedVersion string
|
|
||||||
}{
|
|
||||||
"empty": {
|
|
||||||
input: "",
|
|
||||||
expectedGroup: "",
|
|
||||||
expectedVersion: "",
|
|
||||||
},
|
|
||||||
"certSigning": {
|
|
||||||
input: "certificates.k8s.io/v1beta1",
|
|
||||||
expectedGroup: "certificates.k8s.io",
|
|
||||||
expectedVersion: "v1beta1",
|
|
||||||
},
|
|
||||||
"extensions": {
|
|
||||||
input: "extensions/v1beta1",
|
|
||||||
expectedGroup: "extensions",
|
|
||||||
expectedVersion: "v1beta1",
|
|
||||||
},
|
|
||||||
"normal": {
|
|
||||||
input: "apps/v1",
|
|
||||||
expectedGroup: "apps",
|
|
||||||
expectedVersion: "v1",
|
|
||||||
},
|
|
||||||
"justApps": {
|
|
||||||
input: "apps",
|
|
||||||
expectedGroup: "apps",
|
|
||||||
expectedVersion: "",
|
|
||||||
},
|
|
||||||
"coreV1": {
|
|
||||||
input: "v1",
|
|
||||||
expectedGroup: "",
|
|
||||||
expectedVersion: "v1",
|
|
||||||
},
|
|
||||||
"coreV2": {
|
|
||||||
input: "v2",
|
|
||||||
expectedGroup: "",
|
|
||||||
expectedVersion: "v2",
|
|
||||||
},
|
|
||||||
"coreV2Beta1": {
|
|
||||||
input: "v2beta1",
|
|
||||||
expectedGroup: "",
|
|
||||||
expectedVersion: "v2beta1",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for tn, tc := range testCases {
|
|
||||||
t.Run(tn, func(t *testing.T) {
|
|
||||||
group, version := parseGV(tc.input)
|
|
||||||
if !assert.Equal(t, tc.expectedGroup, group) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
if !assert.Equal(t, tc.expectedVersion, version) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetGVK(t *testing.T) {
|
|
||||||
testCases := map[string]struct {
|
|
||||||
input string
|
|
||||||
expected resid.Gvk
|
|
||||||
parseError string
|
|
||||||
metaError string
|
|
||||||
}{
|
|
||||||
"empty": {
|
|
||||||
input: `
|
|
||||||
`,
|
|
||||||
parseError: "EOF",
|
|
||||||
},
|
|
||||||
"junk": {
|
|
||||||
input: `
|
|
||||||
congress: effective
|
|
||||||
`,
|
|
||||||
metaError: "missing Resource metadata",
|
|
||||||
},
|
|
||||||
"normal": {
|
|
||||||
input: `
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
`,
|
|
||||||
expected: resid.Gvk{Group: "apps", Version: "v1", Kind: "Deployment"},
|
|
||||||
},
|
|
||||||
"apiVersionOnlyWithSlash": {
|
|
||||||
input: `
|
|
||||||
apiVersion: apps/v1
|
|
||||||
`,
|
|
||||||
expected: resid.Gvk{Group: "apps", Version: "v1", Kind: ""},
|
|
||||||
},
|
|
||||||
"apiVersionOnlyNoSlash1": {
|
|
||||||
input: `
|
|
||||||
apiVersion: apps
|
|
||||||
`,
|
|
||||||
expected: resid.Gvk{Group: "apps", Version: "", Kind: ""},
|
|
||||||
},
|
|
||||||
"apiVersionOnlyNoSlash2": {
|
|
||||||
input: `
|
|
||||||
apiVersion: v1
|
|
||||||
`,
|
|
||||||
expected: resid.Gvk{Group: "", Version: "v1", Kind: ""},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for tn, tc := range testCases {
|
|
||||||
t.Run(tn, func(t *testing.T) {
|
|
||||||
obj, err := yaml.Parse(tc.input)
|
|
||||||
if len(tc.parseError) != 0 {
|
|
||||||
if err == nil {
|
|
||||||
t.Error("expected parse error")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !strings.Contains(err.Error(), tc.parseError) {
|
|
||||||
t.Errorf("expected parse err '%s', got '%v'", tc.parseError, err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !assert.NoError(t, err) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
meta, err := obj.GetMeta()
|
|
||||||
if len(tc.metaError) != 0 {
|
|
||||||
if err == nil {
|
|
||||||
t.Error("expected meta error")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !strings.Contains(err.Error(), tc.metaError) {
|
|
||||||
t.Errorf("expected meta err '%s', got '%v'", tc.metaError, err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !assert.NoError(t, err) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
gvk := GetGVK(meta)
|
|
||||||
if !assert.Equal(t, tc.expected, gvk) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
3
api/filters/iampolicygenerator/doc.go
Normal file
3
api/filters/iampolicygenerator/doc.go
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
// Package gkesagenerator contains a kio.Filter that that generates a
|
||||||
|
// iampolicy-related resources for a given cloud provider
|
||||||
|
package iampolicygenerator
|
||||||
46
api/filters/iampolicygenerator/example_test.go
Normal file
46
api/filters/iampolicygenerator/example_test.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
// Copyright 2021 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package iampolicygenerator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleFilter() {
|
||||||
|
f := Filter{}
|
||||||
|
var err = yaml.Unmarshal([]byte(`
|
||||||
|
cloud: gke
|
||||||
|
kubernetesService:
|
||||||
|
namespace: k8s-namespace
|
||||||
|
name: k8s-sa-name
|
||||||
|
serviceAccount:
|
||||||
|
name: gsa-name
|
||||||
|
projectId: project-id
|
||||||
|
`), &f)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = kio.Pipeline{
|
||||||
|
Inputs: []kio.Reader{},
|
||||||
|
Filters: []kio.Filter{f},
|
||||||
|
Outputs: []kio.Writer{kio.ByteWriter{Writer: os.Stdout}},
|
||||||
|
}.Execute()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// apiVersion: v1
|
||||||
|
// kind: ServiceAccount
|
||||||
|
// metadata:
|
||||||
|
// annotations:
|
||||||
|
// iam.gke.io/gcp-service-account: gsa-name@project-id.iam.gserviceaccount.com
|
||||||
|
// name: k8s-sa-name
|
||||||
|
// namespace: k8s-namespace
|
||||||
|
}
|
||||||
55
api/filters/iampolicygenerator/iampolicygenerator.go
Normal file
55
api/filters/iampolicygenerator/iampolicygenerator.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
// Copyright 2021 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package iampolicygenerator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Filter struct {
|
||||||
|
IAMPolicyGenerator types.IAMPolicyGeneratorArgs `json:",inline,omitempty" yaml:",inline,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter adds a GKE service account object to nodes
|
||||||
|
func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||||
|
switch f.IAMPolicyGenerator.Cloud {
|
||||||
|
case types.GKE:
|
||||||
|
IAMPolicyResources, err := f.generateGkeIAMPolicyResources()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
nodes = append(nodes, IAMPolicyResources...)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("cloud provider %s not supported yet", f.IAMPolicyGenerator.Cloud)
|
||||||
|
}
|
||||||
|
return nodes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Filter) generateGkeIAMPolicyResources() ([]*yaml.RNode, error) {
|
||||||
|
var result []*yaml.RNode
|
||||||
|
input := fmt.Sprintf(`
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
iam.gke.io/gcp-service-account: %s@%s.iam.gserviceaccount.com
|
||||||
|
name: %s
|
||||||
|
`, f.IAMPolicyGenerator.ServiceAccount.Name,
|
||||||
|
f.IAMPolicyGenerator.ProjectId,
|
||||||
|
f.IAMPolicyGenerator.KubernetesService.Name)
|
||||||
|
|
||||||
|
if f.IAMPolicyGenerator.Namespace != "" {
|
||||||
|
input = input + fmt.Sprintf("\n namespace: %s", f.IAMPolicyGenerator.Namespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
sa, err := yaml.Parse(input)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return append(result, sa), nil
|
||||||
|
}
|
||||||
75
api/filters/iampolicygenerator/iampolicygenerator_test.go
Normal file
75
api/filters/iampolicygenerator/iampolicygenerator_test.go
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
// Copyright 2021 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package iampolicygenerator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
filtertest "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
||||||
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFilter(t *testing.T) {
|
||||||
|
testCases := map[string]struct {
|
||||||
|
args types.IAMPolicyGeneratorArgs
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
"with namespace": {
|
||||||
|
args: types.IAMPolicyGeneratorArgs{
|
||||||
|
Cloud: types.GKE,
|
||||||
|
KubernetesService: types.KubernetesService{
|
||||||
|
Namespace: "k8s-namespace",
|
||||||
|
Name: "k8s-sa-name",
|
||||||
|
},
|
||||||
|
ServiceAccount: types.ServiceAccount{
|
||||||
|
Name: "gsa-name",
|
||||||
|
ProjectId: "project-id",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: `
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
iam.gke.io/gcp-service-account: gsa-name@project-id.iam.gserviceaccount.com
|
||||||
|
name: k8s-sa-name
|
||||||
|
namespace: k8s-namespace
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
"without namespace": {
|
||||||
|
args: types.IAMPolicyGeneratorArgs{
|
||||||
|
Cloud: types.GKE,
|
||||||
|
KubernetesService: types.KubernetesService{
|
||||||
|
Name: "k8s-sa-name",
|
||||||
|
},
|
||||||
|
ServiceAccount: types.ServiceAccount{
|
||||||
|
Name: "gsa-name",
|
||||||
|
ProjectId: "project-id",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: `
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
iam.gke.io/gcp-service-account: gsa-name@project-id.iam.gserviceaccount.com
|
||||||
|
name: k8s-sa-name
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for tn, tc := range testCases {
|
||||||
|
t.Run(tn, func(t *testing.T) {
|
||||||
|
f := Filter{
|
||||||
|
IAMPolicyGenerator: tc.args,
|
||||||
|
}
|
||||||
|
actual := filtertest.RunFilter(t, "", f)
|
||||||
|
if !assert.Equal(t, strings.TrimSpace(tc.expected), strings.TrimSpace(actual)) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,21 +4,36 @@
|
|||||||
package imagetag
|
package imagetag
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sigs.k8s.io/kustomize/api/filters/filtersutil"
|
||||||
"sigs.k8s.io/kustomize/api/filters/fsslice"
|
"sigs.k8s.io/kustomize/api/filters/fsslice"
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
"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 {
|
type Filter struct {
|
||||||
// imageTag is the tag we want to apply to the inputs
|
// 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"`
|
ImageTag types.Image `json:"imageTag,omitempty" yaml:"imageTag,omitempty"`
|
||||||
|
|
||||||
// FsSlice contains the FieldSpecs to locate the namespace field
|
// FsSlice contains the FieldSpecs to locate an image field,
|
||||||
|
// e.g. Path: "spec/myContainers[]/image"
|
||||||
FsSlice types.FsSlice `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
|
FsSlice types.FsSlice `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
|
||||||
|
|
||||||
|
trackableSetter filtersutil.TrackableSetter
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ kio.Filter = Filter{}
|
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) {
|
func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||||
_, err := kio.FilterAll(yaml.FilterFunc(f.filter)).Filter(nodes)
|
_, err := kio.FilterAll(yaml.FilterFunc(f.filter)).Filter(nodes)
|
||||||
@@ -26,19 +41,32 @@ func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f Filter) filter(node *yaml.RNode) (*yaml.RNode, error) {
|
func (f Filter) filter(node *yaml.RNode) (*yaml.RNode, error) {
|
||||||
|
// FsSlice is an allowlist, not a denyList, so to deny
|
||||||
|
// something via configuration a new config mechanism is
|
||||||
|
// needed. Until then, hardcode it.
|
||||||
|
if f.isOnDenyList(node) {
|
||||||
|
return node, nil
|
||||||
|
}
|
||||||
if err := node.PipeE(fsslice.Filter{
|
if err := node.PipeE(fsslice.Filter{
|
||||||
FsSlice: f.FsSlice,
|
FsSlice: f.FsSlice,
|
||||||
SetValue: updateImageTagFn(f.ImageTag),
|
SetValue: imageTagUpdater{
|
||||||
|
ImageTag: f.ImageTag,
|
||||||
|
trackableSetter: f.trackableSetter,
|
||||||
|
}.SetImageValue,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return node, nil
|
return node, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateImageTagFn(imageTag types.Image) fsslice.SetFn {
|
func (f Filter) isOnDenyList(node *yaml.RNode) bool {
|
||||||
return func(node *yaml.RNode) error {
|
meta, err := node.GetMeta()
|
||||||
return node.PipeE(imageTagUpdater{
|
if err != nil {
|
||||||
ImageTag: imageTag,
|
// 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`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,15 +10,175 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
filtertest "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
filtertest "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestImageTagUpdater_Filter(t *testing.T) {
|
func TestImageTagUpdater_Filter(t *testing.T) {
|
||||||
|
mutationTrackerStub := filtertest.MutationTrackerStub{}
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
input string
|
input string
|
||||||
expectedOutput string
|
expectedOutput string
|
||||||
filter Filter
|
filter Filter
|
||||||
fsSlice types.FsSlice
|
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": {
|
"update with digest": {
|
||||||
input: `
|
input: `
|
||||||
apiVersion: example.com/v1
|
apiVersion: example.com/v1
|
||||||
@@ -49,6 +209,7 @@ spec:
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
"multiple matches in sequence": {
|
"multiple matches in sequence": {
|
||||||
input: `
|
input: `
|
||||||
apiVersion: example.com/v1
|
apiVersion: example.com/v1
|
||||||
@@ -85,17 +246,524 @@ spec:
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"new Tag": {
|
||||||
|
input: `
|
||||||
|
group: apps
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: deploy1
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- image: nginx:1.7.9
|
||||||
|
name: nginx-tagged
|
||||||
|
- image: nginx:latest
|
||||||
|
name: nginx-latest
|
||||||
|
- image: foobar:1
|
||||||
|
name: replaced-with-digest
|
||||||
|
- image: postgres:1.8.0
|
||||||
|
name: postgresdb
|
||||||
|
initContainers:
|
||||||
|
- image: nginx
|
||||||
|
name: nginx-notag
|
||||||
|
- image: nginx@sha256:111111111111111111
|
||||||
|
name: nginx-sha256
|
||||||
|
- image: alpine:1.8.0
|
||||||
|
name: init-alpine`,
|
||||||
|
expectedOutput: `
|
||||||
|
group: apps
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: deploy1
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- image: nginx:v2
|
||||||
|
name: nginx-tagged
|
||||||
|
- image: nginx:v2
|
||||||
|
name: nginx-latest
|
||||||
|
- image: foobar:1
|
||||||
|
name: replaced-with-digest
|
||||||
|
- image: postgres:1.8.0
|
||||||
|
name: postgresdb
|
||||||
|
initContainers:
|
||||||
|
- image: nginx:v2
|
||||||
|
name: nginx-notag
|
||||||
|
- image: nginx:v2
|
||||||
|
name: nginx-sha256
|
||||||
|
- image: alpine:1.8.0
|
||||||
|
name: init-alpine
|
||||||
|
`,
|
||||||
|
filter: Filter{
|
||||||
|
ImageTag: types.Image{
|
||||||
|
Name: "nginx",
|
||||||
|
NewTag: "v2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fsSlice: []types.FieldSpec{
|
||||||
|
{
|
||||||
|
Path: "spec/template/spec/containers[]/image",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Path: "spec/template/spec/initContainers[]/image",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"newImage": {
|
||||||
|
input: `
|
||||||
|
group: apps
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: deploy1
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- image: nginx:1.7.9
|
||||||
|
name: nginx-tagged
|
||||||
|
- image: nginx:latest
|
||||||
|
name: nginx-latest
|
||||||
|
- image: foobar:1
|
||||||
|
name: replaced-with-digest
|
||||||
|
- image: postgres:1.8.0
|
||||||
|
name: postgresdb
|
||||||
|
initContainers:
|
||||||
|
- image: nginx
|
||||||
|
name: nginx-notag
|
||||||
|
- image: nginx@sha256:111111111111111111
|
||||||
|
name: nginx-sha256
|
||||||
|
- image: alpine:1.8.0
|
||||||
|
name: init-alpine
|
||||||
|
`,
|
||||||
|
expectedOutput: `
|
||||||
|
group: apps
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: deploy1
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- image: busybox:1.7.9
|
||||||
|
name: nginx-tagged
|
||||||
|
- image: busybox:latest
|
||||||
|
name: nginx-latest
|
||||||
|
- image: foobar:1
|
||||||
|
name: replaced-with-digest
|
||||||
|
- image: postgres:1.8.0
|
||||||
|
name: postgresdb
|
||||||
|
initContainers:
|
||||||
|
- image: busybox
|
||||||
|
name: nginx-notag
|
||||||
|
- image: busybox@sha256:111111111111111111
|
||||||
|
name: nginx-sha256
|
||||||
|
- image: alpine:1.8.0
|
||||||
|
name: init-alpine
|
||||||
|
`,
|
||||||
|
filter: Filter{
|
||||||
|
ImageTag: types.Image{
|
||||||
|
Name: "nginx",
|
||||||
|
NewName: "busybox",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fsSlice: []types.FieldSpec{
|
||||||
|
{
|
||||||
|
Path: "spec/template/spec/containers[]/image",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Path: "spec/template/spec/initContainers[]/image",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"newImageAndTag": {
|
||||||
|
input: `
|
||||||
|
group: apps
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: deploy1
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- image: nginx:1.7.9
|
||||||
|
name: nginx-tagged
|
||||||
|
- image: nginx:latest
|
||||||
|
name: nginx-latest
|
||||||
|
- image: foobar:1
|
||||||
|
name: replaced-with-digest
|
||||||
|
- image: postgres:1.8.0
|
||||||
|
name: postgresdb
|
||||||
|
initContainers:
|
||||||
|
- image: nginx
|
||||||
|
name: nginx-notag
|
||||||
|
- image: nginx@sha256:111111111111111111
|
||||||
|
name: nginx-sha256
|
||||||
|
- image: alpine:1.8.0
|
||||||
|
name: init-alpine
|
||||||
|
`,
|
||||||
|
expectedOutput: `
|
||||||
|
group: apps
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: deploy1
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- image: busybox:v3
|
||||||
|
name: nginx-tagged
|
||||||
|
- image: busybox:v3
|
||||||
|
name: nginx-latest
|
||||||
|
- image: foobar:1
|
||||||
|
name: replaced-with-digest
|
||||||
|
- image: postgres:1.8.0
|
||||||
|
name: postgresdb
|
||||||
|
initContainers:
|
||||||
|
- image: busybox:v3
|
||||||
|
name: nginx-notag
|
||||||
|
- image: busybox:v3
|
||||||
|
name: nginx-sha256
|
||||||
|
- image: alpine:1.8.0
|
||||||
|
name: init-alpine
|
||||||
|
`,
|
||||||
|
filter: Filter{
|
||||||
|
ImageTag: types.Image{
|
||||||
|
Name: "nginx",
|
||||||
|
NewName: "busybox",
|
||||||
|
NewTag: "v3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fsSlice: []types.FieldSpec{
|
||||||
|
{
|
||||||
|
Path: "spec/template/spec/containers[]/image",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Path: "spec/template/spec/initContainers[]/image",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"newDigest": {
|
||||||
|
input: `
|
||||||
|
group: apps
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: deploy1
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- image: nginx:1.7.9
|
||||||
|
name: nginx-tagged
|
||||||
|
- image: nginx:latest
|
||||||
|
name: nginx-latest
|
||||||
|
- image: foobar:1
|
||||||
|
name: replaced-with-digest
|
||||||
|
- image: postgres:1.8.0
|
||||||
|
name: postgresdb
|
||||||
|
initContainers:
|
||||||
|
- image: nginx
|
||||||
|
name: nginx-notag
|
||||||
|
- image: nginx@sha256:111111111111111111
|
||||||
|
name: nginx-sha256
|
||||||
|
- image: alpine:1.8.0
|
||||||
|
name: init-alpine
|
||||||
|
`,
|
||||||
|
expectedOutput: `
|
||||||
|
group: apps
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: deploy1
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- image: nginx@sha256:222222222222222222
|
||||||
|
name: nginx-tagged
|
||||||
|
- image: nginx@sha256:222222222222222222
|
||||||
|
name: nginx-latest
|
||||||
|
- image: foobar:1
|
||||||
|
name: replaced-with-digest
|
||||||
|
- image: postgres:1.8.0
|
||||||
|
name: postgresdb
|
||||||
|
initContainers:
|
||||||
|
- image: nginx@sha256:222222222222222222
|
||||||
|
name: nginx-notag
|
||||||
|
- image: nginx@sha256:222222222222222222
|
||||||
|
name: nginx-sha256
|
||||||
|
- image: alpine:1.8.0
|
||||||
|
name: init-alpine
|
||||||
|
`,
|
||||||
|
filter: Filter{
|
||||||
|
ImageTag: types.Image{
|
||||||
|
Name: "nginx",
|
||||||
|
Digest: "sha256:222222222222222222",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fsSlice: []types.FieldSpec{
|
||||||
|
{
|
||||||
|
Path: "spec/template/spec/containers/image",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Path: "spec/template/spec/initContainers/image",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"newImageAndDigest": {
|
||||||
|
input: `
|
||||||
|
group: apps
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: deploy1
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- image: nginx:1.7.9
|
||||||
|
name: nginx-tagged
|
||||||
|
- image: nginx:latest
|
||||||
|
name: nginx-latest
|
||||||
|
- image: foobar:1
|
||||||
|
name: replaced-with-digest
|
||||||
|
- image: postgres:1.8.0
|
||||||
|
name: postgresdb
|
||||||
|
initContainers:
|
||||||
|
- image: nginx
|
||||||
|
name: nginx-notag
|
||||||
|
- image: nginx@sha256:111111111111111111
|
||||||
|
name: nginx-sha256
|
||||||
|
- image: alpine:1.8.0
|
||||||
|
name: init-alpine
|
||||||
|
`,
|
||||||
|
expectedOutput: `
|
||||||
|
group: apps
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: deploy1
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- image: busybox@sha256:222222222222222222
|
||||||
|
name: nginx-tagged
|
||||||
|
- image: busybox@sha256:222222222222222222
|
||||||
|
name: nginx-latest
|
||||||
|
- image: foobar:1
|
||||||
|
name: replaced-with-digest
|
||||||
|
- image: postgres:1.8.0
|
||||||
|
name: postgresdb
|
||||||
|
initContainers:
|
||||||
|
- image: busybox@sha256:222222222222222222
|
||||||
|
name: nginx-notag
|
||||||
|
- image: busybox@sha256:222222222222222222
|
||||||
|
name: nginx-sha256
|
||||||
|
- image: alpine:1.8.0
|
||||||
|
name: init-alpine
|
||||||
|
`,
|
||||||
|
filter: Filter{
|
||||||
|
ImageTag: types.Image{
|
||||||
|
Name: "nginx",
|
||||||
|
NewName: "busybox",
|
||||||
|
Digest: "sha256:222222222222222222",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fsSlice: []types.FieldSpec{
|
||||||
|
{
|
||||||
|
Path: "spec/template/spec/containers[]/image",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Path: "spec/template/spec/initContainers[]/image",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"emptyContainers": {
|
||||||
|
input: `
|
||||||
|
group: apps
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: deploy1
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
`,
|
||||||
|
expectedOutput: `
|
||||||
|
group: apps
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: deploy1
|
||||||
|
spec:
|
||||||
|
containers: []
|
||||||
|
`,
|
||||||
|
filter: Filter{
|
||||||
|
ImageTag: types.Image{
|
||||||
|
Name: "nginx",
|
||||||
|
NewTag: "v2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fsSlice: []types.FieldSpec{
|
||||||
|
{
|
||||||
|
Path: "spec/containers[]/image",
|
||||||
|
// CreateIfNotPresent: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"tagWithBraces": {
|
||||||
|
input: `
|
||||||
|
group: apps
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: deploy1
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- image: some.registry.io/my-image:{GENERATED_TAG}
|
||||||
|
name: my-image
|
||||||
|
`,
|
||||||
|
expectedOutput: `
|
||||||
|
group: apps
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: deploy1
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- image: some.registry.io/my-image:my-fixed-tag
|
||||||
|
name: my-image
|
||||||
|
`,
|
||||||
|
filter: Filter{
|
||||||
|
ImageTag: types.Image{
|
||||||
|
Name: "some.registry.io/my-image",
|
||||||
|
NewTag: "my-fixed-tag",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fsSlice: []types.FieldSpec{
|
||||||
|
{
|
||||||
|
Path: "spec/template/spec/containers[]/image",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Path: "spec/template/spec/initContainers[]/image",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"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 {
|
for tn, tc := range testCases {
|
||||||
|
mutationTrackerStub.Reset()
|
||||||
t.Run(tn, func(t *testing.T) {
|
t.Run(tn, func(t *testing.T) {
|
||||||
filter := tc.filter
|
filter := tc.filter
|
||||||
|
filter.WithMutationTracker(tc.setValueCallback)
|
||||||
filter.FsSlice = tc.fsSlice
|
filter.FsSlice = tc.fsSlice
|
||||||
if !assert.Equal(t,
|
if !assert.Equal(t,
|
||||||
strings.TrimSpace(tc.expectedOutput),
|
strings.TrimSpace(tc.expectedOutput),
|
||||||
strings.TrimSpace(filtertest.RunFilter(t, tc.input, filter))) {
|
strings.TrimSpace(filtertest.RunFilter(t, tc.input, filter))) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
assert.Equal(t, tc.expectedSetValueArgs, mutationTrackerStub.SetValueArgs())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
package imagetag
|
package imagetag
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sigs.k8s.io/kustomize/api/internal/utils"
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
@@ -74,7 +75,7 @@ func (f findFieldsFilter) walk(node *yaml.RNode) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
key := n.Key.YNode().Value
|
key := n.Key.YNode().Value
|
||||||
if contains(f.fields, key) {
|
if utils.StringSliceContains(f.fields, key) {
|
||||||
return f.fieldCallback(n.Value)
|
return f.fieldCallback(n.Value)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -87,15 +88,6 @@ func (f findFieldsFilter) walk(node *yaml.RNode) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func contains(slice []string, str string) bool {
|
|
||||||
for _, s := range slice {
|
|
||||||
if s == str {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkImageTagsFn(imageTag types.Image) fieldCallback {
|
func checkImageTagsFn(imageTag types.Image) fieldCallback {
|
||||||
return func(node *yaml.RNode) error {
|
return func(node *yaml.RNode) error {
|
||||||
if node.YNode().Kind != yaml.SequenceNode {
|
if node.YNode().Kind != yaml.SequenceNode {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
package imagetag
|
package imagetag
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sigs.k8s.io/kustomize/api/filters/filtersutil"
|
||||||
"sigs.k8s.io/kustomize/api/image"
|
"sigs.k8s.io/kustomize/api/image"
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
@@ -13,19 +14,20 @@ import (
|
|||||||
// that will update the value of the yaml node based on the provided
|
// 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.
|
// ImageTag if the current value matches the format of an image reference.
|
||||||
type imageTagUpdater struct {
|
type imageTagUpdater struct {
|
||||||
Kind string `yaml:"kind,omitempty"`
|
Kind string `yaml:"kind,omitempty"`
|
||||||
ImageTag types.Image `yaml:"imageTag,omitempty"`
|
ImageTag types.Image `yaml:"imageTag,omitempty"`
|
||||||
|
trackableSetter filtersutil.TrackableSetter
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u imageTagUpdater) Filter(rn *yaml.RNode) (*yaml.RNode, error) {
|
func (u imageTagUpdater) SetImageValue(rn *yaml.RNode) error {
|
||||||
if err := yaml.ErrorIfInvalid(rn, yaml.ScalarNode); err != nil {
|
if err := yaml.ErrorIfInvalid(rn, yaml.ScalarNode); err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
value := rn.YNode().Value
|
value := rn.YNode().Value
|
||||||
|
|
||||||
if !image.IsImageMatched(value, u.ImageTag.Name) {
|
if !image.IsImageMatched(value, u.ImageTag.Name) {
|
||||||
return rn, nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
name, tag := image.Split(value)
|
name, tag := image.Split(value)
|
||||||
@@ -39,5 +41,12 @@ func (u imageTagUpdater) Filter(rn *yaml.RNode) (*yaml.RNode, error) {
|
|||||||
tag = "@" + u.ImageTag.Digest
|
tag = "@" + u.ImageTag.Digest
|
||||||
}
|
}
|
||||||
|
|
||||||
return rn.Pipe(yaml.FieldSetter{StringValue: name + tag})
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,20 +20,29 @@ type Filter struct {
|
|||||||
|
|
||||||
// FsSlice identifies the label fields.
|
// FsSlice identifies the label fields.
|
||||||
FsSlice types.FsSlice
|
FsSlice types.FsSlice
|
||||||
|
|
||||||
|
trackableSetter filtersutil.TrackableSetter
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ kio.Filter = Filter{}
|
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) {
|
func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||||
keys := filtersutil.SortedMapKeys(f.Labels)
|
keys := yaml.SortedMapKeys(f.Labels)
|
||||||
_, err := kio.FilterAll(yaml.FilterFunc(
|
_, err := kio.FilterAll(yaml.FilterFunc(
|
||||||
func(node *yaml.RNode) (*yaml.RNode, error) {
|
func(node *yaml.RNode) (*yaml.RNode, error) {
|
||||||
for _, k := range keys {
|
for _, k := range keys {
|
||||||
if err := node.PipeE(fsslice.Filter{
|
if err := node.PipeE(fsslice.Filter{
|
||||||
FsSlice: f.FsSlice,
|
FsSlice: f.FsSlice,
|
||||||
SetValue: fsslice.SetEntry(k, f.Labels[k], yaml.StringTag),
|
SetValue: f.trackableSetter.SetEntry(
|
||||||
|
k, f.Labels[k], yaml.NodeTagString),
|
||||||
CreateKind: yaml.MappingNode, // Labels are MappingNodes.
|
CreateKind: yaml.MappingNode, // Labels are MappingNodes.
|
||||||
CreateTag: "!!map",
|
CreateTag: yaml.NodeTagMap,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,16 +8,20 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"sigs.k8s.io/kustomize/api/resid"
|
|
||||||
filtertest_test "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
filtertest_test "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/resid"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLabels_Filter(t *testing.T) {
|
func TestLabels_Filter(t *testing.T) {
|
||||||
|
mutationTrackerStub := filtertest_test.MutationTrackerStub{}
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
input string
|
input string
|
||||||
expectedOutput string
|
expectedOutput string
|
||||||
filter Filter
|
filter Filter
|
||||||
|
setEntryCallback func(key, value, tag string, node *yaml.RNode)
|
||||||
|
expectedSetEntryArgs []filtertest_test.SetValueArg
|
||||||
}{
|
}{
|
||||||
"add": {
|
"add": {
|
||||||
input: `
|
input: `
|
||||||
@@ -399,15 +403,74 @@ metadata:
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 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 {
|
for tn, tc := range testCases {
|
||||||
|
mutationTrackerStub.Reset()
|
||||||
t.Run(tn, func(t *testing.T) {
|
t.Run(tn, func(t *testing.T) {
|
||||||
|
tc.filter.WithMutationTracker(tc.setEntryCallback)
|
||||||
if !assert.Equal(t,
|
if !assert.Equal(t,
|
||||||
strings.TrimSpace(tc.expectedOutput),
|
strings.TrimSpace(tc.expectedOutput),
|
||||||
strings.TrimSpace(filtertest_test.RunFilter(t, tc.input, tc.filter))) {
|
strings.TrimSpace(filtertest_test.RunFilter(t, tc.input, tc.filter))) {
|
||||||
t.FailNow()
|
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")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,9 +4,11 @@
|
|||||||
package namespace
|
package namespace
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sigs.k8s.io/kustomize/api/filters/filtersutil"
|
||||||
"sigs.k8s.io/kustomize/api/filters/fsslice"
|
"sigs.k8s.io/kustomize/api/filters/fsslice"
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/resid"
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,9 +18,17 @@ type Filter struct {
|
|||||||
|
|
||||||
// FsSlice contains the FieldSpecs to locate the namespace field
|
// FsSlice contains the FieldSpecs to locate the namespace field
|
||||||
FsSlice types.FsSlice `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
|
FsSlice types.FsSlice `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
|
||||||
|
|
||||||
|
trackableSetter filtersutil.TrackableSetter
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ kio.Filter = Filter{}
|
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) {
|
func (ns Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||||
return kio.FilterAll(yaml.FilterFunc(ns.run)).Filter(nodes)
|
return kio.FilterAll(yaml.FilterFunc(ns.run)).Filter(nodes)
|
||||||
@@ -42,9 +52,9 @@ func (ns Filter) run(node *yaml.RNode) (*yaml.RNode, error) {
|
|||||||
// transformations based on data -- :)
|
// transformations based on data -- :)
|
||||||
err := node.PipeE(fsslice.Filter{
|
err := node.PipeE(fsslice.Filter{
|
||||||
FsSlice: ns.FsSlice,
|
FsSlice: ns.FsSlice,
|
||||||
SetValue: fsslice.SetScalar(ns.Namespace),
|
SetValue: ns.trackableSetter.SetScalar(ns.Namespace),
|
||||||
CreateKind: yaml.ScalarNode, // Namespace is a ScalarNode
|
CreateKind: yaml.ScalarNode, // Namespace is a ScalarNode
|
||||||
CreateTag: yaml.StringTag,
|
CreateTag: yaml.NodeTagString,
|
||||||
})
|
})
|
||||||
return node, err
|
return node, err
|
||||||
}
|
}
|
||||||
@@ -52,36 +62,30 @@ func (ns Filter) run(node *yaml.RNode) (*yaml.RNode, error) {
|
|||||||
// hacks applies the namespace transforms that are hardcoded rather
|
// hacks applies the namespace transforms that are hardcoded rather
|
||||||
// than specified through FieldSpecs.
|
// than specified through FieldSpecs.
|
||||||
func (ns Filter) hacks(obj *yaml.RNode) error {
|
func (ns Filter) hacks(obj *yaml.RNode) error {
|
||||||
meta, err := obj.GetMeta()
|
gvk := resid.GvkFromNode(obj)
|
||||||
if err != nil {
|
if err := ns.metaNamespaceHack(obj, gvk); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
return ns.roleBindingHack(obj, gvk)
|
||||||
if err := ns.metaNamespaceHack(obj, meta); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return ns.roleBindingHack(obj, meta)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// metaNamespaceHack is a hack for implementing the namespace transform
|
// metaNamespaceHack is a hack for implementing the namespace transform
|
||||||
// for the metadata.namespace field on namespace scoped resources.
|
// for the metadata.namespace field on namespace scoped resources.
|
||||||
// namespace scoped resources are determined by NOT being present
|
// namespace scoped resources are determined by NOT being present
|
||||||
// in a blacklist of cluster-scoped resource types (by apiVersion and kind).
|
// in a hard-coded list of cluster-scoped resource types (by apiVersion and kind).
|
||||||
//
|
//
|
||||||
// This hack should be updated to allow individual resources to specify
|
// This hack should be updated to allow individual resources to specify
|
||||||
// if they are cluster scoped through either an annotation on the resources,
|
// if they are cluster scoped through either an annotation on the resources,
|
||||||
// or through inlined OpenAPI on the resource as a YAML comment.
|
// or through inlined OpenAPI on the resource as a YAML comment.
|
||||||
func (ns Filter) metaNamespaceHack(obj *yaml.RNode, meta yaml.ResourceMeta) error {
|
func (ns Filter) metaNamespaceHack(obj *yaml.RNode, gvk resid.Gvk) error {
|
||||||
gvk := fsslice.GetGVK(meta)
|
if gvk.IsClusterScoped() {
|
||||||
if !gvk.IsNamespaceableKind() {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
f := fsslice.Filter{
|
f := fsslice.Filter{
|
||||||
FsSlice: []types.FieldSpec{
|
FsSlice: []types.FieldSpec{
|
||||||
{Path: types.MetadataNamespacePath, CreateIfNotPresent: true},
|
{Path: types.MetadataNamespacePath, CreateIfNotPresent: true},
|
||||||
},
|
},
|
||||||
SetValue: fsslice.SetScalar(ns.Namespace),
|
SetValue: ns.trackableSetter.SetScalar(ns.Namespace),
|
||||||
CreateKind: yaml.ScalarNode, // Namespace is a ScalarNode
|
CreateKind: yaml.ScalarNode, // Namespace is a ScalarNode
|
||||||
}
|
}
|
||||||
_, err := f.Filter(obj)
|
_, err := f.Filter(obj)
|
||||||
@@ -102,37 +106,40 @@ func (ns Filter) metaNamespaceHack(obj *yaml.RNode, meta yaml.ResourceMeta) erro
|
|||||||
// ...
|
// ...
|
||||||
// - name: "something-else" # this will not have the namespace set
|
// - name: "something-else" # this will not have the namespace set
|
||||||
// ...
|
// ...
|
||||||
func (ns Filter) roleBindingHack(obj *yaml.RNode, meta yaml.ResourceMeta) error {
|
func (ns Filter) roleBindingHack(obj *yaml.RNode, gvk resid.Gvk) error {
|
||||||
if meta.Kind != roleBindingKind && meta.Kind != clusterRoleBindingKind {
|
if gvk.Kind != roleBindingKind && gvk.Kind != clusterRoleBindingKind {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lookup the namespace field on all elements.
|
// Lookup the namespace field on all elements.
|
||||||
// We should change the fieldspec so this isn't necessary.
|
// We should change the fieldspec so this isn't necessary.
|
||||||
obj, err := obj.Pipe(yaml.Lookup(subjectsField))
|
obj, err := obj.Pipe(yaml.Lookup(subjectsField))
|
||||||
if err != nil || yaml.IsEmpty(obj) {
|
if err != nil || yaml.IsMissingOrNull(obj) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// add the namespace to each "subject" with name: default
|
// add the namespace to each "subject" with name: default
|
||||||
err = obj.VisitElements(func(o *yaml.RNode) error {
|
err = obj.VisitElements(func(o *yaml.RNode) error {
|
||||||
// copied from kunstruct based kustomize NamespaceTransformer plugin
|
|
||||||
// The only case we need to force the namespace
|
// The only case we need to force the namespace
|
||||||
// if for the "service account". "default" is
|
// if for the "service account". "default" is
|
||||||
// kind of hardcoded here for right now.
|
// kind of hardcoded here for right now.
|
||||||
name, err := o.Pipe(
|
name, err := o.Pipe(
|
||||||
yaml.Lookup("name"), yaml.Match("default"),
|
yaml.Lookup("name"), yaml.Match("default"),
|
||||||
)
|
)
|
||||||
if err != nil || yaml.IsEmpty(name) {
|
if err != nil || yaml.IsMissingOrNull(name) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// set the namespace for the default account
|
// set the namespace for the default account
|
||||||
v := yaml.NewScalarRNode(ns.Namespace)
|
node, err := o.Pipe(
|
||||||
return o.PipeE(
|
|
||||||
yaml.LookupCreate(yaml.ScalarNode, "namespace"),
|
yaml.LookupCreate(yaml.ScalarNode, "namespace"),
|
||||||
yaml.FieldSetter{Value: v},
|
|
||||||
)
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ns.trackableSetter.SetScalar(ns.Namespace)(node)
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -12,8 +12,11 @@ import (
|
|||||||
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
||||||
filtertest_test "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
filtertest_test "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var mutationTrackerStub = filtertest_test.MutationTrackerStub{}
|
||||||
|
|
||||||
var tests = []TestCase{
|
var tests = []TestCase{
|
||||||
{
|
{
|
||||||
name: "add",
|
name: "add",
|
||||||
@@ -194,47 +197,47 @@ metadata:
|
|||||||
{
|
{
|
||||||
name: "update-clusterrolebinding",
|
name: "update-clusterrolebinding",
|
||||||
input: `
|
input: `
|
||||||
apiVersion: example.com/v1
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRoleBinding
|
kind: ClusterRoleBinding
|
||||||
subjects:
|
subjects:
|
||||||
- name: default
|
- name: default
|
||||||
---
|
---
|
||||||
apiVersion: example.com/v1
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRoleBinding
|
kind: ClusterRoleBinding
|
||||||
subjects:
|
subjects:
|
||||||
- name: default
|
- name: default
|
||||||
namespace: foo
|
namespace: foo
|
||||||
---
|
---
|
||||||
apiVersion: example.com/v1
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRoleBinding
|
kind: ClusterRoleBinding
|
||||||
subjects:
|
subjects:
|
||||||
- name: something
|
- name: something
|
||||||
---
|
---
|
||||||
apiVersion: example.com/v1
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRoleBinding
|
kind: ClusterRoleBinding
|
||||||
subjects:
|
subjects:
|
||||||
- name: something
|
- name: something
|
||||||
namespace: foo
|
namespace: foo
|
||||||
`,
|
`,
|
||||||
expected: `
|
expected: `
|
||||||
apiVersion: example.com/v1
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRoleBinding
|
kind: ClusterRoleBinding
|
||||||
subjects:
|
subjects:
|
||||||
- name: default
|
- name: default
|
||||||
namespace: bar
|
namespace: bar
|
||||||
---
|
---
|
||||||
apiVersion: example.com/v1
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRoleBinding
|
kind: ClusterRoleBinding
|
||||||
subjects:
|
subjects:
|
||||||
- name: default
|
- name: default
|
||||||
namespace: bar
|
namespace: bar
|
||||||
---
|
---
|
||||||
apiVersion: example.com/v1
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRoleBinding
|
kind: ClusterRoleBinding
|
||||||
subjects:
|
subjects:
|
||||||
- name: something
|
- name: something
|
||||||
---
|
---
|
||||||
apiVersion: example.com/v1
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRoleBinding
|
kind: ClusterRoleBinding
|
||||||
subjects:
|
subjects:
|
||||||
- name: something
|
- name: something
|
||||||
@@ -283,21 +286,91 @@ a:
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
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 {
|
type TestCase struct {
|
||||||
name string
|
name string
|
||||||
input string
|
input string
|
||||||
expected string
|
expected string
|
||||||
filter namespace.Filter
|
filter namespace.Filter
|
||||||
fsslice types.FsSlice
|
fsslice types.FsSlice
|
||||||
|
mutationTracker func(key, value, tag string, node *yaml.RNode)
|
||||||
|
expectedSetValueArgs []filtertest_test.SetValueArg
|
||||||
}
|
}
|
||||||
|
|
||||||
var config = builtinconfig.MakeDefaultConfig()
|
var config = builtinconfig.MakeDefaultConfig()
|
||||||
|
|
||||||
func TestNamespace_Filter(t *testing.T) {
|
func TestNamespace_Filter(t *testing.T) {
|
||||||
for i := range tests {
|
for i := range tests {
|
||||||
|
mutationTrackerStub.Reset()
|
||||||
test := tests[i]
|
test := tests[i]
|
||||||
|
test.filter.WithMutationTracker(test.mutationTracker)
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
test.filter.FsSlice = append(config.NameSpace, test.fsslice...)
|
test.filter.FsSlice = append(config.NameSpace, test.fsslice...)
|
||||||
if !assert.Equal(t,
|
if !assert.Equal(t,
|
||||||
@@ -306,6 +379,7 @@ func TestNamespace_Filter(t *testing.T) {
|
|||||||
filtertest_test.RunFilter(t, test.input, test.filter))) {
|
filtertest_test.RunFilter(t, test.input, test.filter))) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
assert.Equal(t, test.expectedSetValueArgs, mutationTrackerStub.SetValueArgs())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,14 +15,22 @@ type Filter struct {
|
|||||||
|
|
||||||
var _ kio.Filter = Filter{}
|
var _ kio.Filter = Filter{}
|
||||||
|
|
||||||
|
// Filter does a strategic merge patch, which can delete nodes.
|
||||||
func (pf Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
func (pf Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||||
var result []*yaml.RNode
|
var result []*yaml.RNode
|
||||||
for i := range nodes {
|
for i := range nodes {
|
||||||
r, err := merge2.Merge(pf.Patch, nodes[i])
|
r, err := merge2.Merge(
|
||||||
|
pf.Patch, nodes[i],
|
||||||
|
yaml.MergeOptions{
|
||||||
|
ListIncreaseDirection: yaml.MergeOptionsListPrepend,
|
||||||
|
},
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
result = append(result, r)
|
if r != nil {
|
||||||
|
result = append(result, r)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,101 @@ func TestFilter(t *testing.T) {
|
|||||||
patch *yaml.RNode
|
patch *yaml.RNode
|
||||||
expected string
|
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": {
|
"simple patch": {
|
||||||
input: `
|
input: `
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
@@ -67,10 +162,10 @@ spec:
|
|||||||
template:
|
template:
|
||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
|
- name: foo0
|
||||||
- name: foo1
|
- name: foo1
|
||||||
- name: foo2
|
- name: foo2
|
||||||
- name: foo3
|
- name: foo3
|
||||||
- name: foo0
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
"volumes patch": {
|
"volumes patch": {
|
||||||
@@ -107,10 +202,10 @@ spec:
|
|||||||
template:
|
template:
|
||||||
spec:
|
spec:
|
||||||
volumes:
|
volumes:
|
||||||
|
- name: foo0
|
||||||
- name: foo1
|
- name: foo1
|
||||||
- name: foo2
|
- name: foo2
|
||||||
- name: foo3
|
- name: foo3
|
||||||
- name: foo0
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
"nested patch": {
|
"nested patch": {
|
||||||
@@ -142,6 +237,498 @@ spec:
|
|||||||
- name: nginx
|
- name: nginx
|
||||||
args:
|
args:
|
||||||
- def
|
- 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"
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
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
|
||||||
@@ -1,20 +1,19 @@
|
|||||||
// Copyright 2020 The Kubernetes Authors.
|
// Copyright 2020 The Kubernetes Authors.
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
package prefixsuffix_test
|
package prefix_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/filters/prefixsuffix"
|
"sigs.k8s.io/kustomize/api/filters/prefix"
|
||||||
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ExampleFilter() {
|
func ExampleFilter() {
|
||||||
fss := builtinconfig.MakeDefaultConfig().NamePrefix
|
|
||||||
err := kio.Pipeline{
|
err := kio.Pipeline{
|
||||||
Inputs: []kio.Reader{&kio.ByteReader{Reader: bytes.NewBufferString(`
|
Inputs: []kio.Reader{&kio.ByteReader{Reader: bytes.NewBufferString(`
|
||||||
apiVersion: example.com/v1
|
apiVersion: example.com/v1
|
||||||
@@ -27,7 +26,8 @@ kind: Bar
|
|||||||
metadata:
|
metadata:
|
||||||
name: instance
|
name: instance
|
||||||
`)}},
|
`)}},
|
||||||
Filters: []kio.Filter{prefixsuffix.Filter{Prefix: "baz-", FsSlice: fss}},
|
Filters: []kio.Filter{prefix.Filter{
|
||||||
|
Prefix: "baz-", FieldSpec: types.FieldSpec{Path: "metadata/name"}}},
|
||||||
Outputs: []kio.Writer{kio.ByteWriter{Writer: os.Stdout}},
|
Outputs: []kio.Writer{kio.ByteWriter{Writer: os.Stdout}},
|
||||||
}.Execute()
|
}.Execute()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
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)
|
||||||
|
}
|
||||||
@@ -1,22 +1,23 @@
|
|||||||
// Copyright 2019 The Kubernetes Authors.
|
// Copyright 2019 The Kubernetes Authors.
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
package prefixsuffix_test
|
package prefix_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"sigs.k8s.io/kustomize/api/filters/prefixsuffix"
|
"sigs.k8s.io/kustomize/api/filters/prefix"
|
||||||
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
|
||||||
filtertest_test "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
filtertest_test "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
var tests = []TestCase{
|
var mutationTrackerStub = filtertest_test.MutationTrackerStub{}
|
||||||
{
|
|
||||||
name: "prefix",
|
var tests = map[string]TestCase{
|
||||||
|
"prefix": {
|
||||||
input: `
|
input: `
|
||||||
apiVersion: example.com/v1
|
apiVersion: example.com/v1
|
||||||
kind: Foo
|
kind: Foo
|
||||||
@@ -39,65 +40,13 @@ kind: Bar
|
|||||||
metadata:
|
metadata:
|
||||||
name: foo-instance
|
name: foo-instance
|
||||||
`,
|
`,
|
||||||
filter: prefixsuffix.Filter{Prefix: "foo-"},
|
filter: prefix.Filter{
|
||||||
|
Prefix: "foo-",
|
||||||
|
FieldSpec: types.FieldSpec{Path: "metadata/name"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
"data-fieldspecs": {
|
||||||
name: "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: prefixsuffix.Filter{Suffix: "-foo"},
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: "prefix-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: bar-instance-foo
|
|
||||||
---
|
|
||||||
apiVersion: example.com/v1
|
|
||||||
kind: Bar
|
|
||||||
metadata:
|
|
||||||
name: bar-instance-foo
|
|
||||||
`,
|
|
||||||
filter: prefixsuffix.Filter{Prefix: "bar-", Suffix: "-foo"},
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: "data-fieldspecs",
|
|
||||||
input: `
|
input: `
|
||||||
apiVersion: example.com/v1
|
apiVersion: example.com/v1
|
||||||
kind: Foo
|
kind: Foo
|
||||||
@@ -119,7 +68,7 @@ a:
|
|||||||
apiVersion: example.com/v1
|
apiVersion: example.com/v1
|
||||||
kind: Foo
|
kind: Foo
|
||||||
metadata:
|
metadata:
|
||||||
name: foo-instance
|
name: instance
|
||||||
a:
|
a:
|
||||||
b:
|
b:
|
||||||
c: foo-d
|
c: foo-d
|
||||||
@@ -127,41 +76,79 @@ a:
|
|||||||
apiVersion: example.com/v1
|
apiVersion: example.com/v1
|
||||||
kind: Bar
|
kind: Bar
|
||||||
metadata:
|
metadata:
|
||||||
name: foo-instance
|
name: instance
|
||||||
a:
|
a:
|
||||||
b:
|
b:
|
||||||
c: foo-d
|
c: foo-d
|
||||||
`,
|
`,
|
||||||
filter: prefixsuffix.Filter{Prefix: "foo-"},
|
filter: prefix.Filter{
|
||||||
fsslice: []types.FieldSpec{
|
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{
|
||||||
{
|
{
|
||||||
Path: "a/b/c",
|
Value: "foo-instance",
|
||||||
|
NodePath: []string{"metadata", "name"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: "foo-instance",
|
||||||
|
NodePath: []string{"metadata", "name"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
type TestCase struct {
|
type TestCase struct {
|
||||||
name string
|
input string
|
||||||
input string
|
expected string
|
||||||
expected string
|
filter prefix.Filter
|
||||||
filter prefixsuffix.Filter
|
mutationTracker func(key, value, tag string, node *yaml.RNode)
|
||||||
fsslice types.FsSlice
|
expectedSetValueArgs []filtertest_test.SetValueArg
|
||||||
}
|
}
|
||||||
|
|
||||||
var config = builtinconfig.MakeDefaultConfig()
|
|
||||||
|
|
||||||
func TestFilter(t *testing.T) {
|
func TestFilter(t *testing.T) {
|
||||||
for i := range tests {
|
for name := range tests {
|
||||||
test := tests[i]
|
mutationTrackerStub.Reset()
|
||||||
t.Run(test.name, func(t *testing.T) {
|
test := tests[name]
|
||||||
test.filter.FsSlice = append(config.NamePrefix, test.fsslice...)
|
test.filter.WithMutationTracker(test.mutationTracker)
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
if !assert.Equal(t,
|
if !assert.Equal(t,
|
||||||
strings.TrimSpace(test.expected),
|
strings.TrimSpace(test.expected),
|
||||||
strings.TrimSpace(
|
strings.TrimSpace(
|
||||||
filtertest_test.RunFilter(t, test.input, test.filter))) {
|
filtertest_test.RunFilter(t, test.input, test.filter))) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
assert.Equal(t, test.expectedSetValueArgs, mutationTrackerStub.SetValueArgs())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
// Copyright 2020 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
// Package prefixsuffix contains a kio.Filter implementation of the kustomize
|
|
||||||
// PrefixSuffixTransformer.
|
|
||||||
package prefixsuffix
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
// Copyright 2019 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package prefixsuffix
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"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 applies resource name prefix's and suffix's using the fieldSpecs
|
|
||||||
type Filter struct {
|
|
||||||
Prefix string `json:"prefix,omitempty" yaml:"prefix,omitempty"`
|
|
||||||
Suffix string `json:"suffix,omitempty" yaml:"suffix,omitempty"`
|
|
||||||
|
|
||||||
FsSlice types.FsSlice `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ kio.Filter = Filter{}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
// transformations based on data -- :)
|
|
||||||
err := node.PipeE(fsslice.Filter{
|
|
||||||
FsSlice: ns.FsSlice,
|
|
||||||
SetValue: ns.set,
|
|
||||||
CreateKind: yaml.ScalarNode, // Name is a ScalarNode
|
|
||||||
CreateTag: yaml.StringTag,
|
|
||||||
})
|
|
||||||
return node, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ns Filter) set(node *yaml.RNode) error {
|
|
||||||
return fsslice.SetScalar(fmt.Sprintf(
|
|
||||||
"%s%s%s", ns.Prefix, node.YNode().Value, ns.Suffix))(node)
|
|
||||||
}
|
|
||||||
3
api/filters/refvar/doc.go
Normal file
3
api/filters/refvar/doc.go
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
// Package refvar contains a kio.Filter implementation of the kustomize
|
||||||
|
// refvar transformer (find and replace $(FOO) style variables in strings).
|
||||||
|
package refvar
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
// Copyright 2019 The Kubernetes Authors.
|
// Copyright 2019 The Kubernetes Authors.
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
// Package expansion provides functions find and replace $(FOO) style variables in strings.
|
package refvar
|
||||||
package expansion
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -17,38 +17,64 @@ const (
|
|||||||
|
|
||||||
// syntaxWrap returns the input string wrapped by the expansion syntax.
|
// syntaxWrap returns the input string wrapped by the expansion syntax.
|
||||||
func syntaxWrap(input string) string {
|
func syntaxWrap(input string) string {
|
||||||
return string(operator) + string(referenceOpener) + input + string(referenceCloser)
|
var sb strings.Builder
|
||||||
|
sb.WriteByte(operator)
|
||||||
|
sb.WriteByte(referenceOpener)
|
||||||
|
sb.WriteString(input)
|
||||||
|
sb.WriteByte(referenceCloser)
|
||||||
|
return sb.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// MappingFuncFor returns a mapping function for use with Expand that
|
// MappingFunc maps a string to anything.
|
||||||
// implements the expansion semantics defined in the expansion spec; it
|
type MappingFunc func(string) interface{}
|
||||||
// returns the input string wrapped in the expansion syntax if no mapping
|
|
||||||
// for the input is found.
|
// MakePrimitiveReplacer returns a MappingFunc that uses a map to do
|
||||||
func MappingFuncFor(
|
// replacements, and a histogram to count map hits.
|
||||||
counts map[string]int,
|
//
|
||||||
context ...map[string]interface{}) func(string) interface{} {
|
// Func behavior:
|
||||||
return func(input string) interface{} {
|
//
|
||||||
for _, vars := range context {
|
// If the input key is NOT found in the map, the key is wrapped up as
|
||||||
val, ok := vars[input]
|
// as a variable declaration string and returned, e.g. key FOO becomes $(FOO).
|
||||||
if ok {
|
// This string is presumably put back where it was found, and might get replaced
|
||||||
counts[input]++
|
// later.
|
||||||
switch typedV := val.(type) {
|
//
|
||||||
case string, int64, float64, bool:
|
// If the key is found in the map, the value is returned if it is a primitive
|
||||||
return typedV
|
// type (string, bool, number), and the hit is counted.
|
||||||
default:
|
//
|
||||||
return syntaxWrap(input)
|
// If it's not a primitive type (e.g. a map, struct, func, etc.) then this
|
||||||
}
|
// function doesn't know what to do with it and it returns the key wrapped up
|
||||||
|
// again as if it had not been replaced. This should probably be an error.
|
||||||
|
func MakePrimitiveReplacer(
|
||||||
|
counts map[string]int, someMap map[string]interface{}) MappingFunc {
|
||||||
|
return func(key string) interface{} {
|
||||||
|
if value, ok := someMap[key]; ok {
|
||||||
|
switch typedV := value.(type) {
|
||||||
|
case string, int, int32, int64, float32, float64, bool:
|
||||||
|
counts[key]++
|
||||||
|
return typedV
|
||||||
|
default:
|
||||||
|
// If the value is some complicated type (e.g. a map or struct),
|
||||||
|
// this function doesn't know how to jam it into a string,
|
||||||
|
// so just pretend it was a cache miss.
|
||||||
|
// Likely this should be an error instead of a silent failure,
|
||||||
|
// since the programmer passed an impossible value.
|
||||||
|
log.Printf(
|
||||||
|
"MakePrimitiveReplacer: bad replacement type=%T val=%v",
|
||||||
|
typedV, typedV)
|
||||||
|
return syntaxWrap(key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return syntaxWrap(input)
|
// If unable to return the mapped variable, return it
|
||||||
|
// as it was found, and a later mapping might be able to
|
||||||
|
// replace it.
|
||||||
|
return syntaxWrap(key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expand replaces variable references in the input string according to
|
// DoReplacements replaces variable references in the input string
|
||||||
// the expansion spec using the given mapping function to resolve the
|
// using the mapping function.
|
||||||
// values of variables.
|
func DoReplacements(input string, mapping MappingFunc) interface{} {
|
||||||
func Expand(input string, mapping func(string) interface{}) interface{} {
|
var buf strings.Builder
|
||||||
var buf bytes.Buffer
|
|
||||||
checkpoint := 0
|
checkpoint := 0
|
||||||
for cursor := 0; cursor < len(input); cursor++ {
|
for cursor := 0; cursor < len(input); cursor++ {
|
||||||
if input[cursor] == operator && cursor+1 < len(input) {
|
if input[cursor] == operator && cursor+1 < len(input) {
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
// Copyright 2019 The Kubernetes Authors.
|
// Copyright 2019 The Kubernetes Authors.
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
package expansion_test
|
package refvar_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
. "sigs.k8s.io/kustomize/api/internal/accumulator/expansion"
|
"github.com/stretchr/testify/assert"
|
||||||
|
. "sigs.k8s.io/kustomize/api/filters/refvar"
|
||||||
)
|
)
|
||||||
|
|
||||||
type expected struct {
|
type expected struct {
|
||||||
@@ -15,6 +16,48 @@ type expected struct {
|
|||||||
edited string
|
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) {
|
func TestMapReference(t *testing.T) {
|
||||||
type env struct {
|
type env struct {
|
||||||
Name string
|
Name string
|
||||||
@@ -51,7 +94,7 @@ func TestMapReference(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
declaredEnv := map[string]interface{}{
|
varMap := map[string]interface{}{
|
||||||
"FOO": "bar",
|
"FOO": "bar",
|
||||||
"ZOO": "$(FOO)-1",
|
"ZOO": "$(FOO)-1",
|
||||||
"BLU": "$(ZOO)-2",
|
"BLU": "$(ZOO)-2",
|
||||||
@@ -61,11 +104,11 @@ func TestMapReference(t *testing.T) {
|
|||||||
"ZBOOL": "$(BOOL)",
|
"ZBOOL": "$(BOOL)",
|
||||||
}
|
}
|
||||||
|
|
||||||
counts := make(map[string]int)
|
varCounts := make(map[string]int)
|
||||||
mapping := MappingFuncFor(counts, declaredEnv)
|
|
||||||
|
|
||||||
for _, env := range envs {
|
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{
|
expectedEnv := map[string]expected{
|
||||||
@@ -79,45 +122,20 @@ func TestMapReference(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range expectedEnv {
|
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",
|
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 {
|
} else {
|
||||||
delete(declaredEnv, k)
|
delete(varMap, k)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(declaredEnv) != 0 {
|
if len(varMap) != 0 {
|
||||||
t.Errorf("Unexpected keys in declared env: %v", declaredEnv)
|
t.Errorf("Unexpected keys in declared env: %v", varMap)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMapping(t *testing.T) {
|
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 {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
input string
|
input string
|
||||||
@@ -333,11 +351,17 @@ func doExpansionTest(t *testing.T, context ...map[string]interface{}) {
|
|||||||
expected: "\n",
|
expected: "\n",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
counts := make(map[string]int)
|
counts := make(map[string]int)
|
||||||
mapping := MappingFuncFor(counts, context...)
|
expanded := DoReplacements(
|
||||||
expanded := Expand(fmt.Sprintf("%v", tc.input), mapping)
|
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 {
|
if e, a := tc.expected, expanded; e != a {
|
||||||
t.Errorf("%v: expected %q, got %q", tc.name, e, a)
|
t.Errorf("%v: expected %q, got %q", tc.name, e, a)
|
||||||
}
|
}
|
||||||
@@ -347,8 +371,7 @@ func doExpansionTest(t *testing.T, context ...map[string]interface{}) {
|
|||||||
}
|
}
|
||||||
if len(tc.counts) > 0 {
|
if len(tc.counts) > 0 {
|
||||||
for k, expectedCount := range tc.counts {
|
for k, expectedCount := range tc.counts {
|
||||||
c, ok := counts[k]
|
if c, ok := counts[k]; ok {
|
||||||
if ok {
|
|
||||||
if c != expectedCount {
|
if c != expectedCount {
|
||||||
t.Errorf(
|
t.Errorf(
|
||||||
"%v: k=%s, expected count %d, got %d",
|
"%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
@@ -33,10 +33,8 @@ spec:
|
|||||||
Count: 42,
|
Count: 42,
|
||||||
Name: "instance",
|
Name: "instance",
|
||||||
},
|
},
|
||||||
FsSlice: types.FsSlice{
|
FieldSpec: types.FieldSpec{
|
||||||
{
|
Path: "spec/template/replicas",
|
||||||
Path: "spec/template/replicas",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
Outputs: []kio.Writer{kio.ByteWriter{Writer: os.Stdout}},
|
Outputs: []kio.Writer{kio.ByteWriter{Writer: os.Stdout}},
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ package replicacount
|
|||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/filters/fsslice"
|
"sigs.k8s.io/kustomize/api/filters/fieldspec"
|
||||||
|
"sigs.k8s.io/kustomize/api/filters/filtersutil"
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
@@ -11,39 +12,34 @@ import (
|
|||||||
|
|
||||||
// Filter updates/sets replicas fields using the fieldSpecs
|
// Filter updates/sets replicas fields using the fieldSpecs
|
||||||
type Filter struct {
|
type Filter struct {
|
||||||
Replica types.Replica `json:"replica,omitempty" yaml:"replica,omitempty"`
|
Replica types.Replica `json:"replica,omitempty" yaml:"replica,omitempty"`
|
||||||
|
FieldSpec types.FieldSpec `json:"fieldSpec,omitempty" yaml:"fieldSpec,omitempty"`
|
||||||
|
|
||||||
// FsSlice contains the FieldSpecs to locate the namespace field
|
trackableSetter filtersutil.TrackableSetter
|
||||||
FsSlice types.FsSlice `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ kio.Filter = Filter{}
|
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) {
|
func (rc Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||||
return kio.FilterAll(yaml.FilterFunc(rc.run)).Filter(nodes)
|
return kio.FilterAll(yaml.FilterFunc(rc.run)).Filter(nodes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// run processes each node individually.
|
|
||||||
func (rc Filter) run(node *yaml.RNode) (*yaml.RNode, error) {
|
func (rc Filter) run(node *yaml.RNode) (*yaml.RNode, error) {
|
||||||
meta, err := node.GetMeta()
|
err := node.PipeE(fieldspec.Filter{
|
||||||
if err != nil {
|
FieldSpec: rc.FieldSpec,
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// only update resources where the name matches the Replica name.
|
|
||||||
if meta.Name != rc.Replica.Name {
|
|
||||||
return node, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err = node.PipeE(fsslice.Filter{
|
|
||||||
FsSlice: rc.FsSlice,
|
|
||||||
SetValue: rc.set,
|
SetValue: rc.set,
|
||||||
CreateKind: yaml.ScalarNode, // replicas is a ScalarNode
|
CreateKind: yaml.ScalarNode, // replicas is a ScalarNode
|
||||||
CreateTag: yaml.IntTag,
|
CreateTag: yaml.NodeTagInt,
|
||||||
})
|
})
|
||||||
return node, err
|
return node, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rc Filter) set(node *yaml.RNode) error {
|
func (rc Filter) set(node *yaml.RNode) error {
|
||||||
return fsslice.SetScalar(strconv.FormatInt(rc.Replica.Count, 10))(node)
|
return rc.trackableSetter.SetScalar(strconv.FormatInt(rc.Replica.Count, 10))(node)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,19 +5,19 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
|
||||||
filtertest_test "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
filtertest_test "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFilter(t *testing.T) {
|
func TestFilter(t *testing.T) {
|
||||||
var config = builtinconfig.MakeDefaultConfig()
|
mutationTrackerStub := filtertest_test.MutationTrackerStub{}
|
||||||
|
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
input string
|
input string
|
||||||
expected string
|
expected string
|
||||||
filter Filter
|
filter Filter
|
||||||
fsslice types.FsSlice
|
mutationTracker func(key, value, tag string, node *yaml.RNode)
|
||||||
|
expectedSetValueArgs []filtertest_test.SetValueArg
|
||||||
}{
|
}{
|
||||||
"update field": {
|
"update field": {
|
||||||
input: `
|
input: `
|
||||||
@@ -41,11 +41,7 @@ spec:
|
|||||||
Name: "dep",
|
Name: "dep",
|
||||||
Count: 42,
|
Count: 42,
|
||||||
},
|
},
|
||||||
},
|
FieldSpec: types.FieldSpec{Path: "spec/replicas"},
|
||||||
fsslice: types.FsSlice{
|
|
||||||
{
|
|
||||||
Path: "spec/replicas",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"add field": {
|
"add field": {
|
||||||
@@ -73,9 +69,7 @@ spec:
|
|||||||
Name: "cus",
|
Name: "cus",
|
||||||
Count: 42,
|
Count: 42,
|
||||||
},
|
},
|
||||||
},
|
FieldSpec: types.FieldSpec{
|
||||||
fsslice: types.FsSlice{
|
|
||||||
{
|
|
||||||
Path: "spec/template/replicas",
|
Path: "spec/template/replicas",
|
||||||
CreateIfNotPresent: true,
|
CreateIfNotPresent: true,
|
||||||
},
|
},
|
||||||
@@ -108,9 +102,7 @@ spec:
|
|||||||
Name: "cus",
|
Name: "cus",
|
||||||
Count: 42,
|
Count: 42,
|
||||||
},
|
},
|
||||||
},
|
FieldSpec: types.FieldSpec{
|
||||||
fsslice: types.FsSlice{
|
|
||||||
{
|
|
||||||
Path: "spec/template/replicas",
|
Path: "spec/template/replicas",
|
||||||
CreateIfNotPresent: true,
|
CreateIfNotPresent: true,
|
||||||
},
|
},
|
||||||
@@ -140,9 +132,7 @@ spec:
|
|||||||
Name: "cus",
|
Name: "cus",
|
||||||
Count: 42,
|
Count: 42,
|
||||||
},
|
},
|
||||||
},
|
FieldSpec: types.FieldSpec{
|
||||||
fsslice: types.FsSlice{
|
|
||||||
{
|
|
||||||
Path: "spec/template/replicas",
|
Path: "spec/template/replicas",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -154,7 +144,6 @@ kind: Custom
|
|||||||
metadata:
|
metadata:
|
||||||
name: cus
|
name: cus
|
||||||
spec:
|
spec:
|
||||||
replicas: 5
|
|
||||||
template:
|
template:
|
||||||
replicas: 5
|
replicas: 5
|
||||||
`,
|
`,
|
||||||
@@ -164,7 +153,6 @@ kind: Custom
|
|||||||
metadata:
|
metadata:
|
||||||
name: cus
|
name: cus
|
||||||
spec:
|
spec:
|
||||||
replicas: 42
|
|
||||||
template:
|
template:
|
||||||
replicas: 42
|
replicas: 42
|
||||||
`,
|
`,
|
||||||
@@ -173,27 +161,54 @@ spec:
|
|||||||
Name: "cus",
|
Name: "cus",
|
||||||
Count: 42,
|
Count: 42,
|
||||||
},
|
},
|
||||||
|
FieldSpec: types.FieldSpec{Path: "spec/template/replicas"},
|
||||||
},
|
},
|
||||||
fsslice: types.FsSlice{
|
},
|
||||||
{
|
"mutation tracker": {
|
||||||
Path: "spec/template/replicas",
|
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{
|
||||||
{
|
{
|
||||||
Path: "spec/replicas",
|
Value: "42",
|
||||||
|
NodePath: []string{"spec", "replicas"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for tn, tc := range testCases {
|
for tn, tc := range testCases {
|
||||||
|
mutationTrackerStub.Reset()
|
||||||
|
tc.filter.WithMutationTracker(tc.mutationTracker)
|
||||||
t.Run(tn, func(t *testing.T) {
|
t.Run(tn, func(t *testing.T) {
|
||||||
tc.filter.FsSlice = append(config.Replicas, tc.fsslice...)
|
|
||||||
if !assert.Equal(t,
|
if !assert.Equal(t,
|
||||||
strings.TrimSpace(tc.expected),
|
strings.TrimSpace(tc.expected),
|
||||||
strings.TrimSpace(
|
strings.TrimSpace(
|
||||||
filtertest_test.RunFilter(t, tc.input, tc.filter))) {
|
filtertest_test.RunFilter(t, tc.input, tc.filter))) {
|
||||||
t.FailNow()
|
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())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ package valueadd
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/filesys"
|
"sigs.k8s.io/kustomize/kyaml/filesys"
|
||||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
)
|
)
|
||||||
@@ -98,7 +98,17 @@ var _ kio.Filter = Filter{}
|
|||||||
func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||||
_, err := kio.FilterAll(yaml.FilterFunc(
|
_, err := kio.FilterAll(yaml.FilterFunc(
|
||||||
func(node *yaml.RNode) (*yaml.RNode, error) {
|
func(node *yaml.RNode) (*yaml.RNode, error) {
|
||||||
fields := strings.Split(f.FieldPath, "/")
|
var fields []string
|
||||||
|
// if there is forward slash '/' in the field name, a back slash '\'
|
||||||
|
// will be used to escape it.
|
||||||
|
for _, f := range strings.Split(f.FieldPath, "/") {
|
||||||
|
if len(fields) > 0 && strings.HasSuffix(fields[len(fields)-1], "\\") {
|
||||||
|
concatField := strings.TrimSuffix(fields[len(fields)-1], "\\") + "/" + f
|
||||||
|
fields = append(fields[:len(fields)-1], concatField)
|
||||||
|
} else {
|
||||||
|
fields = append(fields, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
// TODO: support SequenceNode.
|
// TODO: support SequenceNode.
|
||||||
// Presumably here one could look for array indices (digits) at
|
// Presumably here one could look for array indices (digits) at
|
||||||
// the end of the field path (as described in IETF RFC 6902 JSON),
|
// the end of the field path (as described in IETF RFC 6902 JSON),
|
||||||
|
|||||||
@@ -94,6 +94,20 @@ spec:
|
|||||||
FilePathPosition: 2,
|
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 {
|
for tn, tc := range testCases {
|
||||||
|
|||||||
23
api/go.mod
23
api/go.mod
@@ -1,21 +1,16 @@
|
|||||||
module sigs.k8s.io/kustomize/api
|
module sigs.k8s.io/kustomize/api
|
||||||
|
|
||||||
go 1.14
|
go 1.16
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/evanphx/json-patch v4.5.0+incompatible
|
github.com/evanphx/json-patch v4.11.0+incompatible
|
||||||
github.com/go-openapi/spec v0.19.5
|
github.com/go-errors/errors v1.0.1
|
||||||
github.com/golangci/golangci-lint v1.21.0
|
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||||
github.com/pkg/errors v0.8.1
|
github.com/imdario/mergo v0.3.5
|
||||||
github.com/stretchr/testify v1.4.0
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/yujunz/go-getter v1.4.1-lite
|
github.com/stretchr/testify v1.7.0
|
||||||
golang.org/x/tools v0.0.0-20191010075000-0337d82405ff
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
gopkg.in/yaml.v2 v2.3.0
|
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e
|
||||||
k8s.io/api v0.17.0
|
sigs.k8s.io/kustomize/kyaml v0.13.2
|
||||||
k8s.io/apimachinery v0.17.0
|
|
||||||
k8s.io/client-go v0.17.0
|
|
||||||
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a
|
|
||||||
sigs.k8s.io/kustomize/kyaml v0.3.4
|
|
||||||
sigs.k8s.io/yaml v1.2.0
|
sigs.k8s.io/yaml v1.2.0
|
||||||
)
|
)
|
||||||
|
|||||||
915
api/go.sum
915
api/go.sum
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,8 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SortArrayAndComputeHash sorts a string array and
|
// SortArrayAndComputeHash sorts a string array and
|
||||||
@@ -18,12 +20,12 @@ func SortArrayAndComputeHash(s []string) (string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return Encode(Hash(string(data)))
|
return encode(hex256(string(data)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copied from https://github.com/kubernetes/kubernetes
|
// Copied from https://github.com/kubernetes/kubernetes
|
||||||
// /blob/master/pkg/kubectl/util/hash/hash.go
|
// /blob/master/pkg/kubectl/util/hash/hash.go
|
||||||
func Encode(hex string) (string, error) {
|
func encode(hex string) (string, error) {
|
||||||
if len(hex) < 10 {
|
if len(hex) < 10 {
|
||||||
return "", fmt.Errorf(
|
return "", fmt.Errorf(
|
||||||
"input length must be at least 10")
|
"input length must be at least 10")
|
||||||
@@ -46,7 +48,108 @@ func Encode(hex string) (string, error) {
|
|||||||
return string(enc), nil
|
return string(enc), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hash returns the hex form of the sha256 of the argument.
|
// hex256 returns the hex form of the sha256 of the argument.
|
||||||
func Hash(data string) string {
|
func hex256(data string) string {
|
||||||
return fmt.Sprintf("%x", sha256.Sum256([]byte(data)))
|
return fmt.Sprintf("%x", sha256.Sum256([]byte(data)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hasher computes the hash of an RNode.
|
||||||
|
type Hasher struct{}
|
||||||
|
|
||||||
|
// Hash returns a hash of the argument.
|
||||||
|
func (h *Hasher) Hash(node *yaml.RNode) (r string, err error) {
|
||||||
|
var encoded string
|
||||||
|
switch node.GetKind() {
|
||||||
|
case "ConfigMap":
|
||||||
|
encoded, err = encodeConfigMap(node)
|
||||||
|
case "Secret":
|
||||||
|
encoded, err = encodeSecret(node)
|
||||||
|
default:
|
||||||
|
var encodedBytes []byte
|
||||||
|
encodedBytes, err = json.Marshal(node.YNode())
|
||||||
|
encoded = string(encodedBytes)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return encode(hex256(encoded))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNodeValues(
|
||||||
|
node *yaml.RNode, paths []string) (map[string]interface{}, error) {
|
||||||
|
values := make(map[string]interface{})
|
||||||
|
for _, p := range paths {
|
||||||
|
vn, err := node.Pipe(yaml.Lookup(p))
|
||||||
|
if err != nil {
|
||||||
|
return map[string]interface{}{}, err
|
||||||
|
}
|
||||||
|
if vn == nil {
|
||||||
|
values[p] = ""
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if vn.YNode().Kind != yaml.ScalarNode {
|
||||||
|
vs, err := vn.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
return map[string]interface{}{}, err
|
||||||
|
}
|
||||||
|
// data, binaryData and stringData are all maps
|
||||||
|
var v map[string]interface{}
|
||||||
|
json.Unmarshal(vs, &v)
|
||||||
|
values[p] = v
|
||||||
|
} else {
|
||||||
|
values[p] = vn.YNode().Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return values, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeConfigMap encodes a ConfigMap.
|
||||||
|
// Data, Kind, and Name are taken into account.
|
||||||
|
// BinaryData is included if it's not empty to avoid useless key in output.
|
||||||
|
func encodeConfigMap(node *yaml.RNode) (string, error) {
|
||||||
|
// get fields
|
||||||
|
paths := []string{"metadata/name", "data", "binaryData"}
|
||||||
|
values, err := getNodeValues(node, paths)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
m := map[string]interface{}{
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"name": values["metadata/name"],
|
||||||
|
"data": values["data"],
|
||||||
|
}
|
||||||
|
if _, ok := values["binaryData"].(map[string]interface{}); ok {
|
||||||
|
m["binaryData"] = values["binaryData"]
|
||||||
|
}
|
||||||
|
|
||||||
|
// json.Marshal sorts the keys in a stable order in the encoding
|
||||||
|
data, err := json.Marshal(m)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(data), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeSecret encodes a Secret.
|
||||||
|
// Data, Kind, Name, and Type are taken into account.
|
||||||
|
// StringData is included if it's not empty to avoid useless key in output.
|
||||||
|
func encodeSecret(node *yaml.RNode) (string, error) {
|
||||||
|
// get fields
|
||||||
|
paths := []string{"type", "metadata/name", "data", "stringData"}
|
||||||
|
values, err := getNodeValues(node, paths)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
m := map[string]interface{}{"kind": "Secret", "type": values["type"],
|
||||||
|
"name": values["metadata/name"], "data": values["data"]}
|
||||||
|
if _, ok := values["stringData"].(map[string]interface{}); ok {
|
||||||
|
m["stringData"] = values["stringData"]
|
||||||
|
}
|
||||||
|
|
||||||
|
// json.Marshal sorts the keys in a stable order in the encoding
|
||||||
|
data, err := json.Marshal(m)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(data), nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
// Copyright 2019 The Kubernetes Authors.
|
// Copyright 2019 The Kubernetes Authors.
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
package hasher_test
|
package hasher
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
. "sigs.k8s.io/kustomize/api/hasher"
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSortArrayAndComputeHash(t *testing.T) {
|
func TestSortArrayAndComputeHash(t *testing.T) {
|
||||||
@@ -31,11 +32,325 @@ func TestSortArrayAndComputeHash(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHash(t *testing.T) {
|
func Test_hex256(t *testing.T) {
|
||||||
// hash the empty string to be sure that sha256 is being used
|
// hash the empty string to be sure that sha256 is being used
|
||||||
expect := "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
expect := "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||||
sum := Hash("")
|
sum := hex256("")
|
||||||
if expect != sum {
|
if expect != sum {
|
||||||
t.Errorf("expected hash %q but got %q", expect, sum)
|
t.Errorf("expected hash %q but got %q", expect, sum)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConfigMapHash(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
desc string
|
||||||
|
cmYaml string
|
||||||
|
hash string
|
||||||
|
err string
|
||||||
|
}{
|
||||||
|
// empty map
|
||||||
|
{"empty data", `
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap`, "6ct58987ht", ""},
|
||||||
|
// one key
|
||||||
|
{"one key", `
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
data:
|
||||||
|
one: ""`, "9g67k2htb6", ""},
|
||||||
|
// three keys (tests sorting order)
|
||||||
|
{"three keys", `
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
data:
|
||||||
|
two: 2
|
||||||
|
one: ""
|
||||||
|
three: 3`, "7757f9kkct", ""},
|
||||||
|
// empty binary data map
|
||||||
|
{"empty binary data", `
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap`, "6ct58987ht", ""},
|
||||||
|
// one key with binary data
|
||||||
|
{"one key with binary data", `
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
binaryData:
|
||||||
|
one: ""`, "6mtk2m274t", ""},
|
||||||
|
// three keys with binary data (tests sorting order)
|
||||||
|
{"three keys with binary data", `
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
binaryData:
|
||||||
|
two: 2
|
||||||
|
one: ""
|
||||||
|
three: 3`, "9th7kc28dg", ""},
|
||||||
|
// two keys, one with string and another with binary data
|
||||||
|
{"two keys with one each", `
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
data:
|
||||||
|
one: ""
|
||||||
|
binaryData:
|
||||||
|
two: ""`, "698h7c7t9m", ""},
|
||||||
|
}
|
||||||
|
h := &Hasher{}
|
||||||
|
for _, c := range cases {
|
||||||
|
node, err := yaml.Parse(c.cmYaml)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
hashed, err := h.Hash(node)
|
||||||
|
if SkipRest(t, c.desc, err, c.err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if c.hash != hashed {
|
||||||
|
t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSecretHash(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
desc string
|
||||||
|
secretYaml string
|
||||||
|
hash string
|
||||||
|
err string
|
||||||
|
}{
|
||||||
|
// empty map
|
||||||
|
{"empty data", `
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
type: my-type`, "5gmgkf8578", ""},
|
||||||
|
// one key
|
||||||
|
{"one key", `
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
type: my-type
|
||||||
|
data:
|
||||||
|
one: ""`, "74bd68bm66", ""},
|
||||||
|
// three keys (tests sorting order)
|
||||||
|
{"three keys", `
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
type: my-type
|
||||||
|
data:
|
||||||
|
two: 2
|
||||||
|
one: ""
|
||||||
|
three: 3`, "4gf75c7476", ""},
|
||||||
|
// with stringdata
|
||||||
|
{"stringdata", `
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
type: my-type
|
||||||
|
data:
|
||||||
|
one: ""
|
||||||
|
stringData:
|
||||||
|
two: 2`, "c4h4264gdb", ""},
|
||||||
|
// empty stringdata
|
||||||
|
{"empty stringdata", `
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
type: my-type
|
||||||
|
data:
|
||||||
|
one: ""`, "74bd68bm66", ""},
|
||||||
|
}
|
||||||
|
h := &Hasher{}
|
||||||
|
for _, c := range cases {
|
||||||
|
node, err := yaml.Parse(c.secretYaml)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
hashed, err := h.Hash(node)
|
||||||
|
if SkipRest(t, c.desc, err, c.err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if c.hash != hashed {
|
||||||
|
t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBasicHash(t *testing.T) {
|
||||||
|
cases := map[string]struct {
|
||||||
|
res string
|
||||||
|
hash string
|
||||||
|
err string
|
||||||
|
}{
|
||||||
|
"minimal": {`
|
||||||
|
apiVersion: test/v1
|
||||||
|
kind: TestResource
|
||||||
|
metadata:
|
||||||
|
name: my-resource`, "244782mkb7", ""},
|
||||||
|
"with spec": {`
|
||||||
|
apiVersion: test/v1
|
||||||
|
kind: TestResource
|
||||||
|
metadata:
|
||||||
|
name: my-resource
|
||||||
|
spec:
|
||||||
|
foo: 1
|
||||||
|
bar: abc`, "59m2mdccg4", ""},
|
||||||
|
}
|
||||||
|
h := &Hasher{}
|
||||||
|
for n := range cases {
|
||||||
|
c := cases[n]
|
||||||
|
t.Run(n, func(t *testing.T) {
|
||||||
|
node, err := yaml.Parse(c.res)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
hashed, err := h.Hash(node)
|
||||||
|
if SkipRest(t, n, err, c.err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if c.hash != hashed {
|
||||||
|
t.Errorf("case %q, expect hash %q but got %q", n, c.hash, h)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeConfigMap(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
desc string
|
||||||
|
cmYaml string
|
||||||
|
expect string
|
||||||
|
err string
|
||||||
|
}{
|
||||||
|
// empty map
|
||||||
|
{"empty data", `
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap`, `{"data":"","kind":"ConfigMap","name":""}`, ""},
|
||||||
|
// one key
|
||||||
|
{"one key", `
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
data:
|
||||||
|
one: ""`, `{"data":{"one":""},"kind":"ConfigMap","name":""}`, ""},
|
||||||
|
// three keys (tests sorting order)
|
||||||
|
{"three keys", `
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
data:
|
||||||
|
two: 2
|
||||||
|
one: ""
|
||||||
|
three: 3`, `{"data":{"one":"","three":3,"two":2},"kind":"ConfigMap","name":""}`, ""},
|
||||||
|
// empty binary map
|
||||||
|
{"empty data", `
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap`, `{"data":"","kind":"ConfigMap","name":""}`, ""},
|
||||||
|
// one key with binary data
|
||||||
|
{"one key", `
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
binaryData:
|
||||||
|
one: ""`, `{"binaryData":{"one":""},"data":"","kind":"ConfigMap","name":""}`, ""},
|
||||||
|
// three keys with binary data (tests sorting order)
|
||||||
|
{"three keys", `
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
binaryData:
|
||||||
|
two: 2
|
||||||
|
one: ""
|
||||||
|
three: 3`, `{"binaryData":{"one":"","three":3,"two":2},"data":"","kind":"ConfigMap","name":""}`, ""},
|
||||||
|
// two keys, one string and one binary values
|
||||||
|
{"two keys with one each", `
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
data:
|
||||||
|
one: ""
|
||||||
|
binaryData:
|
||||||
|
two: ""`, `{"binaryData":{"two":""},"data":{"one":""},"kind":"ConfigMap","name":""}`, ""},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
node, err := yaml.Parse(c.cmYaml)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
s, err := encodeConfigMap(node)
|
||||||
|
if SkipRest(t, c.desc, err, c.err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if s != c.expect {
|
||||||
|
t.Errorf("case %q, expect %q but got %q from encode %#v", c.desc, c.expect, s, c.cmYaml)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeSecret(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
desc string
|
||||||
|
secretYaml string
|
||||||
|
expect string
|
||||||
|
err string
|
||||||
|
}{
|
||||||
|
// empty map
|
||||||
|
{"empty data", `
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
type: my-type`, `{"data":"","kind":"Secret","name":"","type":"my-type"}`, ""},
|
||||||
|
// one key
|
||||||
|
{"one key", `
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
type: my-type
|
||||||
|
data:
|
||||||
|
one: ""`, `{"data":{"one":""},"kind":"Secret","name":"","type":"my-type"}`, ""},
|
||||||
|
// three keys (tests sorting order) - note json.Marshal base64 encodes the values because they come in as []byte
|
||||||
|
{"three keys", `
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
type: my-type
|
||||||
|
data:
|
||||||
|
two: 2
|
||||||
|
one: ""
|
||||||
|
three: 3`, `{"data":{"one":"","three":3,"two":2},"kind":"Secret","name":"","type":"my-type"}`, ""},
|
||||||
|
// with stringdata
|
||||||
|
{"stringdata", `
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
type: my-type
|
||||||
|
data:
|
||||||
|
one: ""
|
||||||
|
stringData:
|
||||||
|
two: 2`, `{"data":{"one":""},"kind":"Secret","name":"","stringData":{"two":2},"type":"my-type"}`, ""},
|
||||||
|
// empty stringdata
|
||||||
|
{"empty stringdata", `
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
type: my-type
|
||||||
|
data:
|
||||||
|
one: ""`, `{"data":{"one":""},"kind":"Secret","name":"","type":"my-type"}`, ""},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
node, err := yaml.Parse(c.secretYaml)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
s, err := encodeSecret(node)
|
||||||
|
if SkipRest(t, c.desc, err, c.err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if s != c.expect {
|
||||||
|
t.Errorf("case %q, expect %q but got %q from encode %#v", c.desc, c.expect, s, c.secretYaml)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SkipRest returns true if there was a non-nil error or if we expected an
|
||||||
|
// error that didn't happen, and logs the appropriate error on the test object.
|
||||||
|
// The return value indicates whether we should skip the rest of the test case
|
||||||
|
// due to the error result.
|
||||||
|
func SkipRest(t *testing.T, desc string, err error, contains string) bool {
|
||||||
|
if err != nil {
|
||||||
|
if len(contains) == 0 {
|
||||||
|
t.Errorf("case %q, expect nil error but got %q", desc, err.Error())
|
||||||
|
} else if !strings.Contains(err.Error(), contains) {
|
||||||
|
t.Errorf("case %q, expect error to contain %q but got %q", desc, contains, err.Error())
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
} else if len(contains) > 0 {
|
||||||
|
t.Errorf("case %q, expect error to contain %q but got nil error", desc, contains)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
package ifc
|
package ifc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sigs.k8s.io/kustomize/api/resid"
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Validator provides functions to validate annotations and labels
|
// Validator provides functions to validate annotations and labels
|
||||||
@@ -38,51 +38,10 @@ type Loader interface {
|
|||||||
Cleanup() error
|
Cleanup() error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Kunstructured allows manipulation of k8s objects
|
// KustHasher returns a hash of the argument
|
||||||
// that do not have Golang structs.
|
|
||||||
type Kunstructured interface {
|
|
||||||
Copy() Kunstructured
|
|
||||||
GetAnnotations() map[string]string
|
|
||||||
GetBool(path string) (bool, error)
|
|
||||||
GetFieldValue(string) (interface{}, error)
|
|
||||||
GetFloat64(path string) (float64, error)
|
|
||||||
GetGvk() resid.Gvk
|
|
||||||
GetInt64(path string) (int64, error)
|
|
||||||
GetKind() string
|
|
||||||
GetLabels() map[string]string
|
|
||||||
GetMap(path string) (map[string]interface{}, error)
|
|
||||||
GetName() string
|
|
||||||
GetSlice(path string) ([]interface{}, error)
|
|
||||||
GetString(string) (string, error)
|
|
||||||
GetStringMap(path string) (map[string]string, error)
|
|
||||||
GetStringSlice(string) ([]string, error)
|
|
||||||
Map() map[string]interface{}
|
|
||||||
MarshalJSON() ([]byte, error)
|
|
||||||
MatchesAnnotationSelector(selector string) (bool, error)
|
|
||||||
MatchesLabelSelector(selector string) (bool, error)
|
|
||||||
Patch(Kunstructured) error
|
|
||||||
SetAnnotations(map[string]string)
|
|
||||||
SetGvk(resid.Gvk)
|
|
||||||
SetLabels(map[string]string)
|
|
||||||
SetMap(map[string]interface{})
|
|
||||||
SetName(string)
|
|
||||||
SetNamespace(string)
|
|
||||||
UnmarshalJSON([]byte) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// KunstructuredFactory makes instances of Kunstructured.
|
|
||||||
type KunstructuredFactory interface {
|
|
||||||
SliceFromBytes([]byte) ([]Kunstructured, error)
|
|
||||||
FromMap(m map[string]interface{}) Kunstructured
|
|
||||||
Hasher() KunstructuredHasher
|
|
||||||
MakeConfigMap(kvLdr KvLoader, args *types.ConfigMapArgs) (Kunstructured, error)
|
|
||||||
MakeSecret(kvLdr KvLoader, args *types.SecretArgs) (Kunstructured, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// KunstructuredHasher returns a hash of the argument
|
|
||||||
// or an error.
|
// or an error.
|
||||||
type KunstructuredHasher interface {
|
type KustHasher interface {
|
||||||
Hash(Kunstructured) (string, error)
|
Hash(*yaml.RNode) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// See core.v1.SecretTypeOpaque
|
// See core.v1.SecretTypeOpaque
|
||||||
|
|||||||
@@ -7,19 +7,26 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/go-openapi/spec"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"k8s.io/kube-openapi/pkg/common"
|
"k8s.io/kube-openapi/pkg/validation/spec"
|
||||||
"sigs.k8s.io/kustomize/api/filesys"
|
|
||||||
"sigs.k8s.io/kustomize/api/ifc"
|
"sigs.k8s.io/kustomize/api/ifc"
|
||||||
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
||||||
"sigs.k8s.io/kustomize/api/resid"
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/filesys"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/resid"
|
||||||
"sigs.k8s.io/yaml"
|
"sigs.k8s.io/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
type myProperties map[string]spec.Schema
|
// OpenAPIDefinition describes single type.
|
||||||
type nameToApiMap map[string]common.OpenAPIDefinition
|
// 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
|
// LoadConfigFromCRDs parse CRD schemas from paths into a TransformerConfig
|
||||||
func LoadConfigFromCRDs(
|
func LoadConfigFromCRDs(
|
||||||
@@ -162,7 +169,7 @@ func loadCrdIntoConfig(
|
|||||||
err = theConfig.AddNamereferenceFieldSpec(
|
err = theConfig.AddNamereferenceFieldSpec(
|
||||||
builtinconfig.NameBackReferences{
|
builtinconfig.NameBackReferences{
|
||||||
Gvk: resid.Gvk{Kind: kind, Version: version},
|
Gvk: resid.Gvk{Kind: kind, Version: version},
|
||||||
FieldSpecs: []types.FieldSpec{
|
Referrers: []types.FieldSpec{
|
||||||
makeFs(theGvk, append(path, propName, nameKey))},
|
makeFs(theGvk, append(path, propName, nameKey))},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -171,9 +178,12 @@ func loadCrdIntoConfig(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if property.Ref.GetURL() != nil {
|
if property.Ref.GetURL() != nil {
|
||||||
loadCrdIntoConfig(
|
err = loadCrdIntoConfig(
|
||||||
theConfig, theGvk, theMap,
|
theConfig, theGvk, theMap,
|
||||||
property.Ref.String(), append(path, propName))
|
property.Ref.String(), append(path, propName))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/filesys"
|
"github.com/stretchr/testify/require"
|
||||||
"sigs.k8s.io/kustomize/api/loader"
|
|
||||||
|
|
||||||
. "sigs.k8s.io/kustomize/api/internal/accumulator"
|
. "sigs.k8s.io/kustomize/api/internal/accumulator"
|
||||||
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
||||||
"sigs.k8s.io/kustomize/api/resid"
|
"sigs.k8s.io/kustomize/api/loader"
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/filesys"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/resid"
|
||||||
)
|
)
|
||||||
|
|
||||||
// This defines two CRD's: Bee and MyKind.
|
// This defines two CRD's: Bee and MyKind.
|
||||||
@@ -140,21 +140,19 @@ func TestLoadCRDs(t *testing.T) {
|
|||||||
nbrs := []builtinconfig.NameBackReferences{
|
nbrs := []builtinconfig.NameBackReferences{
|
||||||
{
|
{
|
||||||
Gvk: resid.Gvk{Kind: "Secret", Version: "v1"},
|
Gvk: resid.Gvk{Kind: "Secret", Version: "v1"},
|
||||||
FieldSpecs: []types.FieldSpec{
|
Referrers: []types.FieldSpec{
|
||||||
{
|
{
|
||||||
CreateIfNotPresent: false,
|
Gvk: resid.Gvk{Kind: "MyKind"},
|
||||||
Gvk: resid.Gvk{Kind: "MyKind"},
|
Path: "spec/secretRef/name",
|
||||||
Path: "spec/secretRef/name",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Gvk: resid.Gvk{Kind: "Bee", Version: "v1beta1"},
|
Gvk: resid.Gvk{Kind: "Bee", Version: "v1beta1"},
|
||||||
FieldSpecs: []types.FieldSpec{
|
Referrers: []types.FieldSpec{
|
||||||
{
|
{
|
||||||
CreateIfNotPresent: false,
|
Gvk: resid.Gvk{Kind: "MyKind"},
|
||||||
Gvk: resid.Gvk{Kind: "MyKind"},
|
Path: "spec/beeRef/name",
|
||||||
Path: "spec/beeRef/name",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -165,16 +163,13 @@ func TestLoadCRDs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fSys := filesys.MakeFsInMemory()
|
fSys := filesys.MakeFsInMemory()
|
||||||
fSys.WriteFile("/testpath/crd.json", []byte(crdContent))
|
err := fSys.WriteFile("/testpath/crd.json", []byte(crdContent))
|
||||||
|
require.NoError(t, err)
|
||||||
ldr, err := loader.NewLoader(loader.RestrictionRootOnly, "/testpath", fSys)
|
ldr, err := loader.NewLoader(loader.RestrictionRootOnly, "/testpath", fSys)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("unexpected error:%v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
actualTc, err := LoadConfigFromCRDs(ldr, []string{"crd.json"})
|
actualTc, err := LoadConfigFromCRDs(ldr, []string{"crd.json"})
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("unexpected error:%v", err)
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(actualTc, expectedTc) {
|
if !reflect.DeepEqual(actualTc, expectedTc) {
|
||||||
t.Fatalf("expected\n %v\n but got\n %v\n", expectedTc, actualTc)
|
t.Fatalf("expected\n %v\n but got\n %v\n", expectedTc, actualTc)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,22 +7,27 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
|
"sigs.k8s.io/kustomize/api/filters/nameref"
|
||||||
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
||||||
"sigs.k8s.io/kustomize/api/resid"
|
|
||||||
"sigs.k8s.io/kustomize/api/resmap"
|
"sigs.k8s.io/kustomize/api/resmap"
|
||||||
"sigs.k8s.io/kustomize/api/resource"
|
"sigs.k8s.io/kustomize/api/resource"
|
||||||
"sigs.k8s.io/kustomize/api/transform"
|
"sigs.k8s.io/kustomize/kyaml/resid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type nameReferenceTransformer struct {
|
type nameReferenceTransformer struct {
|
||||||
backRefs []builtinconfig.NameBackReferences
|
backRefs []builtinconfig.NameBackReferences
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const doDebug = false
|
||||||
|
|
||||||
var _ resmap.Transformer = &nameReferenceTransformer{}
|
var _ resmap.Transformer = &nameReferenceTransformer{}
|
||||||
|
|
||||||
|
type filterMap map[*resource.Resource][]nameref.Filter
|
||||||
|
|
||||||
// newNameReferenceTransformer constructs a nameReferenceTransformer
|
// newNameReferenceTransformer constructs a nameReferenceTransformer
|
||||||
// with a given slice of NameBackReferences.
|
// with a given slice of NameBackReferences.
|
||||||
func newNameReferenceTransformer(br []builtinconfig.NameBackReferences) resmap.Transformer {
|
func newNameReferenceTransformer(
|
||||||
|
br []builtinconfig.NameBackReferences) resmap.Transformer {
|
||||||
if br == nil {
|
if br == nil {
|
||||||
log.Fatal("backrefs not expected to be nil")
|
log.Fatal("backrefs not expected to be nil")
|
||||||
}
|
}
|
||||||
@@ -35,13 +40,64 @@ func newNameReferenceTransformer(br []builtinconfig.NameBackReferences) resmap.T
|
|||||||
//
|
//
|
||||||
// For example, a HorizontalPodAutoscaler (HPA)
|
// For example, a HorizontalPodAutoscaler (HPA)
|
||||||
// necessarily refers to a Deployment, the thing that
|
// necessarily refers to a Deployment, the thing that
|
||||||
// the HPA scales. The Deployment name might change
|
// an HPA scales. In this case:
|
||||||
// (e.g. prefix added), and the reference in the HPA
|
|
||||||
// has to be fixed.
|
|
||||||
//
|
//
|
||||||
// In the outer loop over the ResMap below, say we
|
// - the HPA instance is the Referrer,
|
||||||
// encounter a specific HPA. Then, in scanning backrefs,
|
// - the Deployment instance is the ReferralTarget.
|
||||||
// we encounter an entry like
|
//
|
||||||
|
// 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
|
// - kind: Deployment
|
||||||
// fieldSpecs:
|
// fieldSpecs:
|
||||||
@@ -50,220 +106,60 @@ func newNameReferenceTransformer(br []builtinconfig.NameBackReferences) resmap.T
|
|||||||
//
|
//
|
||||||
// This entry says that an HPA, via its
|
// This entry says that an HPA, via its
|
||||||
// 'spec/scaleTargetRef/name' field, may refer to a
|
// 'spec/scaleTargetRef/name' field, may refer to a
|
||||||
// Deployment. This match to HPA means we may need to
|
// Deployment.
|
||||||
// modify the value in its 'spec/scaleTargetRef/name'
|
|
||||||
// field, by searching for the thing it refers to,
|
|
||||||
// and getting its new name.
|
|
||||||
//
|
//
|
||||||
// As a filter, and search optimization, we compute a
|
// This means that a filter will need to hunt for the right Deployment,
|
||||||
// subset of all resources that the HPA could refer to,
|
// obtain it's new name, and write that name into the HPA's
|
||||||
// by excluding objects from other namespaces, and
|
// 'spec/scaleTargetRef/name' field. Return a filter that can do that.
|
||||||
// excluding objects that don't have the same prefix-
|
func (t *nameReferenceTransformer) determineFilters(
|
||||||
// suffix mods as the HPA.
|
resources []*resource.Resource) (fMap filterMap) {
|
||||||
//
|
|
||||||
// We look in this subset for all Deployment objects
|
// We cache the resource OrgId values because they don't change and otherwise are very visible in a memory pprof
|
||||||
// with a resId that has a Name matching the field value
|
resourceOrgIds := make([]resid.ResId, len(resources))
|
||||||
// present in the HPA. If no match do nothing; if more
|
for i, resource := range resources {
|
||||||
// than one match, it's an error.
|
resourceOrgIds[i] = resource.OrgId()
|
||||||
//
|
}
|
||||||
// We overwrite the HPA name field with the value found
|
|
||||||
// in the Deployment's name field (the name in the raw
|
fMap = make(filterMap)
|
||||||
// object - the modified name - not the unmodified name
|
for _, backReference := range t.backRefs {
|
||||||
// in the Deployment's resId).
|
for _, referrerSpec := range backReference.Referrers {
|
||||||
//
|
for i, res := range resources {
|
||||||
// This process assumes that the name stored in a ResId
|
if resourceOrgIds[i].IsSelected(&referrerSpec.Gvk) {
|
||||||
// (the ResMap key) isn't modified by name transformers.
|
// If this is true, the res might be a referrer, and if
|
||||||
// Name transformers should only modify the name in the
|
// so, the name reference it holds might need an update.
|
||||||
// body of the resource object (the value in the ResMap).
|
if resHasField(res, referrerSpec.Path) {
|
||||||
//
|
// Optimization - the referrer has the field
|
||||||
func (o *nameReferenceTransformer) Transform(m resmap.ResMap) error {
|
// that might need updating.
|
||||||
// TODO: Too much looping, here and in transitive calls.
|
fMap[res] = append(fMap[res], nameref.Filter{
|
||||||
for _, referrer := range m.Resources() {
|
// Name field to write in the Referrer.
|
||||||
var candidates resmap.ResMap
|
// If the path specified here isn't found in
|
||||||
for _, target := range o.backRefs {
|
// the Referrer, nothing happens (no error,
|
||||||
for _, fSpec := range target.FieldSpecs {
|
// no field creation).
|
||||||
if referrer.OrgId().IsSelected(&fSpec.Gvk) {
|
NameFieldToUpdate: referrerSpec,
|
||||||
if candidates == nil {
|
// Specification of object class to read from.
|
||||||
candidates = m.SubsetThatCouldBeReferencedByResource(referrer)
|
// Always read from metadata/name field.
|
||||||
}
|
ReferralTarget: backReference.Gvk,
|
||||||
err := transform.MutateField(
|
})
|
||||||
referrer.Map(),
|
|
||||||
fSpec.PathSlice(),
|
|
||||||
fSpec.CreateIfNotPresent,
|
|
||||||
o.getNewNameFunc(
|
|
||||||
// referrer could be an HPA instance,
|
|
||||||
// target could be Gvk for Deployment,
|
|
||||||
// candidate a list of resources "reachable"
|
|
||||||
// from the HPA.
|
|
||||||
referrer, target.Gvk, candidates))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return fMap
|
||||||
}
|
}
|
||||||
|
|
||||||
// selectReferral picks the referral among a subset of candidates.
|
// TODO: check res for field existence here to avoid extra work.
|
||||||
// It returns the current name and namespace of the selected candidate.
|
// res.GetFieldValue, which uses yaml.Lookup under the hood, doesn't know
|
||||||
// Note that the content of the referricalCandidateSubset slice is most of the time
|
// how to parse fieldspec-style paths that make no distinction
|
||||||
// identical to the referralCandidates resmap. Still in some cases, such
|
// between maps and sequences. This means it cannot lookup commonly
|
||||||
// as ClusterRoleBinding, the subset only contains the resources of a specific
|
// used "indeterminate" paths like
|
||||||
// namespace.
|
// spec/containers/env/valueFrom/configMapKeyRef/name
|
||||||
func (o *nameReferenceTransformer) selectReferral(
|
// ('containers' is a list, not a map).
|
||||||
oldName string,
|
// However, the fieldspec filter does know how to handle this;
|
||||||
referrer *resource.Resource,
|
// extract that code and call it here?
|
||||||
target resid.Gvk,
|
func resHasField(res *resource.Resource, path string) bool {
|
||||||
referralCandidates resmap.ResMap,
|
return true
|
||||||
referralCandidateSubset []*resource.Resource) (interface{}, interface{}, error) {
|
// fld := strings.Join(utils.PathSplitter(path), ".")
|
||||||
|
// _, e := res.GetFieldValue(fld)
|
||||||
for _, res := range referralCandidateSubset {
|
// return e == nil
|
||||||
id := res.OrgId()
|
|
||||||
if id.IsSelected(&target) && res.GetOriginalName() == oldName {
|
|
||||||
matches := referralCandidates.GetMatchingResourcesByOriginalId(id.Equals)
|
|
||||||
// If there's more than one match, there's no way
|
|
||||||
// to know which one to pick, so emit error.
|
|
||||||
if len(matches) > 1 {
|
|
||||||
return nil, nil, fmt.Errorf(
|
|
||||||
"multiple matches for %s:\n %v",
|
|
||||||
id, getIds(matches))
|
|
||||||
}
|
|
||||||
// In the resource, note that it is referenced
|
|
||||||
// by the referrer.
|
|
||||||
res.AppendRefBy(referrer.CurId())
|
|
||||||
// Return transformed name of the object,
|
|
||||||
// complete with prefixes, hashes, etc.
|
|
||||||
return res.GetName(), res.GetNamespace(), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return oldName, nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// utility function to replace a simple string by the new name
|
|
||||||
func (o *nameReferenceTransformer) getSimpleNameField(
|
|
||||||
oldName string,
|
|
||||||
referrer *resource.Resource,
|
|
||||||
target resid.Gvk,
|
|
||||||
referralCandidates resmap.ResMap,
|
|
||||||
referralCandidateSubset []*resource.Resource) (interface{}, error) {
|
|
||||||
|
|
||||||
newName, _, err := o.selectReferral(oldName, referrer, target,
|
|
||||||
referralCandidates, referralCandidateSubset)
|
|
||||||
|
|
||||||
return newName, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// utility function to replace name field within a map[string]interface{}
|
|
||||||
// and leverage the namespace field.
|
|
||||||
func (o *nameReferenceTransformer) getNameAndNsStruct(
|
|
||||||
inMap map[string]interface{},
|
|
||||||
referrer *resource.Resource,
|
|
||||||
target resid.Gvk,
|
|
||||||
referralCandidates resmap.ResMap) (interface{}, error) {
|
|
||||||
|
|
||||||
// Example:
|
|
||||||
if _, ok := inMap["name"]; !ok {
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"%#v is expected to contain a name field", inMap)
|
|
||||||
}
|
|
||||||
oldName, ok := inMap["name"].(string)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"%#v is expected to contain a name field of type string", oldName)
|
|
||||||
}
|
|
||||||
|
|
||||||
subset := referralCandidates.Resources()
|
|
||||||
if namespacevalue, ok := inMap["namespace"]; ok {
|
|
||||||
namespace := namespacevalue.(string)
|
|
||||||
bynamespace := referralCandidates.GroupedByOriginalNamespace()
|
|
||||||
if _, ok := bynamespace[namespace]; !ok {
|
|
||||||
return inMap, nil
|
|
||||||
}
|
|
||||||
subset = bynamespace[namespace]
|
|
||||||
}
|
|
||||||
|
|
||||||
newname, newnamespace, err := o.selectReferral(oldName, referrer, target,
|
|
||||||
referralCandidates, subset)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newname == oldName) && (newnamespace == nil) {
|
|
||||||
// no candidate found.
|
|
||||||
return inMap, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
inMap["name"] = newname
|
|
||||||
if newnamespace != "" {
|
|
||||||
// We don't want value "" to replace value "default" since
|
|
||||||
// the empty string is handled as a wild card here not default namespace
|
|
||||||
// by kubernetes.
|
|
||||||
inMap["namespace"] = newnamespace
|
|
||||||
}
|
|
||||||
return inMap, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *nameReferenceTransformer) getNewNameFunc(
|
|
||||||
referrer *resource.Resource,
|
|
||||||
target resid.Gvk,
|
|
||||||
referralCandidates resmap.ResMap) func(in interface{}) (interface{}, error) {
|
|
||||||
return func(in interface{}) (interface{}, error) {
|
|
||||||
switch thing := in.(type) {
|
|
||||||
case string:
|
|
||||||
return o.getSimpleNameField(thing, referrer, target,
|
|
||||||
referralCandidates, referralCandidates.Resources())
|
|
||||||
case map[string]interface{}:
|
|
||||||
// Kind: ValidatingWebhookConfiguration
|
|
||||||
// FieldSpec is webhooks/clientConfig/service
|
|
||||||
return o.getNameAndNsStruct(thing, referrer, target,
|
|
||||||
referralCandidates)
|
|
||||||
case []interface{}:
|
|
||||||
for idx, item := range thing {
|
|
||||||
switch value := item.(type) {
|
|
||||||
case string:
|
|
||||||
// Kind: Role/ClusterRole
|
|
||||||
// FieldSpec is rules.resourceNames
|
|
||||||
newName, err := o.getSimpleNameField(value, referrer, target,
|
|
||||||
referralCandidates, referralCandidates.Resources())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
thing[idx] = newName
|
|
||||||
case map[string]interface{}:
|
|
||||||
// Kind: RoleBinding/ClusterRoleBinding
|
|
||||||
// FieldSpec is subjects
|
|
||||||
// Note: The corresponding fieldSpec had been changed from
|
|
||||||
// from path: subjects/name to just path: subjects. This is
|
|
||||||
// what get mutatefield to request the mapping of the whole
|
|
||||||
// map containing namespace and name instead of just a simple
|
|
||||||
// string field containing the name
|
|
||||||
newMap, err := o.getNameAndNsStruct(value, referrer, target,
|
|
||||||
referralCandidates)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
thing[idx] = newMap
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"%#v is expected to be either a []string or a []map[string]interface{}", in)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return in, nil
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"%#v is expected to be either a string or a []interface{}", in)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getIds(rs []*resource.Resource) []string {
|
|
||||||
var result []string
|
|
||||||
for _, r := range rs {
|
|
||||||
result = append(result, r.CurId().String()+"\n")
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,17 +8,16 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
||||||
"sigs.k8s.io/kustomize/api/k8sdeps/kunstruct"
|
"sigs.k8s.io/kustomize/api/provider"
|
||||||
"sigs.k8s.io/kustomize/api/resid"
|
|
||||||
"sigs.k8s.io/kustomize/api/resmap"
|
"sigs.k8s.io/kustomize/api/resmap"
|
||||||
"sigs.k8s.io/kustomize/api/resource"
|
|
||||||
resmaptest_test "sigs.k8s.io/kustomize/api/testutils/resmaptest"
|
resmaptest_test "sigs.k8s.io/kustomize/api/testutils/resmaptest"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/resid"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const notEqualErrFmt = "expected (self) doesn't match actual (other): %v"
|
||||||
|
|
||||||
func TestNameReferenceHappyRun(t *testing.T) {
|
func TestNameReferenceHappyRun(t *testing.T) {
|
||||||
rf := resource.NewFactory(
|
m := resmaptest_test.NewRmBuilderDefault(t).AddWithName(
|
||||||
kunstruct.NewKunstructuredFactoryImpl())
|
|
||||||
m := resmaptest_test.NewRmBuilder(t, rf).AddWithName(
|
|
||||||
"cm1",
|
"cm1",
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"apiVersion": "v1",
|
"apiVersion": "v1",
|
||||||
@@ -220,6 +219,7 @@ func TestNameReferenceHappyRun(t *testing.T) {
|
|||||||
"secret1",
|
"secret1",
|
||||||
"secret1",
|
"secret1",
|
||||||
"secret2",
|
"secret2",
|
||||||
|
"cm1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -261,7 +261,8 @@ func TestNameReferenceHappyRun(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}).ResMap()
|
}).ResMap()
|
||||||
|
|
||||||
expected := resmaptest_test.NewSeededRmBuilder(t, rf, m.ShallowCopy()).ReplaceResource(
|
expected := resmaptest_test.NewSeededRmBuilderDefault(
|
||||||
|
t, m.ShallowCopy()).ReplaceResource(
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"group": "apps",
|
"group": "apps",
|
||||||
"apiVersion": "v1",
|
"apiVersion": "v1",
|
||||||
@@ -422,6 +423,7 @@ func TestNameReferenceHappyRun(t *testing.T) {
|
|||||||
"someprefix-secret1-somehash",
|
"someprefix-secret1-somehash",
|
||||||
"someprefix-secret1-somehash",
|
"someprefix-secret1-somehash",
|
||||||
"secret2",
|
"secret2",
|
||||||
|
"someprefix-cm1-somehash",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -470,19 +472,17 @@ func TestNameReferenceHappyRun(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err = expected.ErrorIfNotEqualLists(m); err != nil {
|
if err = expected.ErrorIfNotEqualLists(m); err != nil {
|
||||||
t.Fatalf("actual doesn't match expected: %v", err)
|
t.Fatalf(notEqualErrFmt, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNameReferenceUnhappyRun(t *testing.T) {
|
func TestNameReferenceUnhappyRun(t *testing.T) {
|
||||||
rf := resource.NewFactory(
|
|
||||||
kunstruct.NewKunstructuredFactoryImpl())
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
resMap resmap.ResMap
|
resMap resmap.ResMap
|
||||||
expectedErr string
|
expectedErr string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
resMap: resmaptest_test.NewRmBuilder(t, rf).Add(
|
resMap: resmaptest_test.NewRmBuilderDefault(t).Add(
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"apiVersion": "rbac.authorization.k8s.io/v1",
|
"apiVersion": "rbac.authorization.k8s.io/v1",
|
||||||
"kind": "ClusterRole",
|
"kind": "ClusterRole",
|
||||||
@@ -502,7 +502,7 @@ func TestNameReferenceUnhappyRun(t *testing.T) {
|
|||||||
}).ResMap(),
|
}).ResMap(),
|
||||||
expectedErr: "is expected to be"},
|
expectedErr: "is expected to be"},
|
||||||
{
|
{
|
||||||
resMap: resmaptest_test.NewRmBuilder(t, rf).Add(
|
resMap: resmaptest_test.NewRmBuilderDefault(t).Add(
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"apiVersion": "rbac.authorization.k8s.io/v1",
|
"apiVersion": "rbac.authorization.k8s.io/v1",
|
||||||
"kind": "ClusterRole",
|
"kind": "ClusterRole",
|
||||||
@@ -520,7 +520,10 @@ func TestNameReferenceUnhappyRun(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}).ResMap(),
|
}).ResMap(),
|
||||||
expectedErr: "is expected to contain a name field"},
|
expectedErr: `updating name reference in 'rules/resourceNames' field of 'ClusterRole.v1.rbac.authorization.k8s.io/cr.[noNs]': ` +
|
||||||
|
`considering field 'rules/resourceNames' of object ClusterRole.v1.rbac.authorization.k8s.io/cr.[noNs]: visit traversal on ` +
|
||||||
|
`path: [resourceNames]: path config error; no 'name' field in node`,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
nrt := newNameReferenceTransformer(builtinconfig.MakeDefaultConfig().NameReference)
|
nrt := newNameReferenceTransformer(builtinconfig.MakeDefaultConfig().NameReference)
|
||||||
@@ -531,15 +534,14 @@ func TestNameReferenceUnhappyRun(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !strings.Contains(err.Error(), test.expectedErr) {
|
if !strings.Contains(err.Error(), test.expectedErr) {
|
||||||
t.Fatalf("Incorrect error.\nExpected: %s, but got %v",
|
t.Fatalf("Incorrect error.\nExpected:\n %s\nGot:\n%v",
|
||||||
test.expectedErr, err)
|
test.expectedErr, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNameReferencePersistentVolumeHappyRun(t *testing.T) {
|
func TestNameReferencePersistentVolumeHappyRun(t *testing.T) {
|
||||||
rf := resource.NewFactory(
|
rf := provider.NewDefaultDepProvider().GetResourceFactory()
|
||||||
kunstruct.NewKunstructuredFactoryImpl())
|
|
||||||
|
|
||||||
v1 := rf.FromMapWithName(
|
v1 := rf.FromMapWithName(
|
||||||
"volume1",
|
"volume1",
|
||||||
@@ -590,7 +592,7 @@ func TestNameReferencePersistentVolumeHappyRun(t *testing.T) {
|
|||||||
v2.AppendRefBy(c2.CurId())
|
v2.AppendRefBy(c2.CurId())
|
||||||
|
|
||||||
if err := m1.ErrorIfNotEqualLists(m2); err != nil {
|
if err := m1.ErrorIfNotEqualLists(m2); err != nil {
|
||||||
t.Fatalf("actual doesn't match expected: %v", err)
|
t.Fatalf(notEqualErrFmt, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -664,9 +666,7 @@ const (
|
|||||||
// object with the same original names (uniquename) in different namespaces
|
// object with the same original names (uniquename) in different namespaces
|
||||||
// and with different current Id.
|
// and with different current Id.
|
||||||
func TestNameReferenceNamespace(t *testing.T) {
|
func TestNameReferenceNamespace(t *testing.T) {
|
||||||
rf := resource.NewFactory(
|
m := resmaptest_test.NewRmBuilderDefault(t).
|
||||||
kunstruct.NewKunstructuredFactoryImpl())
|
|
||||||
m := resmaptest_test.NewRmBuilder(t, rf).
|
|
||||||
// Add ConfigMap with the same org name in noNs, "ns1" and "ns2" namespaces
|
// Add ConfigMap with the same org name in noNs, "ns1" and "ns2" namespaces
|
||||||
AddWithName(orgname, map[string]interface{}{
|
AddWithName(orgname, map[string]interface{}{
|
||||||
"apiVersion": "v1",
|
"apiVersion": "v1",
|
||||||
@@ -715,7 +715,7 @@ func TestNameReferenceNamespace(t *testing.T) {
|
|||||||
AddWithNsAndName(ns1, orgname, deploymentMap(ns1, prefixedname, orgname, orgname)).
|
AddWithNsAndName(ns1, orgname, deploymentMap(ns1, prefixedname, orgname, orgname)).
|
||||||
AddWithNsAndName(ns2, orgname, deploymentMap(ns2, suffixedname, orgname, orgname)).ResMap()
|
AddWithNsAndName(ns2, orgname, deploymentMap(ns2, suffixedname, orgname, orgname)).ResMap()
|
||||||
|
|
||||||
expected := resmaptest_test.NewSeededRmBuilder(t, rf, m.ShallowCopy()).
|
expected := resmaptest_test.NewSeededRmBuilderDefault(t, m.ShallowCopy()).
|
||||||
ReplaceResource(deploymentMap(defaultNs, modifiedname, modifiedname, modifiedname)).
|
ReplaceResource(deploymentMap(defaultNs, modifiedname, modifiedname, modifiedname)).
|
||||||
ReplaceResource(deploymentMap(ns1, prefixedname, prefixedname, prefixedname)).
|
ReplaceResource(deploymentMap(ns1, prefixedname, prefixedname, prefixedname)).
|
||||||
ReplaceResource(deploymentMap(ns2, suffixedname, suffixedname, suffixedname)).ResMap()
|
ReplaceResource(deploymentMap(ns2, suffixedname, suffixedname, suffixedname)).ResMap()
|
||||||
@@ -726,8 +726,9 @@ func TestNameReferenceNamespace(t *testing.T) {
|
|||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m.RemoveBuildAnnotations()
|
||||||
if err = expected.ErrorIfNotEqualLists(m); err != nil {
|
if err = expected.ErrorIfNotEqualLists(m); err != nil {
|
||||||
t.Fatalf("actual doesn't match expected: %v", err)
|
t.Fatalf(notEqualErrFmt, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -735,9 +736,7 @@ func TestNameReferenceNamespace(t *testing.T) {
|
|||||||
// object with the same original names (uniquename) in different namespaces
|
// object with the same original names (uniquename) in different namespaces
|
||||||
// and with different current Id.
|
// and with different current Id.
|
||||||
func TestNameReferenceClusterWide(t *testing.T) {
|
func TestNameReferenceClusterWide(t *testing.T) {
|
||||||
rf := resource.NewFactory(
|
m := resmaptest_test.NewRmBuilderDefault(t).
|
||||||
kunstruct.NewKunstructuredFactoryImpl())
|
|
||||||
m := resmaptest_test.NewRmBuilder(t, rf).
|
|
||||||
// Add ServiceAccount with the same org name in noNs, "ns1" and "ns2" namespaces
|
// Add ServiceAccount with the same org name in noNs, "ns1" and "ns2" namespaces
|
||||||
AddWithName(orgname, map[string]interface{}{
|
AddWithName(orgname, map[string]interface{}{
|
||||||
"apiVersion": "v1",
|
"apiVersion": "v1",
|
||||||
@@ -789,9 +788,9 @@ func TestNameReferenceClusterWide(t *testing.T) {
|
|||||||
"name": modifiedname,
|
"name": modifiedname,
|
||||||
},
|
},
|
||||||
"roleRef": map[string]interface{}{
|
"roleRef": map[string]interface{}{
|
||||||
"apiVersion": "rbac.authorization.k8s.io/v1",
|
"apiGroup": "rbac.authorization.k8s.io",
|
||||||
"kind": "ClusterRole",
|
"kind": "ClusterRole",
|
||||||
"name": orgname,
|
"name": orgname,
|
||||||
},
|
},
|
||||||
"subjects": []interface{}{
|
"subjects": []interface{}{
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
@@ -816,7 +815,7 @@ func TestNameReferenceClusterWide(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}}).ResMap()
|
}}).ResMap()
|
||||||
|
|
||||||
expected := resmaptest_test.NewSeededRmBuilder(t, rf, m.ShallowCopy()).
|
expected := resmaptest_test.NewSeededRmBuilderDefault(t, m.ShallowCopy()).
|
||||||
ReplaceResource(
|
ReplaceResource(
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"apiVersion": "rbac.authorization.k8s.io/v1",
|
"apiVersion": "rbac.authorization.k8s.io/v1",
|
||||||
@@ -845,9 +844,9 @@ func TestNameReferenceClusterWide(t *testing.T) {
|
|||||||
"name": modifiedname,
|
"name": modifiedname,
|
||||||
},
|
},
|
||||||
"roleRef": map[string]interface{}{
|
"roleRef": map[string]interface{}{
|
||||||
"apiVersion": "rbac.authorization.k8s.io/v1",
|
"apiGroup": "rbac.authorization.k8s.io",
|
||||||
"kind": "ClusterRole",
|
"kind": "ClusterRole",
|
||||||
"name": modifiedname,
|
"name": modifiedname,
|
||||||
},
|
},
|
||||||
// The following tests required a change in
|
// The following tests required a change in
|
||||||
// getNameFunc implementation in order to leverage
|
// getNameFunc implementation in order to leverage
|
||||||
@@ -877,9 +876,9 @@ func TestNameReferenceClusterWide(t *testing.T) {
|
|||||||
}).ResMap()
|
}).ResMap()
|
||||||
|
|
||||||
clusterRoleId := resid.NewResId(
|
clusterRoleId := resid.NewResId(
|
||||||
resid.Gvk{Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "ClusterRole"}, modifiedname)
|
resid.NewGvk("rbac.authorization.k8s.io", "v1", "ClusterRole"), modifiedname)
|
||||||
clusterRoleBindingId := resid.NewResId(
|
clusterRoleBindingId := resid.NewResId(
|
||||||
resid.Gvk{Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "ClusterRoleBinding"}, modifiedname)
|
resid.NewGvk("rbac.authorization.k8s.io", "v1", "ClusterRoleBinding"), modifiedname)
|
||||||
clusterRole, _ := expected.GetByCurrentId(clusterRoleId)
|
clusterRole, _ := expected.GetByCurrentId(clusterRoleId)
|
||||||
clusterRole.AppendRefBy(clusterRoleBindingId)
|
clusterRole.AppendRefBy(clusterRoleBindingId)
|
||||||
|
|
||||||
@@ -889,8 +888,11 @@ func TestNameReferenceClusterWide(t *testing.T) {
|
|||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expected.RemoveBuildAnnotations()
|
||||||
|
m.RemoveBuildAnnotations()
|
||||||
|
|
||||||
if err = expected.ErrorIfNotEqualLists(m); err != nil {
|
if err = expected.ErrorIfNotEqualLists(m); err != nil {
|
||||||
t.Fatalf("actual doesn't match expected: %v", err)
|
t.Fatalf(notEqualErrFmt, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -898,9 +900,7 @@ func TestNameReferenceClusterWide(t *testing.T) {
|
|||||||
// object with the same original names (uniquename) in different namespaces
|
// object with the same original names (uniquename) in different namespaces
|
||||||
// and with different current Id.
|
// and with different current Id.
|
||||||
func TestNameReferenceNamespaceTransformation(t *testing.T) {
|
func TestNameReferenceNamespaceTransformation(t *testing.T) {
|
||||||
rf := resource.NewFactory(
|
m := resmaptest_test.NewRmBuilderDefault(t).
|
||||||
kunstruct.NewKunstructuredFactoryImpl())
|
|
||||||
m := resmaptest_test.NewRmBuilder(t, rf).
|
|
||||||
AddWithNsAndName(ns4, orgname, map[string]interface{}{
|
AddWithNsAndName(ns4, orgname, map[string]interface{}{
|
||||||
"apiVersion": "v1",
|
"apiVersion": "v1",
|
||||||
"kind": "Secret",
|
"kind": "Secret",
|
||||||
@@ -937,9 +937,9 @@ func TestNameReferenceNamespaceTransformation(t *testing.T) {
|
|||||||
"name": modifiedname,
|
"name": modifiedname,
|
||||||
},
|
},
|
||||||
"roleRef": map[string]interface{}{
|
"roleRef": map[string]interface{}{
|
||||||
"apiVersion": "rbac.authorization.k8s.io/v1",
|
"apiGroup": "rbac.authorization.k8s.io",
|
||||||
"kind": "ClusterRole",
|
"kind": "ClusterRole",
|
||||||
"name": orgname,
|
"name": orgname,
|
||||||
},
|
},
|
||||||
"subjects": []interface{}{
|
"subjects": []interface{}{
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
@@ -964,7 +964,7 @@ func TestNameReferenceNamespaceTransformation(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}}).ResMap()
|
}}).ResMap()
|
||||||
|
|
||||||
expected := resmaptest_test.NewSeededRmBuilder(t, rf, m.ShallowCopy()).
|
expected := resmaptest_test.NewSeededRmBuilderDefault(t, m.ShallowCopy()).
|
||||||
ReplaceResource(
|
ReplaceResource(
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"apiVersion": "rbac.authorization.k8s.io/v1",
|
"apiVersion": "rbac.authorization.k8s.io/v1",
|
||||||
@@ -973,9 +973,9 @@ func TestNameReferenceNamespaceTransformation(t *testing.T) {
|
|||||||
"name": modifiedname,
|
"name": modifiedname,
|
||||||
},
|
},
|
||||||
"roleRef": map[string]interface{}{
|
"roleRef": map[string]interface{}{
|
||||||
"apiVersion": "rbac.authorization.k8s.io/v1",
|
"apiGroup": "rbac.authorization.k8s.io",
|
||||||
"kind": "ClusterRole",
|
"kind": "ClusterRole",
|
||||||
"name": modifiedname,
|
"name": modifiedname,
|
||||||
},
|
},
|
||||||
// The following tests required a change in
|
// The following tests required a change in
|
||||||
// getNameFunc implementation in order to leverage
|
// getNameFunc implementation in order to leverage
|
||||||
@@ -1005,9 +1005,11 @@ func TestNameReferenceNamespaceTransformation(t *testing.T) {
|
|||||||
}).ResMap()
|
}).ResMap()
|
||||||
|
|
||||||
clusterRoleId := resid.NewResId(
|
clusterRoleId := resid.NewResId(
|
||||||
resid.Gvk{Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "ClusterRole"}, modifiedname)
|
resid.NewGvk("rbac.authorization.k8s.io", "v1", "ClusterRole"),
|
||||||
|
modifiedname)
|
||||||
clusterRoleBindingId := resid.NewResId(
|
clusterRoleBindingId := resid.NewResId(
|
||||||
resid.Gvk{Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "ClusterRoleBinding"}, modifiedname)
|
resid.NewGvk("rbac.authorization.k8s.io", "v1", "ClusterRoleBinding"),
|
||||||
|
modifiedname)
|
||||||
clusterRole, _ := expected.GetByCurrentId(clusterRoleId)
|
clusterRole, _ := expected.GetByCurrentId(clusterRoleId)
|
||||||
clusterRole.AppendRefBy(clusterRoleBindingId)
|
clusterRole.AppendRefBy(clusterRoleBindingId)
|
||||||
|
|
||||||
@@ -1017,8 +1019,9 @@ func TestNameReferenceNamespaceTransformation(t *testing.T) {
|
|||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m.RemoveBuildAnnotations()
|
||||||
if err = expected.ErrorIfNotEqualLists(m); err != nil {
|
if err = expected.ErrorIfNotEqualLists(m); err != nil {
|
||||||
t.Fatalf("actual doesn't match expected: %v", err)
|
t.Fatalf(notEqualErrFmt, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1026,9 +1029,7 @@ func TestNameReferenceNamespaceTransformation(t *testing.T) {
|
|||||||
// It validates the change done is IsSameFuzzyNamespace which
|
// It validates the change done is IsSameFuzzyNamespace which
|
||||||
// uses the IsNsEquals method instead of the simple == operator.
|
// uses the IsNsEquals method instead of the simple == operator.
|
||||||
func TestNameReferenceCandidateSelection(t *testing.T) {
|
func TestNameReferenceCandidateSelection(t *testing.T) {
|
||||||
rf := resource.NewFactory(
|
m := resmaptest_test.NewRmBuilderDefault(t).
|
||||||
kunstruct.NewKunstructuredFactoryImpl())
|
|
||||||
m := resmaptest_test.NewRmBuilder(t, rf).
|
|
||||||
AddWithName("cm1", map[string]interface{}{
|
AddWithName("cm1", map[string]interface{}{
|
||||||
"apiVersion": "v1",
|
"apiVersion": "v1",
|
||||||
"kind": "ConfigMap",
|
"kind": "ConfigMap",
|
||||||
@@ -1045,7 +1046,7 @@ func TestNameReferenceCandidateSelection(t *testing.T) {
|
|||||||
AddWithName("deploy1", deploymentMap("", "p1-deploy1", "cm1", "secret1")).
|
AddWithName("deploy1", deploymentMap("", "p1-deploy1", "cm1", "secret1")).
|
||||||
ResMap()
|
ResMap()
|
||||||
|
|
||||||
expected := resmaptest_test.NewSeededRmBuilder(t, rf, m.ShallowCopy()).
|
expected := resmaptest_test.NewSeededRmBuilderDefault(t, m.ShallowCopy()).
|
||||||
ReplaceResource(deploymentMap("", "p1-deploy1", "p1-cm1-hash", "p1-secret1-hash")).
|
ReplaceResource(deploymentMap("", "p1-deploy1", "p1-cm1-hash", "p1-secret1-hash")).
|
||||||
ResMap()
|
ResMap()
|
||||||
|
|
||||||
@@ -1055,7 +1056,8 @@ func TestNameReferenceCandidateSelection(t *testing.T) {
|
|||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m.RemoveBuildAnnotations()
|
||||||
if err = expected.ErrorIfNotEqualLists(m); err != nil {
|
if err = expected.ErrorIfNotEqualLists(m); err != nil {
|
||||||
t.Fatalf("actual doesn't match expected: %v", err)
|
t.Fatalf(notEqualErrFmt, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,8 @@
|
|||||||
package accumulator
|
package accumulator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"sigs.k8s.io/kustomize/api/filters/refvar"
|
||||||
|
|
||||||
expansion2 "sigs.k8s.io/kustomize/api/internal/accumulator/expansion"
|
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/resmap"
|
"sigs.k8s.io/kustomize/api/resmap"
|
||||||
"sigs.k8s.io/kustomize/api/transform"
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -17,7 +13,6 @@ type refVarTransformer struct {
|
|||||||
varMap map[string]interface{}
|
varMap map[string]interface{}
|
||||||
replacementCounts map[string]int
|
replacementCounts map[string]int
|
||||||
fieldSpecs []types.FieldSpec
|
fieldSpecs []types.FieldSpec
|
||||||
mappingFunc func(string) interface{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// newRefVarTransformer returns a new refVarTransformer
|
// newRefVarTransformer returns a new refVarTransformer
|
||||||
@@ -31,66 +26,12 @@ func newRefVarTransformer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// replaceVars accepts as 'in' a string, or string array, which can have
|
|
||||||
// embedded instances of $VAR style variables, e.g. a container command string.
|
|
||||||
// The function returns the string with the variables expanded to their final
|
|
||||||
// values.
|
|
||||||
func (rv *refVarTransformer) replaceVars(in interface{}) (interface{}, error) {
|
|
||||||
switch vt := in.(type) {
|
|
||||||
case []interface{}:
|
|
||||||
var xs []interface{}
|
|
||||||
for _, a := range in.([]interface{}) {
|
|
||||||
x, ok := a.(string)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("expected array of strings, found %v", in)
|
|
||||||
}
|
|
||||||
xs = append(xs, expansion2.Expand(x, rv.mappingFunc))
|
|
||||||
}
|
|
||||||
return xs, nil
|
|
||||||
case map[string]interface{}:
|
|
||||||
inMap := in.(map[string]interface{})
|
|
||||||
xs := make(map[string]interface{}, len(inMap))
|
|
||||||
for k, v := range inMap {
|
|
||||||
s, ok := v.(string)
|
|
||||||
if !ok {
|
|
||||||
// This field can not contain a $(VAR) since it is not
|
|
||||||
// of string type. For instance .spec.replicas: 3 in
|
|
||||||
// a Deployment object
|
|
||||||
xs[k] = v
|
|
||||||
} else {
|
|
||||||
// This field can potentially contains a $(VAR) since it is
|
|
||||||
// of string type. For instance .spec.replicas: $(REPLICAS)
|
|
||||||
// in a Deployment object
|
|
||||||
xs[k] = expansion2.Expand(s, rv.mappingFunc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return xs, nil
|
|
||||||
case interface{}:
|
|
||||||
s, ok := in.(string)
|
|
||||||
if !ok {
|
|
||||||
// This field can not contain a $(VAR) since it is not of string type.
|
|
||||||
return in, nil
|
|
||||||
}
|
|
||||||
// This field can potentially contain a $(VAR) since it is
|
|
||||||
// of string type.
|
|
||||||
return expansion2.Expand(s, rv.mappingFunc), nil
|
|
||||||
// staticcheck erroneously claims that `case nil`
|
|
||||||
// is unreachable here, so suppressing it.
|
|
||||||
//nolint:staticcheck
|
|
||||||
case nil:
|
|
||||||
return nil, nil
|
|
||||||
default:
|
|
||||||
return "", fmt.Errorf("invalid type encountered %T", vt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnusedVars returns slice of Var names that were unused
|
// UnusedVars returns slice of Var names that were unused
|
||||||
// after a Transform run.
|
// after a Transform run.
|
||||||
func (rv *refVarTransformer) UnusedVars() []string {
|
func (rv *refVarTransformer) UnusedVars() []string {
|
||||||
var unused []string
|
var unused []string
|
||||||
for k := range rv.varMap {
|
for k := range rv.varMap {
|
||||||
_, ok := rv.replacementCounts[k]
|
if _, ok := rv.replacementCounts[k]; !ok {
|
||||||
if !ok {
|
|
||||||
unused = append(unused, k)
|
unused = append(unused, k)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,16 +41,15 @@ func (rv *refVarTransformer) UnusedVars() []string {
|
|||||||
// Transform replaces $(VAR) style variables with values.
|
// Transform replaces $(VAR) style variables with values.
|
||||||
func (rv *refVarTransformer) Transform(m resmap.ResMap) error {
|
func (rv *refVarTransformer) Transform(m resmap.ResMap) error {
|
||||||
rv.replacementCounts = make(map[string]int)
|
rv.replacementCounts = make(map[string]int)
|
||||||
rv.mappingFunc = expansion2.MappingFuncFor(
|
mf := refvar.MakePrimitiveReplacer(rv.replacementCounts, rv.varMap)
|
||||||
rv.replacementCounts, rv.varMap)
|
|
||||||
for _, res := range m.Resources() {
|
for _, res := range m.Resources() {
|
||||||
for _, fieldSpec := range rv.fieldSpecs {
|
for _, fieldSpec := range rv.fieldSpecs {
|
||||||
if res.OrgId().IsSelected(&fieldSpec.Gvk) {
|
err := res.ApplyFilter(refvar.Filter{
|
||||||
if err := transform.MutateField(
|
MappingFunc: mf,
|
||||||
res.Map(), fieldSpec.PathSlice(),
|
FieldSpec: fieldSpec,
|
||||||
false, rv.replaceVars); err != nil {
|
})
|
||||||
return err
|
if err != nil {
|
||||||
}
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,12 +7,10 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/k8sdeps/kunstruct"
|
|
||||||
"sigs.k8s.io/kustomize/api/resid"
|
|
||||||
"sigs.k8s.io/kustomize/api/resmap"
|
"sigs.k8s.io/kustomize/api/resmap"
|
||||||
"sigs.k8s.io/kustomize/api/resource"
|
|
||||||
resmaptest_test "sigs.k8s.io/kustomize/api/testutils/resmaptest"
|
resmaptest_test "sigs.k8s.io/kustomize/api/testutils/resmaptest"
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/resid"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRefVarTransformer(t *testing.T) {
|
func TestRefVarTransformer(t *testing.T) {
|
||||||
@@ -25,14 +23,12 @@ func TestRefVarTransformer(t *testing.T) {
|
|||||||
res resmap.ResMap
|
res resmap.ResMap
|
||||||
unused []string
|
unused []string
|
||||||
}
|
}
|
||||||
testCases := []struct {
|
testCases := map[string]struct {
|
||||||
description string
|
given given
|
||||||
given given
|
expected expected
|
||||||
expected expected
|
errMessage string
|
||||||
errMessage string
|
|
||||||
}{
|
}{
|
||||||
{
|
"var replacement in map[string]": {
|
||||||
description: "var replacement in map[string]",
|
|
||||||
given: given{
|
given: given{
|
||||||
varMap: map[string]interface{}{
|
varMap: map[string]interface{}{
|
||||||
"FOO": "replacementForFoo",
|
"FOO": "replacementForFoo",
|
||||||
@@ -44,11 +40,9 @@ func TestRefVarTransformer(t *testing.T) {
|
|||||||
{Gvk: resid.Gvk{Version: "v1", Kind: "ConfigMap"}, Path: "data/map"},
|
{Gvk: resid.Gvk{Version: "v1", Kind: "ConfigMap"}, Path: "data/map"},
|
||||||
{Gvk: resid.Gvk{Version: "v1", Kind: "ConfigMap"}, Path: "data/slice"},
|
{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/interface"},
|
||||||
{Gvk: resid.Gvk{Version: "v1", Kind: "ConfigMap"}, Path: "data/nil"},
|
|
||||||
{Gvk: resid.Gvk{Version: "v1", Kind: "ConfigMap"}, Path: "data/num"},
|
{Gvk: resid.Gvk{Version: "v1", Kind: "ConfigMap"}, Path: "data/num"},
|
||||||
},
|
},
|
||||||
res: resmaptest_test.NewRmBuilder(
|
res: resmaptest_test.NewRmBuilderDefault(t).
|
||||||
t, resource.NewFactory(kunstruct.NewKunstructuredFactoryImpl())).
|
|
||||||
Add(map[string]interface{}{
|
Add(map[string]interface{}{
|
||||||
"apiVersion": "v1",
|
"apiVersion": "v1",
|
||||||
"kind": "ConfigMap",
|
"kind": "ConfigMap",
|
||||||
@@ -63,7 +57,7 @@ func TestRefVarTransformer(t *testing.T) {
|
|||||||
"item4": "$(BAZ)+$(BAZ)",
|
"item4": "$(BAZ)+$(BAZ)",
|
||||||
"item5": "$(BOO)",
|
"item5": "$(BOO)",
|
||||||
"item6": "if $(BOO)",
|
"item6": "if $(BOO)",
|
||||||
"item7": 2019,
|
"item7": int64(2019),
|
||||||
},
|
},
|
||||||
"slice": []interface{}{
|
"slice": []interface{}{
|
||||||
"$(FOO)",
|
"$(FOO)",
|
||||||
@@ -74,13 +68,11 @@ func TestRefVarTransformer(t *testing.T) {
|
|||||||
"if $(BOO)",
|
"if $(BOO)",
|
||||||
},
|
},
|
||||||
"interface": "$(FOO)",
|
"interface": "$(FOO)",
|
||||||
"nil": nil,
|
"num": int64(2019),
|
||||||
"num": 2019,
|
|
||||||
}}).ResMap(),
|
}}).ResMap(),
|
||||||
},
|
},
|
||||||
expected: expected{
|
expected: expected{
|
||||||
res: resmaptest_test.NewRmBuilder(
|
res: resmaptest_test.NewRmBuilderDefault(t).
|
||||||
t, resource.NewFactory(kunstruct.NewKunstructuredFactoryImpl())).
|
|
||||||
Add(map[string]interface{}{
|
Add(map[string]interface{}{
|
||||||
"apiVersion": "v1",
|
"apiVersion": "v1",
|
||||||
"kind": "ConfigMap",
|
"kind": "ConfigMap",
|
||||||
@@ -95,7 +87,7 @@ func TestRefVarTransformer(t *testing.T) {
|
|||||||
"item4": "5+5",
|
"item4": "5+5",
|
||||||
"item5": true,
|
"item5": true,
|
||||||
"item6": "if true",
|
"item6": "if true",
|
||||||
"item7": 2019,
|
"item7": int64(2019),
|
||||||
},
|
},
|
||||||
"slice": []interface{}{
|
"slice": []interface{}{
|
||||||
"replacementForFoo",
|
"replacementForFoo",
|
||||||
@@ -106,21 +98,18 @@ func TestRefVarTransformer(t *testing.T) {
|
|||||||
"if true",
|
"if true",
|
||||||
},
|
},
|
||||||
"interface": "replacementForFoo",
|
"interface": "replacementForFoo",
|
||||||
"nil": nil,
|
"num": int64(2019),
|
||||||
"num": 2019,
|
|
||||||
}}).ResMap(),
|
}}).ResMap(),
|
||||||
unused: []string{"BAR"},
|
unused: []string{"BAR"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
"var replacement panic in map[string]": {
|
||||||
description: "var replacement panic in map[string]",
|
|
||||||
given: given{
|
given: given{
|
||||||
varMap: map[string]interface{}{},
|
varMap: map[string]interface{}{},
|
||||||
fs: []types.FieldSpec{
|
fs: []types.FieldSpec{
|
||||||
{Gvk: resid.Gvk{Version: "v1", Kind: "ConfigMap"}, Path: "data/slice"},
|
{Gvk: resid.Gvk{Version: "v1", Kind: "ConfigMap"}, Path: "data/slice"},
|
||||||
},
|
},
|
||||||
res: resmaptest_test.NewRmBuilder(
|
res: resmaptest_test.NewRmBuilderDefault(t).
|
||||||
t, resource.NewFactory(kunstruct.NewKunstructuredFactoryImpl())).
|
|
||||||
Add(map[string]interface{}{
|
Add(map[string]interface{}{
|
||||||
"apiVersion": "v1",
|
"apiVersion": "v1",
|
||||||
"kind": "ConfigMap",
|
"kind": "ConfigMap",
|
||||||
@@ -131,24 +120,52 @@ func TestRefVarTransformer(t *testing.T) {
|
|||||||
"slice": []interface{}{5}, // noticeably *not* a []string
|
"slice": []interface{}{5}, // noticeably *not* a []string
|
||||||
}}).ResMap(),
|
}}).ResMap(),
|
||||||
},
|
},
|
||||||
errMessage: "expected array of strings, found [5]",
|
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 _, tc := range testCases {
|
for tn, tc := range testCases {
|
||||||
t.Run(tc.description, func(t *testing.T) {
|
t.Run(tn, func(t *testing.T) {
|
||||||
// arrange
|
|
||||||
tr := newRefVarTransformer(tc.given.varMap, tc.given.fs)
|
tr := newRefVarTransformer(tc.given.varMap, tc.given.fs)
|
||||||
|
|
||||||
// act
|
|
||||||
err := tr.Transform(tc.given.res)
|
err := tr.Transform(tc.given.res)
|
||||||
|
|
||||||
// assert
|
|
||||||
if tc.errMessage != "" {
|
if tc.errMessage != "" {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("missing expected error %v", tc.errMessage)
|
t.Fatalf("missing expected error %v", tc.errMessage)
|
||||||
} else if err.Error() != tc.errMessage {
|
} else if err.Error() != tc.errMessage {
|
||||||
t.Fatalf("actual error doesn't match expected error: \nACTUAL: %v\nEXPECTED: %v", err.Error(), tc.errMessage)
|
t.Fatalf(`actual error doesn't match expected error:
|
||||||
|
ACTUAL: %v
|
||||||
|
EXPECTED: %v`,
|
||||||
|
err.Error(), tc.errMessage)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -158,7 +175,13 @@ func TestRefVarTransformer(t *testing.T) {
|
|||||||
a, e := tc.given.res, tc.expected.res
|
a, e := tc.given.res, tc.expected.res
|
||||||
if !reflect.DeepEqual(a, e) {
|
if !reflect.DeepEqual(a, e) {
|
||||||
err = e.ErrorIfNotEqualLists(a)
|
err = e.ErrorIfNotEqualLists(a)
|
||||||
t.Fatalf("actual doesn't match expected: \nACTUAL:\n%v\nEXPECTED:\n%v\nERR: %v", a, e, err)
|
t.Fatalf(`actual doesn't match expected:
|
||||||
|
ACTUAL:
|
||||||
|
%v
|
||||||
|
EXPECTED:
|
||||||
|
%v
|
||||||
|
ERR: %v`,
|
||||||
|
a, e, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
||||||
"sigs.k8s.io/kustomize/api/resid"
|
|
||||||
"sigs.k8s.io/kustomize/api/resmap"
|
"sigs.k8s.io/kustomize/api/resmap"
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/resid"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ResAccumulator accumulates resources and the rules
|
// ResAccumulator accumulates resources and the rules
|
||||||
@@ -41,13 +41,11 @@ func (ra *ResAccumulator) Vars() []types.Var {
|
|||||||
return ra.varSet.AsSlice()
|
return ra.varSet.AsSlice()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ra *ResAccumulator) AppendAll(
|
func (ra *ResAccumulator) AppendAll(resources resmap.ResMap) error {
|
||||||
resources resmap.ResMap) error {
|
|
||||||
return ra.resMap.AppendAll(resources)
|
return ra.resMap.AppendAll(resources)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ra *ResAccumulator) AbsorbAll(
|
func (ra *ResAccumulator) AbsorbAll(resources resmap.ResMap) error {
|
||||||
resources resmap.ResMap) error {
|
|
||||||
return ra.resMap.AbsorbAll(resources)
|
return ra.resMap.AbsorbAll(resources)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,16 +59,25 @@ func (ra *ResAccumulator) GetTransformerConfig() *builtinconfig.TransformerConfi
|
|||||||
return ra.tConfig
|
return ra.tConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MergeVars accumulates vars into ResAccumulator.
|
||||||
|
// A Var is a tuple of name, object reference and field reference.
|
||||||
|
// This func takes a list of vars from the current kustomization file and
|
||||||
|
// annotates the accumulated resources with the names of the vars that match
|
||||||
|
// those resources. E.g. if there's a var named "sam" that wants to get
|
||||||
|
// its data from a ConfigMap named "james", and the resource list contains a
|
||||||
|
// ConfigMap named "james", then that ConfigMap will be annotated with the
|
||||||
|
// var name "sam". Later this annotation is used to find the data for "sam"
|
||||||
|
// by digging into a particular fieldpath of "james".
|
||||||
func (ra *ResAccumulator) MergeVars(incoming []types.Var) error {
|
func (ra *ResAccumulator) MergeVars(incoming []types.Var) error {
|
||||||
for _, v := range incoming {
|
for _, v := range incoming {
|
||||||
targetId := resid.NewResIdWithNamespace(v.ObjRef.GVK(), v.ObjRef.Name, v.ObjRef.Namespace)
|
targetId := resid.NewResIdWithNamespace(v.ObjRef.GVK(), v.ObjRef.Name, v.ObjRef.Namespace)
|
||||||
idMatcher := targetId.GvknEquals
|
idMatcher := targetId.GvknEquals
|
||||||
if targetId.Namespace != "" || !targetId.IsNamespaceableKind() {
|
if targetId.Namespace != "" || targetId.IsClusterScoped() {
|
||||||
// Preserve backward compatibility. An empty namespace means
|
// Preserve backward compatibility. An empty namespace means
|
||||||
// wildcard search on the namespace hence we still use GvknEquals
|
// wildcard search on the namespace hence we still use GvknEquals
|
||||||
idMatcher = targetId.Equals
|
idMatcher = targetId.Equals
|
||||||
}
|
}
|
||||||
matched := ra.resMap.GetMatchingResourcesByOriginalId(idMatcher)
|
matched := ra.resMap.GetMatchingResourcesByAnyId(idMatcher)
|
||||||
if len(matched) > 1 {
|
if len(matched) > 1 {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"found %d resId matches for var %s "+
|
"found %d resId matches for var %s "+
|
||||||
@@ -100,18 +107,17 @@ func (ra *ResAccumulator) findVarValueFromResources(v types.Var) (interface{}, e
|
|||||||
for _, res := range ra.resMap.Resources() {
|
for _, res := range ra.resMap.Resources() {
|
||||||
for _, varName := range res.GetRefVarNames() {
|
for _, varName := range res.GetRefVarNames() {
|
||||||
if varName == v.Name {
|
if varName == v.Name {
|
||||||
|
//nolint: staticcheck
|
||||||
s, err := res.GetFieldValue(v.FieldRef.FieldPath)
|
s, err := res.GetFieldValue(v.FieldRef.FieldPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf(
|
return "", fmt.Errorf(
|
||||||
"field specified in var '%v' "+
|
"field specified in var '%v' "+
|
||||||
"not found in corresponding resource", v)
|
"not found in corresponding resource", v)
|
||||||
}
|
}
|
||||||
|
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", fmt.Errorf(
|
return "", fmt.Errorf(
|
||||||
"var '%v' cannot be mapped to a field "+
|
"var '%v' cannot be mapped to a field "+
|
||||||
"in the set of known resources", v)
|
"in the set of known resources", v)
|
||||||
@@ -127,10 +133,8 @@ func (ra *ResAccumulator) makeVarReplacementMap() (map[string]interface{}, error
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
result[v.Name] = s
|
result[v.Name] = s
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,6 +165,26 @@ func (ra *ResAccumulator) FixBackReferences() (err error) {
|
|||||||
if ra.tConfig.NameReference == nil {
|
if ra.tConfig.NameReference == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return ra.Transform(newNameReferenceTransformer(
|
return ra.Transform(
|
||||||
ra.tConfig.NameReference))
|
newNameReferenceTransformer(ra.tConfig.NameReference))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intersection drops the resources which "other" does not have.
|
||||||
|
func (ra *ResAccumulator) Intersection(other resmap.ResMap) error {
|
||||||
|
for _, curId := range ra.resMap.AllIds() {
|
||||||
|
toDelete := true
|
||||||
|
for _, otherId := range other.AllIds() {
|
||||||
|
if otherId == curId {
|
||||||
|
toDelete = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if toDelete {
|
||||||
|
err := ra.resMap.Remove(curId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,26 +10,25 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
. "sigs.k8s.io/kustomize/api/internal/accumulator"
|
. "sigs.k8s.io/kustomize/api/internal/accumulator"
|
||||||
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
||||||
"sigs.k8s.io/kustomize/api/k8sdeps/kunstruct"
|
"sigs.k8s.io/kustomize/api/provider"
|
||||||
"sigs.k8s.io/kustomize/api/resid"
|
|
||||||
"sigs.k8s.io/kustomize/api/resmap"
|
"sigs.k8s.io/kustomize/api/resmap"
|
||||||
"sigs.k8s.io/kustomize/api/resource"
|
"sigs.k8s.io/kustomize/api/resource"
|
||||||
resmaptest_test "sigs.k8s.io/kustomize/api/testutils/resmaptest"
|
resmaptest_test "sigs.k8s.io/kustomize/api/testutils/resmaptest"
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/resid"
|
||||||
)
|
)
|
||||||
|
|
||||||
func makeResAccumulator(t *testing.T) (*ResAccumulator, *resource.Factory) {
|
func makeResAccumulator(t *testing.T) *ResAccumulator {
|
||||||
ra := MakeEmptyAccumulator()
|
ra := MakeEmptyAccumulator()
|
||||||
err := ra.MergeConfig(builtinconfig.MakeDefaultConfig())
|
err := ra.MergeConfig(builtinconfig.MakeDefaultConfig())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected err: %v", err)
|
t.Fatalf("unexpected err: %v", err)
|
||||||
}
|
}
|
||||||
rf := resource.NewFactory(
|
|
||||||
kunstruct.NewKunstructuredFactoryImpl())
|
|
||||||
err = ra.AppendAll(
|
err = ra.AppendAll(
|
||||||
resmaptest_test.NewRmBuilder(t, rf).
|
resmaptest_test.NewRmBuilderDefault(t).
|
||||||
Add(map[string]interface{}{
|
Add(map[string]interface{}{
|
||||||
"apiVersion": "apps/v1",
|
"apiVersion": "apps/v1",
|
||||||
"kind": "Deployment",
|
"kind": "Deployment",
|
||||||
@@ -66,11 +65,11 @@ func makeResAccumulator(t *testing.T) (*ResAccumulator, *resource.Factory) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected err: %v", err)
|
t.Fatalf("unexpected err: %v", err)
|
||||||
}
|
}
|
||||||
return ra, rf
|
return ra
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolveVarsHappy(t *testing.T) {
|
func TestResolveVarsHappy(t *testing.T) {
|
||||||
ra, _ := makeResAccumulator(t)
|
ra := makeResAccumulator(t)
|
||||||
err := ra.MergeVars([]types.Var{
|
err := ra.MergeVars([]types.Var{
|
||||||
{
|
{
|
||||||
Name: "SERVICE_ONE",
|
Name: "SERVICE_ONE",
|
||||||
@@ -99,7 +98,7 @@ func TestResolveVarsHappy(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestResolveVarsOneUnused(t *testing.T) {
|
func TestResolveVarsOneUnused(t *testing.T) {
|
||||||
ra, _ := makeResAccumulator(t)
|
ra := makeResAccumulator(t)
|
||||||
err := ra.MergeVars([]types.Var{
|
err := ra.MergeVars([]types.Var{
|
||||||
{
|
{
|
||||||
Name: "SERVICE_ONE",
|
Name: "SERVICE_ONE",
|
||||||
@@ -140,11 +139,10 @@ func expectLog(t *testing.T, log bytes.Buffer, expect string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestResolveVarsVarNeedsDisambiguation(t *testing.T) {
|
func TestResolveVarsVarNeedsDisambiguation(t *testing.T) {
|
||||||
ra, rf := makeResAccumulator(t)
|
ra := makeResAccumulator(t)
|
||||||
|
|
||||||
rm0 := resmap.New()
|
rm0 := resmap.New()
|
||||||
err := rm0.Append(
|
err := rm0.Append(
|
||||||
rf.FromMap(
|
provider.NewDefaultDepProvider().GetResourceFactory().FromMap(
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"apiVersion": "v1",
|
"apiVersion": "v1",
|
||||||
"kind": "Service",
|
"kind": "Service",
|
||||||
@@ -213,8 +211,7 @@ func makeVarToNamepaceAndPath(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestResolveVarConflicts(t *testing.T) {
|
func TestResolveVarConflicts(t *testing.T) {
|
||||||
rf := resource.NewFactory(kunstruct.NewKunstructuredFactoryImpl())
|
rf := provider.NewDefaultDepProvider().GetResourceFactory()
|
||||||
|
|
||||||
// create configmaps in foo and bar namespaces with `data.provider` values.
|
// create configmaps in foo and bar namespaces with `data.provider` values.
|
||||||
fooAws := makeNamespacedConfigMapWithDataProviderValue("foo", "aws")
|
fooAws := makeNamespacedConfigMapWithDataProviderValue("foo", "aws")
|
||||||
barAws := makeNamespacedConfigMapWithDataProviderValue("bar", "aws")
|
barAws := makeNamespacedConfigMapWithDataProviderValue("bar", "aws")
|
||||||
@@ -228,20 +225,26 @@ func TestResolveVarConflicts(t *testing.T) {
|
|||||||
// create accumulators holding apparently conflicting vars that are not
|
// create accumulators holding apparently conflicting vars that are not
|
||||||
// actually in conflict because they point to the same concrete value.
|
// actually in conflict because they point to the same concrete value.
|
||||||
rm0 := resmap.New()
|
rm0 := resmap.New()
|
||||||
rm0.Append(rf.FromMap(fooAws))
|
err := rm0.Append(rf.FromMap(fooAws))
|
||||||
|
require.NoError(t, err)
|
||||||
ac0 := MakeEmptyAccumulator()
|
ac0 := MakeEmptyAccumulator()
|
||||||
ac0.AppendAll(rm0)
|
err = ac0.AppendAll(rm0)
|
||||||
ac0.MergeVars([]types.Var{varFoo})
|
require.NoError(t, err)
|
||||||
|
err = ac0.MergeVars([]types.Var{varFoo})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
rm1 := resmap.New()
|
rm1 := resmap.New()
|
||||||
rm1.Append(rf.FromMap(barAws))
|
err = rm1.Append(rf.FromMap(barAws))
|
||||||
|
require.NoError(t, err)
|
||||||
ac1 := MakeEmptyAccumulator()
|
ac1 := MakeEmptyAccumulator()
|
||||||
ac1.AppendAll(rm1)
|
err = ac1.AppendAll(rm1)
|
||||||
ac1.MergeVars([]types.Var{varBar})
|
require.NoError(t, err)
|
||||||
|
err = ac1.MergeVars([]types.Var{varBar})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
// validate that two vars of the same name which reference the same concrete
|
// validate that two vars of the same name which reference the same concrete
|
||||||
// value do not produce a conflict.
|
// value do not produce a conflict.
|
||||||
err := ac0.MergeAccumulator(ac1)
|
err = ac0.MergeAccumulator(ac1)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("see bug gh-1600")
|
t.Fatalf("see bug gh-1600")
|
||||||
}
|
}
|
||||||
@@ -250,10 +253,13 @@ func TestResolveVarConflicts(t *testing.T) {
|
|||||||
// two above (because it contains a variable whose name is used in the other
|
// two above (because it contains a variable whose name is used in the other
|
||||||
// accumulators AND whose concrete values are different).
|
// accumulators AND whose concrete values are different).
|
||||||
rm2 := resmap.New()
|
rm2 := resmap.New()
|
||||||
rm2.Append(rf.FromMap(barGcp))
|
err = rm2.Append(rf.FromMap(barGcp))
|
||||||
|
require.NoError(t, err)
|
||||||
ac2 := MakeEmptyAccumulator()
|
ac2 := MakeEmptyAccumulator()
|
||||||
ac2.AppendAll(rm2)
|
err = ac2.AppendAll(rm2)
|
||||||
ac2.MergeVars([]types.Var{varBar})
|
require.NoError(t, err)
|
||||||
|
err = ac2.MergeVars([]types.Var{varBar})
|
||||||
|
require.NoError(t, err)
|
||||||
err = ac1.MergeAccumulator(ac2)
|
err = ac1.MergeAccumulator(ac2)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("dupe vars w/ different concrete values should conflict")
|
t.Fatalf("dupe vars w/ different concrete values should conflict")
|
||||||
@@ -261,7 +267,7 @@ func TestResolveVarConflicts(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestResolveVarsGoodResIdBadField(t *testing.T) {
|
func TestResolveVarsGoodResIdBadField(t *testing.T) {
|
||||||
ra, _ := makeResAccumulator(t)
|
ra := makeResAccumulator(t)
|
||||||
err := ra.MergeVars([]types.Var{
|
err := ra.MergeVars([]types.Var{
|
||||||
{
|
{
|
||||||
Name: "SERVICE_ONE",
|
Name: "SERVICE_ONE",
|
||||||
@@ -286,7 +292,7 @@ func TestResolveVarsGoodResIdBadField(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestResolveVarsUnmappableVar(t *testing.T) {
|
func TestResolveVarsUnmappableVar(t *testing.T) {
|
||||||
ra, _ := makeResAccumulator(t)
|
ra := makeResAccumulator(t)
|
||||||
err := ra.MergeVars([]types.Var{
|
err := ra.MergeVars([]types.Var{
|
||||||
{
|
{
|
||||||
Name: "SERVICE_THREE",
|
Name: "SERVICE_THREE",
|
||||||
@@ -310,7 +316,7 @@ func TestResolveVarsUnmappableVar(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestResolveVarsWithNoambiguation(t *testing.T) {
|
func TestResolveVarsWithNoambiguation(t *testing.T) {
|
||||||
ra1, rf := makeResAccumulator(t)
|
ra1 := makeResAccumulator(t)
|
||||||
err := ra1.MergeVars([]types.Var{
|
err := ra1.MergeVars([]types.Var{
|
||||||
{
|
{
|
||||||
Name: "SERVICE_ONE",
|
Name: "SERVICE_ONE",
|
||||||
@@ -327,7 +333,7 @@ func TestResolveVarsWithNoambiguation(t *testing.T) {
|
|||||||
// Create another accumulator having a resource with different prefix
|
// Create another accumulator having a resource with different prefix
|
||||||
ra2 := MakeEmptyAccumulator()
|
ra2 := MakeEmptyAccumulator()
|
||||||
|
|
||||||
m := resmaptest_test.NewRmBuilder(t, rf).
|
m := resmaptest_test.NewRmBuilderDefault(t).
|
||||||
Add(map[string]interface{}{
|
Add(map[string]interface{}{
|
||||||
"apiVersion": "apps/v1",
|
"apiVersion": "apps/v1",
|
||||||
"kind": "Deployment",
|
"kind": "Deployment",
|
||||||
@@ -348,19 +354,21 @@ func TestResolveVarsWithNoambiguation(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}).
|
}}).
|
||||||
|
// Make it seem like this resource
|
||||||
|
// went through a prefix transformer.
|
||||||
Add(map[string]interface{}{
|
Add(map[string]interface{}{
|
||||||
"apiVersion": "v1",
|
"apiVersion": "v1",
|
||||||
"kind": "Service",
|
"kind": "Service",
|
||||||
"metadata": map[string]interface{}{
|
"metadata": map[string]interface{}{
|
||||||
"name": "backendOne",
|
"name": "sub-backendOne",
|
||||||
|
"annotations": map[string]interface{}{
|
||||||
|
"internal.config.kubernetes.io/previousKinds": "Service",
|
||||||
|
"internal.config.kubernetes.io/previousNames": "backendOne",
|
||||||
|
"internal.config.kubernetes.io/previousNamespaces": "default",
|
||||||
|
"internal.config.kubernetes.io/prefixes": "sub-",
|
||||||
|
},
|
||||||
}}).ResMap()
|
}}).ResMap()
|
||||||
|
|
||||||
// Make it seem like this resource
|
|
||||||
// went through a prefix transformer.
|
|
||||||
r := m.GetByIndex(1)
|
|
||||||
r.AddNamePrefix("sub-")
|
|
||||||
r.SetName("sub-backendOne") // original name remains "backendOne"
|
|
||||||
|
|
||||||
err = ra2.AppendAll(m)
|
err = ra2.AppendAll(m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected err: %v", err)
|
t.Fatalf("unexpected err: %v", err)
|
||||||
@@ -402,7 +410,8 @@ func find(name string, resMap resmap.ResMap) *resource.Resource {
|
|||||||
func getCommand(r *resource.Resource) string {
|
func getCommand(r *resource.Resource) string {
|
||||||
var m map[string]interface{}
|
var m map[string]interface{}
|
||||||
var c []interface{}
|
var c []interface{}
|
||||||
m, _ = r.Map()["spec"].(map[string]interface{})
|
resourceMap, _ := r.Map()
|
||||||
|
m, _ = resourceMap["spec"].(map[string]interface{})
|
||||||
m, _ = m["template"].(map[string]interface{})
|
m, _ = m["template"].(map[string]interface{})
|
||||||
m, _ = m["spec"].(map[string]interface{})
|
m, _ = m["spec"].(map[string]interface{})
|
||||||
c, _ = m["containers"].([]interface{})
|
c, _ = m["containers"].([]interface{})
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"sigs.k8s.io/kustomize/api/filters/annotations"
|
"sigs.k8s.io/kustomize/api/filters/annotations"
|
||||||
"sigs.k8s.io/kustomize/api/resmap"
|
"sigs.k8s.io/kustomize/api/resmap"
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
"sigs.k8s.io/kustomize/kyaml/filtersutil"
|
|
||||||
"sigs.k8s.io/yaml"
|
"sigs.k8s.io/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -25,16 +24,13 @@ func (p *AnnotationsTransformerPlugin) Config(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *AnnotationsTransformerPlugin) Transform(m resmap.ResMap) error {
|
func (p *AnnotationsTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||||
for _, r := range m.Resources() {
|
if len(p.Annotations) == 0 {
|
||||||
err := filtersutil.ApplyToJSON(annotations.Filter{
|
return nil
|
||||||
Annotations: p.Annotations,
|
|
||||||
FsSlice: p.FieldSpecs,
|
|
||||||
}, r)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
return m.ApplyFilter(annotations.Filter{
|
||||||
|
Annotations: p.Annotations,
|
||||||
|
FsSlice: p.FieldSpecs,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAnnotationsTransformerPlugin() resmap.TransformerPlugin {
|
func NewAnnotationsTransformerPlugin() resmap.TransformerPlugin {
|
||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type HashTransformerPlugin struct {
|
type HashTransformerPlugin struct {
|
||||||
hasher ifc.KunstructuredHasher
|
hasher ifc.KustHasher
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *HashTransformerPlugin) Config(
|
func (p *HashTransformerPlugin) Config(
|
||||||
@@ -24,10 +24,11 @@ func (p *HashTransformerPlugin) Config(
|
|||||||
func (p *HashTransformerPlugin) Transform(m resmap.ResMap) error {
|
func (p *HashTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||||
for _, res := range m.Resources() {
|
for _, res := range m.Resources() {
|
||||||
if res.NeedHashSuffix() {
|
if res.NeedHashSuffix() {
|
||||||
h, err := p.hasher.Hash(res)
|
h, err := res.Hash(p.hasher)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
res.StorePreviousId()
|
||||||
res.SetName(fmt.Sprintf("%s-%s", res.GetName(), h))
|
res.SetName(fmt.Sprintf("%s-%s", res.GetName(), h))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
339
api/internal/builtins/HelmChartInflationGenerator.go
Normal file
339
api/internal/builtins/HelmChartInflationGenerator.go
Normal file
@@ -0,0 +1,339 @@
|
|||||||
|
// Code generated by pluginator on HelmChartInflationGenerator; DO NOT EDIT.
|
||||||
|
// pluginator {unknown 1970-01-01T00:00:00Z }
|
||||||
|
|
||||||
|
package builtins
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/imdario/mergo"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"sigs.k8s.io/kustomize/api/resmap"
|
||||||
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
|
"sigs.k8s.io/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HelmChartInflationGeneratorPlugin is a plugin to generate resources
|
||||||
|
// from a remote or local helm chart.
|
||||||
|
type HelmChartInflationGeneratorPlugin struct {
|
||||||
|
h *resmap.PluginHelpers
|
||||||
|
types.HelmGlobals
|
||||||
|
types.HelmChart
|
||||||
|
tmpDir string
|
||||||
|
}
|
||||||
|
|
||||||
|
var KustomizePlugin HelmChartInflationGeneratorPlugin
|
||||||
|
|
||||||
|
const (
|
||||||
|
valuesMergeOptionMerge = "merge"
|
||||||
|
valuesMergeOptionOverride = "override"
|
||||||
|
valuesMergeOptionReplace = "replace"
|
||||||
|
)
|
||||||
|
|
||||||
|
var legalMergeOptions = []string{
|
||||||
|
valuesMergeOptionMerge,
|
||||||
|
valuesMergeOptionOverride,
|
||||||
|
valuesMergeOptionReplace,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config uses the input plugin configurations `config` to setup the generator
|
||||||
|
// options
|
||||||
|
func (p *HelmChartInflationGeneratorPlugin) Config(
|
||||||
|
h *resmap.PluginHelpers, config []byte) (err error) {
|
||||||
|
if h.GeneralConfig() == nil {
|
||||||
|
return fmt.Errorf("unable to access general config")
|
||||||
|
}
|
||||||
|
if !h.GeneralConfig().HelmConfig.Enabled {
|
||||||
|
return fmt.Errorf("must specify --enable-helm")
|
||||||
|
}
|
||||||
|
if h.GeneralConfig().HelmConfig.Command == "" {
|
||||||
|
return fmt.Errorf("must specify --helm-command")
|
||||||
|
}
|
||||||
|
p.h = h
|
||||||
|
if err = yaml.Unmarshal(config, p); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return p.validateArgs()
|
||||||
|
}
|
||||||
|
|
||||||
|
// This uses the real file system since tmpDir may be used
|
||||||
|
// by the helm subprocess. Cannot use a chroot jail or fake
|
||||||
|
// filesystem since we allow the user to use previously
|
||||||
|
// downloaded charts. This is safe since this plugin is
|
||||||
|
// owned by kustomize.
|
||||||
|
func (p *HelmChartInflationGeneratorPlugin) establishTmpDir() (err error) {
|
||||||
|
if p.tmpDir != "" {
|
||||||
|
// already done.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
p.tmpDir, err = ioutil.TempDir("", "kustomize-helm-")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *HelmChartInflationGeneratorPlugin) validateArgs() (err error) {
|
||||||
|
if p.Name == "" {
|
||||||
|
return fmt.Errorf("chart name cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChartHome might be consulted by the plugin (to read
|
||||||
|
// values files below it), so it must be located under
|
||||||
|
// the loader root (unless root restrictions are
|
||||||
|
// disabled, in which case this can be an absolute path).
|
||||||
|
if p.ChartHome == "" {
|
||||||
|
p.ChartHome = "charts"
|
||||||
|
}
|
||||||
|
|
||||||
|
// The ValuesFile may be consulted by the plugin, so it must
|
||||||
|
// be under the loader root (unless root restrictions are
|
||||||
|
// disabled).
|
||||||
|
if p.ValuesFile == "" {
|
||||||
|
p.ValuesFile = filepath.Join(p.ChartHome, p.Name, "values.yaml")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = p.errIfIllegalValuesMerge(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfigHome is not loaded by the plugin, and can be located anywhere.
|
||||||
|
if p.ConfigHome == "" {
|
||||||
|
if err = p.establishTmpDir(); err != nil {
|
||||||
|
return errors.Wrap(
|
||||||
|
err, "unable to create tmp dir for HELM_CONFIG_HOME")
|
||||||
|
}
|
||||||
|
p.ConfigHome = filepath.Join(p.tmpDir, "helm")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *HelmChartInflationGeneratorPlugin) errIfIllegalValuesMerge() error {
|
||||||
|
if p.ValuesMerge == "" {
|
||||||
|
// Use the default.
|
||||||
|
p.ValuesMerge = valuesMergeOptionOverride
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, opt := range legalMergeOptions {
|
||||||
|
if p.ValuesMerge == opt {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("valuesMerge must be one of %v", legalMergeOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *HelmChartInflationGeneratorPlugin) absChartHome() string {
|
||||||
|
if filepath.IsAbs(p.ChartHome) {
|
||||||
|
return p.ChartHome
|
||||||
|
}
|
||||||
|
return filepath.Join(p.h.Loader().Root(), p.ChartHome)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *HelmChartInflationGeneratorPlugin) runHelmCommand(
|
||||||
|
args []string) ([]byte, error) {
|
||||||
|
stdout := new(bytes.Buffer)
|
||||||
|
stderr := new(bytes.Buffer)
|
||||||
|
cmd := exec.Command(p.h.GeneralConfig().HelmConfig.Command, args...)
|
||||||
|
cmd.Stdout = stdout
|
||||||
|
cmd.Stderr = stderr
|
||||||
|
env := []string{
|
||||||
|
fmt.Sprintf("HELM_CONFIG_HOME=%s", p.ConfigHome),
|
||||||
|
fmt.Sprintf("HELM_CACHE_HOME=%s/.cache", p.ConfigHome),
|
||||||
|
fmt.Sprintf("HELM_DATA_HOME=%s/.data", p.ConfigHome)}
|
||||||
|
cmd.Env = append(os.Environ(), env...)
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
helm := p.h.GeneralConfig().HelmConfig.Command
|
||||||
|
err = errors.Wrap(
|
||||||
|
fmt.Errorf(
|
||||||
|
"unable to run: '%s %s' with env=%s (is '%s' installed?)",
|
||||||
|
helm, strings.Join(args, " "), env, helm),
|
||||||
|
stderr.String(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return stdout.Bytes(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// createNewMergedValuesFile replaces/merges original values file with ValuesInline.
|
||||||
|
func (p *HelmChartInflationGeneratorPlugin) createNewMergedValuesFile() (
|
||||||
|
path string, err error) {
|
||||||
|
if p.ValuesMerge == valuesMergeOptionMerge ||
|
||||||
|
p.ValuesMerge == valuesMergeOptionOverride {
|
||||||
|
if err = p.replaceValuesInline(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var b []byte
|
||||||
|
b, err = yaml.Marshal(p.ValuesInline)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return p.writeValuesBytes(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *HelmChartInflationGeneratorPlugin) replaceValuesInline() error {
|
||||||
|
pValues, err := p.h.Loader().Load(p.ValuesFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
chValues := make(map[string]interface{})
|
||||||
|
if err = yaml.Unmarshal(pValues, &chValues); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch p.ValuesMerge {
|
||||||
|
case valuesMergeOptionOverride:
|
||||||
|
err = mergo.Merge(
|
||||||
|
&chValues, p.ValuesInline, mergo.WithOverride)
|
||||||
|
case valuesMergeOptionMerge:
|
||||||
|
err = mergo.Merge(&chValues, p.ValuesInline)
|
||||||
|
}
|
||||||
|
p.ValuesInline = chValues
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// copyValuesFile to avoid branching. TODO: get rid of this.
|
||||||
|
func (p *HelmChartInflationGeneratorPlugin) copyValuesFile() (string, error) {
|
||||||
|
b, err := p.h.Loader().Load(p.ValuesFile)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return p.writeValuesBytes(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write a absolute path file in the tmp file system.
|
||||||
|
func (p *HelmChartInflationGeneratorPlugin) writeValuesBytes(
|
||||||
|
b []byte) (string, error) {
|
||||||
|
if err := p.establishTmpDir(); err != nil {
|
||||||
|
return "", fmt.Errorf("cannot create tmp dir to write helm values")
|
||||||
|
}
|
||||||
|
path := filepath.Join(p.tmpDir, p.Name+"-kustomize-values.yaml")
|
||||||
|
return path, ioutil.WriteFile(path, b, 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *HelmChartInflationGeneratorPlugin) cleanup() {
|
||||||
|
if p.tmpDir != "" {
|
||||||
|
os.RemoveAll(p.tmpDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate implements generator
|
||||||
|
func (p *HelmChartInflationGeneratorPlugin) Generate() (rm resmap.ResMap, err error) {
|
||||||
|
defer p.cleanup()
|
||||||
|
if err = p.checkHelmVersion(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if path, exists := p.chartExistsLocally(); !exists {
|
||||||
|
if p.Repo == "" {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"no repo specified for pull, no chart found at '%s'", path)
|
||||||
|
}
|
||||||
|
if _, err := p.runHelmCommand(p.pullCommand()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(p.ValuesInline) > 0 {
|
||||||
|
p.ValuesFile, err = p.createNewMergedValuesFile()
|
||||||
|
} else {
|
||||||
|
p.ValuesFile, err = p.copyValuesFile()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var stdout []byte
|
||||||
|
stdout, err = p.runHelmCommand(p.templateCommand())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rm, err = p.h.ResmapFactory().NewResMapFromBytes(stdout)
|
||||||
|
if err == nil {
|
||||||
|
return rm, nil
|
||||||
|
}
|
||||||
|
// try to remove the contents before first "---" because
|
||||||
|
// helm may produce messages to stdout before it
|
||||||
|
stdoutStr := string(stdout)
|
||||||
|
if idx := strings.Index(stdoutStr, "---"); idx != -1 {
|
||||||
|
return p.h.ResmapFactory().NewResMapFromBytes([]byte(stdoutStr[idx:]))
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *HelmChartInflationGeneratorPlugin) templateCommand() []string {
|
||||||
|
args := []string{"template"}
|
||||||
|
if p.ReleaseName != "" {
|
||||||
|
args = append(args, p.ReleaseName)
|
||||||
|
}
|
||||||
|
if p.Namespace != "" {
|
||||||
|
args = append(args, "--namespace", p.Namespace)
|
||||||
|
}
|
||||||
|
args = append(args, filepath.Join(p.absChartHome(), p.Name))
|
||||||
|
if p.ValuesFile != "" {
|
||||||
|
args = append(args, "--values", p.ValuesFile)
|
||||||
|
}
|
||||||
|
if p.ReleaseName == "" {
|
||||||
|
// AFAICT, this doesn't work as intended due to a bug in helm.
|
||||||
|
// See https://github.com/helm/helm/issues/6019
|
||||||
|
// I've tried placing the flag before and after the name argument.
|
||||||
|
args = append(args, "--generate-name")
|
||||||
|
}
|
||||||
|
if p.IncludeCRDs {
|
||||||
|
args = append(args, "--include-crds")
|
||||||
|
}
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *HelmChartInflationGeneratorPlugin) pullCommand() []string {
|
||||||
|
args := []string{
|
||||||
|
"pull",
|
||||||
|
"--untar",
|
||||||
|
"--untardir", p.absChartHome(),
|
||||||
|
"--repo", p.Repo,
|
||||||
|
p.Name}
|
||||||
|
if p.Version != "" {
|
||||||
|
args = append(args, "--version", p.Version)
|
||||||
|
}
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
|
||||||
|
// chartExistsLocally will return true if the chart does exist in
|
||||||
|
// local chart home.
|
||||||
|
func (p *HelmChartInflationGeneratorPlugin) chartExistsLocally() (string, bool) {
|
||||||
|
path := filepath.Join(p.absChartHome(), p.Name)
|
||||||
|
s, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return path, s.IsDir()
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkHelmVersion will return an error if the helm version is not V3
|
||||||
|
func (p *HelmChartInflationGeneratorPlugin) checkHelmVersion() error {
|
||||||
|
stdout, err := p.runHelmCommand([]string{"version", "-c", "--short"})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r, err := regexp.Compile(`v?\d+(\.\d+)+`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v := r.FindString(string(stdout))
|
||||||
|
if v == "" {
|
||||||
|
return fmt.Errorf("cannot find version string in %s", string(stdout))
|
||||||
|
}
|
||||||
|
if v[0] == 'v' {
|
||||||
|
v = v[1:]
|
||||||
|
}
|
||||||
|
majorVersion := strings.Split(v, ".")[0]
|
||||||
|
if majorVersion != "3" {
|
||||||
|
return fmt.Errorf("this plugin requires helm V3 but got v%s", v)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHelmChartInflationGeneratorPlugin() resmap.GeneratorPlugin {
|
||||||
|
return &HelmChartInflationGeneratorPlugin{}
|
||||||
|
}
|
||||||
33
api/internal/builtins/IAMPolicyGenerator.go
Normal file
33
api/internal/builtins/IAMPolicyGenerator.go
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
// Code generated by pluginator on IAMPolicyGenerator; DO NOT EDIT.
|
||||||
|
// pluginator {unknown 1970-01-01T00:00:00Z }
|
||||||
|
|
||||||
|
package builtins
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sigs.k8s.io/kustomize/api/filters/iampolicygenerator"
|
||||||
|
"sigs.k8s.io/kustomize/api/resmap"
|
||||||
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
|
"sigs.k8s.io/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IAMPolicyGeneratorPlugin struct {
|
||||||
|
types.IAMPolicyGeneratorArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *IAMPolicyGeneratorPlugin) Config(h *resmap.PluginHelpers, config []byte) (err error) {
|
||||||
|
p.IAMPolicyGeneratorArgs = types.IAMPolicyGeneratorArgs{}
|
||||||
|
err = yaml.Unmarshal(config, p)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *IAMPolicyGeneratorPlugin) Generate() (resmap.ResMap, error) {
|
||||||
|
r := resmap.New()
|
||||||
|
err := r.ApplyFilter(iampolicygenerator.Filter{
|
||||||
|
IAMPolicyGenerator: p.IAMPolicyGeneratorArgs,
|
||||||
|
})
|
||||||
|
return r, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIAMPolicyGeneratorPlugin() resmap.GeneratorPlugin {
|
||||||
|
return &IAMPolicyGeneratorPlugin{}
|
||||||
|
}
|
||||||
41
api/internal/builtins/ImageTagTransformer.go
Normal file
41
api/internal/builtins/ImageTagTransformer.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
// Code generated by pluginator on ImageTagTransformer; DO NOT EDIT.
|
||||||
|
// pluginator {unknown 1970-01-01T00:00:00Z }
|
||||||
|
|
||||||
|
package builtins
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sigs.k8s.io/kustomize/api/filters/imagetag"
|
||||||
|
"sigs.k8s.io/kustomize/api/resmap"
|
||||||
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
|
"sigs.k8s.io/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Find matching image declarations and replace
|
||||||
|
// the name, tag and/or digest.
|
||||||
|
type ImageTagTransformerPlugin struct {
|
||||||
|
ImageTag types.Image `json:"imageTag,omitempty" yaml:"imageTag,omitempty"`
|
||||||
|
FieldSpecs []types.FieldSpec `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ImageTagTransformerPlugin) Config(
|
||||||
|
_ *resmap.PluginHelpers, c []byte) (err error) {
|
||||||
|
p.ImageTag = types.Image{}
|
||||||
|
p.FieldSpecs = nil
|
||||||
|
return yaml.Unmarshal(c, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ImageTagTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||||
|
if err := m.ApplyFilter(imagetag.LegacyFilter{
|
||||||
|
ImageTag: p.ImageTag,
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return m.ApplyFilter(imagetag.Filter{
|
||||||
|
ImageTag: p.ImageTag,
|
||||||
|
FsSlice: p.FieldSpecs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewImageTagTransformerPlugin() resmap.TransformerPlugin {
|
||||||
|
return &ImageTagTransformerPlugin{}
|
||||||
|
}
|
||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"sigs.k8s.io/kustomize/api/filters/labels"
|
"sigs.k8s.io/kustomize/api/filters/labels"
|
||||||
"sigs.k8s.io/kustomize/api/resmap"
|
"sigs.k8s.io/kustomize/api/resmap"
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
"sigs.k8s.io/kustomize/kyaml/filtersutil"
|
|
||||||
"sigs.k8s.io/yaml"
|
"sigs.k8s.io/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -25,16 +24,13 @@ func (p *LabelTransformerPlugin) Config(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *LabelTransformerPlugin) Transform(m resmap.ResMap) error {
|
func (p *LabelTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||||
for _, r := range m.Resources() {
|
if len(p.Labels) == 0 {
|
||||||
err := filtersutil.ApplyToJSON(labels.Filter{
|
return nil
|
||||||
Labels: p.Labels,
|
|
||||||
FsSlice: p.FieldSpecs,
|
|
||||||
}, r)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
return m.ApplyFilter(labels.Filter{
|
||||||
|
Labels: p.Labels,
|
||||||
|
FsSlice: p.FieldSpecs,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLabelTransformerPlugin() resmap.TransformerPlugin {
|
func NewLabelTransformerPlugin() resmap.TransformerPlugin {
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user