mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-29 01:30:51 +00:00
Compare commits
1593 Commits
api/v0.6.5
...
release-ap
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c8db65a90 | ||
|
|
35b5b7554f | ||
|
|
6395344bcb | ||
|
|
4a3bb926c5 | ||
|
|
ba5335cf48 | ||
|
|
03ac2e1ada | ||
|
|
1cf5b00af8 | ||
|
|
9097f7b5a6 | ||
|
|
0c8174544f | ||
|
|
a37572d193 | ||
|
|
ba7315ca76 | ||
|
|
ddf768195c | ||
|
|
b11cc3ae67 | ||
|
|
22668eae16 | ||
|
|
02eb788b78 | ||
|
|
41fb6915b3 | ||
|
|
596519d3f2 | ||
|
|
d3d92157fa | ||
|
|
c83ebd9530 | ||
|
|
c3b5d8fa19 | ||
|
|
7a773a3a48 | ||
|
|
46d4934b68 | ||
|
|
d0ef2f70a1 | ||
|
|
b8d2ff2afa | ||
|
|
7e0158e1e9 | ||
|
|
5948f6aa63 | ||
|
|
496a962a53 | ||
|
|
17b42a99f5 | ||
|
|
271f393218 | ||
|
|
c0dc68d6e7 | ||
|
|
188e35fbfd | ||
|
|
233b6adf7e | ||
|
|
41296b9814 | ||
|
|
66e4e1582b | ||
|
|
2e230b4d4b | ||
|
|
bcae65770a | ||
|
|
d49e5aa5aa | ||
|
|
94af647556 | ||
|
|
9d5491c2e2 | ||
|
|
cf89eae804 | ||
|
|
9452a031ba | ||
|
|
a464ed0c59 | ||
|
|
ec212711d4 | ||
|
|
2f2e14e953 | ||
|
|
ed72bb02d4 | ||
|
|
02e0b38bb0 | ||
|
|
01ab069bd2 | ||
|
|
cdc4a5083b | ||
|
|
5e84de2a89 | ||
|
|
e5041bae6f | ||
|
|
0d600af35a | ||
|
|
0989b26098 | ||
|
|
7888aef305 | ||
|
|
fe604fd3d1 | ||
|
|
4fc02497ae | ||
|
|
e96c38e3ab | ||
|
|
29104d6fa9 | ||
|
|
df57e196d8 | ||
|
|
f3c825f550 | ||
|
|
03c94eabb7 | ||
|
|
c071cdaedd | ||
|
|
f68a0c50d9 | ||
|
|
db34e923c5 | ||
|
|
df80b29694 | ||
|
|
ea193328e3 | ||
|
|
2d4bce5112 | ||
|
|
af06ae6b69 | ||
|
|
46875b6ac4 | ||
|
|
0fa010c7e7 | ||
|
|
3e652d6fea | ||
|
|
f54014ce3b | ||
|
|
08924dc2f5 | ||
|
|
49c0ed1326 | ||
|
|
7440f974b8 | ||
|
|
ab9b89213b | ||
|
|
9fc012cc4e | ||
|
|
877d72b10b | ||
|
|
c743f13d0d | ||
|
|
0d32543ebd | ||
|
|
28ee975948 | ||
|
|
2a9adbeb1e | ||
|
|
672c751715 | ||
|
|
d3a7b9008b | ||
|
|
b0d2e4bdcd | ||
|
|
71bf0d5d14 | ||
|
|
0a9c5cb0cf | ||
|
|
ff4136b1a2 | ||
|
|
1567b96ed3 | ||
|
|
14947e449b | ||
|
|
b368b347d1 | ||
|
|
5c359bda28 | ||
|
|
c94b5d8f25 | ||
|
|
b699204a9e | ||
|
|
cf3a452ddd | ||
|
|
faad014f96 | ||
|
|
8e46ef57be | ||
|
|
458d48430a | ||
|
|
3cecdc6214 | ||
|
|
7cac778866 | ||
|
|
0aa379c228 | ||
|
|
cd0f9b0c5b | ||
|
|
651f0c1097 | ||
|
|
6348ea9515 | ||
|
|
4b64f1e0e8 | ||
|
|
7306402cca | ||
|
|
485f7d44d9 | ||
|
|
9e57ab72fc | ||
|
|
d17ef91d94 | ||
|
|
e97cecf9dc | ||
|
|
5833f4ca80 | ||
|
|
e86c479690 | ||
|
|
d082c75b65 | ||
|
|
d2e59002ae | ||
|
|
c857ff8371 | ||
|
|
866e84059f | ||
|
|
497d2ee031 | ||
|
|
c4febc59d5 | ||
|
|
19955e73ff | ||
|
|
271bf31ce9 | ||
|
|
809182c6b6 | ||
|
|
0dd3d84104 | ||
|
|
de6b978491 | ||
|
|
8fa8e14eeb | ||
|
|
26b5e628cf | ||
|
|
80853c61b8 | ||
|
|
c5ff592810 | ||
|
|
b9df5686f4 | ||
|
|
7ff87ef5b9 | ||
|
|
c4d38108cf | ||
|
|
784ae5efa3 | ||
|
|
e8640724a9 | ||
|
|
7b0ec99d90 | ||
|
|
fbfcb0479a | ||
|
|
3421fcf81e | ||
|
|
97de780feb | ||
|
|
3490fb8716 | ||
|
|
2fe04496c2 | ||
|
|
b0d7721049 | ||
|
|
18f22f10a3 | ||
|
|
3ae5aa9e13 | ||
|
|
48f21e920a | ||
|
|
dcdefca70e | ||
|
|
c64351aa01 | ||
|
|
c60979ee3e | ||
|
|
cb80659c22 | ||
|
|
b1086ac49b | ||
|
|
1fcd66258f | ||
|
|
d1d578c392 | ||
|
|
f67dd5bbbd | ||
|
|
ac5c51ba2c | ||
|
|
53cc76fe43 | ||
|
|
07eb595eb2 | ||
|
|
8cb7acfdcb | ||
|
|
975482390f | ||
|
|
701695c343 | ||
|
|
8db1267983 | ||
|
|
da7ec577b2 | ||
|
|
d17b171207 | ||
|
|
70ce89d993 | ||
|
|
e25db3df2e | ||
|
|
3eae520532 | ||
|
|
3ec7b10bc0 | ||
|
|
6dabba1d23 | ||
|
|
bd05631887 | ||
|
|
6950a0d246 | ||
|
|
c90504a19d | ||
|
|
8dab94964f | ||
|
|
ff40460d3b | ||
|
|
a7f4db7fb4 | ||
|
|
5d0762411c | ||
|
|
b4e116346e | ||
|
|
9091919699 | ||
|
|
702a56e2f1 | ||
|
|
00a9c59dd7 | ||
|
|
4dfc2a9507 | ||
|
|
2f17803c0a | ||
|
|
5ed96a34d7 | ||
|
|
14cb815b5d | ||
|
|
a5df6f7fd9 | ||
|
|
4f5dfb5d42 | ||
|
|
6374d3d593 | ||
|
|
22f9daa3ab | ||
|
|
c3c7013f09 | ||
|
|
3277ff9dbf | ||
|
|
746bd18a8c | ||
|
|
d7763045ea | ||
|
|
30612cfd17 | ||
|
|
3006846d67 | ||
|
|
2f9617ff56 | ||
|
|
28b3e9fb9c | ||
|
|
6a7caf4192 | ||
|
|
32e13bdf66 | ||
|
|
fcfd30ca18 | ||
|
|
a86723c3a3 | ||
|
|
01d7fae382 | ||
|
|
39349c4fbc | ||
|
|
836a721a47 | ||
|
|
78e8d4318a | ||
|
|
67591762a6 | ||
|
|
f38648df34 | ||
|
|
d46bb0a453 | ||
|
|
939545a007 | ||
|
|
fb768687ea | ||
|
|
7bc51153d0 | ||
|
|
2b3d9dd67b | ||
|
|
98fce99f96 | ||
|
|
3c1e695f3f | ||
|
|
91ff4eefb2 | ||
|
|
f0c0d931fe | ||
|
|
f5b2b751eb | ||
|
|
cfd9278fd8 | ||
|
|
b79d77a8a7 | ||
|
|
91f65b3441 | ||
|
|
ba55d95542 | ||
|
|
851b3fc28c | ||
|
|
90493ec374 | ||
|
|
51b767b06e | ||
|
|
e3160373f0 | ||
|
|
a5b61016bb | ||
|
|
d62cc6d6a2 | ||
|
|
bcebad1664 | ||
|
|
9abf5fca3c | ||
|
|
26b9af0379 | ||
|
|
c65ef489ca | ||
|
|
c754eadabc | ||
|
|
816e2365bf | ||
|
|
302cc866ad | ||
|
|
bf97d23a00 | ||
|
|
3687250ca2 | ||
|
|
69e5228264 | ||
|
|
7af5ce56bd | ||
|
|
d711b275f0 | ||
|
|
9577d61167 | ||
|
|
2c23b960ff | ||
|
|
6e5d2674ce | ||
|
|
2554d690c8 | ||
|
|
cec9298b2d | ||
|
|
54ae9ba9fc | ||
|
|
b73dfe7f35 | ||
|
|
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 | ||
|
|
afff3ce5ab | ||
|
|
71b763888c | ||
|
|
28cdcc2e46 |
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -8,7 +8,7 @@ assignees: ""
|
|||||||
---
|
---
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Please read this page: https://kubernetes-sigs.github.io/kustomize/contributing/bugs/ before
|
Please read this page: https://kubectl.docs.kubernetes.io/contributing/kustomize/bugs/ before
|
||||||
filing a bug
|
filing a bug
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
|||||||
6
.github/dependabot.yml
vendored
Normal file
6
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
54
.github/workflows/go.yml
vendored
54
.github/workflows/go.yml
vendored
@@ -6,6 +6,9 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches: [ master ]
|
branches: [ master ]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
@@ -14,18 +17,21 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Set up Go 1.x
|
- name: Set up Go 1.x
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v3
|
||||||
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@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Lint
|
- name: Lint
|
||||||
run: ./scripts/kyaml-pre-commit.sh
|
run: make lint
|
||||||
env:
|
|
||||||
KUSTOMIZE_DOCKER_E2E: false # don't need to do e2e tests for linting
|
- name: Verify boilerplate
|
||||||
|
run: make check-license
|
||||||
|
|
||||||
test-linux:
|
test-linux:
|
||||||
name: Test Linux
|
name: Test Linux
|
||||||
@@ -33,21 +39,16 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Set up Go 1.x
|
- name: Set up Go 1.x
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v3
|
||||||
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: Test kyaml
|
- name: Test all modules
|
||||||
run: go test -cover ./...
|
run: make test-unit-non-plugin
|
||||||
working-directory: ./kyaml
|
|
||||||
|
|
||||||
- name: Test cmd/config
|
|
||||||
run: go test -cover ./...
|
|
||||||
working-directory: ./cmd/config
|
|
||||||
env:
|
env:
|
||||||
KUSTOMIZE_DOCKER_E2E: true
|
KUSTOMIZE_DOCKER_E2E: true
|
||||||
|
|
||||||
@@ -57,21 +58,16 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Set up Go 1.x
|
- name: Set up Go 1.x
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v3
|
||||||
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: Test kyaml
|
- name: Test all modules
|
||||||
run: go test -cover ./...
|
run: make test-unit-non-plugin
|
||||||
working-directory: ./kyaml
|
|
||||||
|
|
||||||
- name: Test cmd/config
|
|
||||||
run: go test -cover ./...
|
|
||||||
working-directory: ./cmd/config
|
|
||||||
env:
|
env:
|
||||||
KUSTOMIZE_DOCKER_E2E: false # docker not installed on mac
|
KUSTOMIZE_DOCKER_E2E: false # docker not installed on mac
|
||||||
|
|
||||||
@@ -81,9 +77,9 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Set up Go 1.x
|
- name: Set up Go 1.x
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v3
|
||||||
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
|
||||||
@@ -98,3 +94,9 @@ jobs:
|
|||||||
working-directory: ./cmd/config
|
working-directory: ./cmd/config
|
||||||
env:
|
env:
|
||||||
KUSTOMIZE_DOCKER_E2E: false # docker on windows not working well yet
|
KUSTOMIZE_DOCKER_E2E: false # docker on windows not working well yet
|
||||||
|
|
||||||
|
# TODO (#4001): replace specific modules above with this once Windows tests are passing.
|
||||||
|
#- name: Test all modules
|
||||||
|
# run: make test-unit-non-plugin
|
||||||
|
# env:
|
||||||
|
# KUSTOMIZE_DOCKER_E2E: false # docker on windows not working well yet
|
||||||
|
|||||||
10
.gitignore
vendored
10
.gitignore
vendored
@@ -21,3 +21,13 @@
|
|||||||
*.DS_store
|
*.DS_store
|
||||||
|
|
||||||
.bin
|
.bin
|
||||||
|
|
||||||
|
# Hugo site
|
||||||
|
publishedSite/
|
||||||
|
site/public/
|
||||||
|
site/resources/
|
||||||
|
site/.hugo_build.lock
|
||||||
|
**/node_modules/
|
||||||
|
|
||||||
|
# goreleaser artifacts
|
||||||
|
**/dist/
|
||||||
|
|||||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "site/themes/docsy"]
|
||||||
|
path = site/themes/docsy
|
||||||
|
url = https://github.com/google/docsy.git
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
run:
|
|
||||||
deadline: 5m
|
|
||||||
|
|
||||||
linters:
|
|
||||||
disable-all: true
|
|
||||||
enable:
|
|
||||||
- bodyclose
|
|
||||||
- deadcode
|
|
||||||
- depguard
|
|
||||||
# - dogsled
|
|
||||||
- dupl
|
|
||||||
# - errcheck
|
|
||||||
# - funlen
|
|
||||||
# - gochecknoinits
|
|
||||||
- goconst
|
|
||||||
# - gocritic
|
|
||||||
- gocyclo
|
|
||||||
- gofmt
|
|
||||||
- goimports
|
|
||||||
- golint
|
|
||||||
- gosec
|
|
||||||
- gosimple
|
|
||||||
- govet
|
|
||||||
- ineffassign
|
|
||||||
- interfacer
|
|
||||||
- lll
|
|
||||||
- misspell
|
|
||||||
- nakedret
|
|
||||||
# - scopelint
|
|
||||||
- staticcheck
|
|
||||||
- structcheck
|
|
||||||
# stylecheck demands that acronyms not be treated as words
|
|
||||||
# in camelCase, so JsonOp become JSONOp, etc. Yuck.
|
|
||||||
# - stylecheck
|
|
||||||
- typecheck
|
|
||||||
- unconvert
|
|
||||||
- unparam
|
|
||||||
- unused
|
|
||||||
- varcheck
|
|
||||||
# - whitespace
|
|
||||||
|
|
||||||
linters-settings:
|
|
||||||
dupl:
|
|
||||||
threshold: 400
|
|
||||||
lll:
|
|
||||||
line-length: 170
|
|
||||||
gocyclo:
|
|
||||||
min-complexity: 15
|
|
||||||
golint:
|
|
||||||
min-confidence: 0.85
|
|
||||||
136
.golangci.yml
Normal file
136
.golangci.yml
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
# Copyright 2019 The Kubernetes Authors.
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
run:
|
||||||
|
deadline: 5m
|
||||||
|
|
||||||
|
linters:
|
||||||
|
# please, do not use `enable-all`: it's deprecated and will be removed soon.
|
||||||
|
# inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint
|
||||||
|
disable-all: true
|
||||||
|
enable:
|
||||||
|
- asciicheck
|
||||||
|
- bidichk
|
||||||
|
- bodyclose
|
||||||
|
- contextcheck
|
||||||
|
# - cyclop
|
||||||
|
- deadcode
|
||||||
|
- depguard
|
||||||
|
- dogsled
|
||||||
|
- dupl
|
||||||
|
- durationcheck
|
||||||
|
- errcheck
|
||||||
|
- errname
|
||||||
|
- errorlint
|
||||||
|
- exhaustive
|
||||||
|
# - exhaustivestruct
|
||||||
|
- exportloopref
|
||||||
|
# - forbidigo
|
||||||
|
- forcetypeassert
|
||||||
|
# - funlen
|
||||||
|
# - gci
|
||||||
|
- gochecknoglobals
|
||||||
|
- gochecknoinits
|
||||||
|
# - gocognit
|
||||||
|
- goconst
|
||||||
|
- gocritic
|
||||||
|
- gocyclo
|
||||||
|
# - godot
|
||||||
|
# - godox
|
||||||
|
- goerr113
|
||||||
|
- gofmt
|
||||||
|
# - gofumpt
|
||||||
|
- goheader
|
||||||
|
- goimports
|
||||||
|
- gomnd
|
||||||
|
- gomoddirectives
|
||||||
|
- gomodguard
|
||||||
|
- goprintffuncname
|
||||||
|
- gosec
|
||||||
|
- gosimple
|
||||||
|
- govet
|
||||||
|
# - ifshort # too many false positives
|
||||||
|
- importas
|
||||||
|
- ineffassign
|
||||||
|
# - ireturn
|
||||||
|
- lll
|
||||||
|
- makezero
|
||||||
|
- misspell
|
||||||
|
- nakedret
|
||||||
|
- nestif
|
||||||
|
- nilerr
|
||||||
|
# - nilnil
|
||||||
|
# - nlreturn
|
||||||
|
# - noctx
|
||||||
|
- nolintlint
|
||||||
|
# - paralleltest
|
||||||
|
- prealloc
|
||||||
|
- predeclared
|
||||||
|
- promlinter
|
||||||
|
- revive
|
||||||
|
- rowserrcheck
|
||||||
|
- sqlclosecheck
|
||||||
|
- staticcheck
|
||||||
|
- structcheck
|
||||||
|
# - stylecheck
|
||||||
|
- tagliatelle
|
||||||
|
- tenv
|
||||||
|
- testpackage
|
||||||
|
- thelper
|
||||||
|
- tparallel
|
||||||
|
- typecheck
|
||||||
|
- unconvert
|
||||||
|
- unparam
|
||||||
|
- unused
|
||||||
|
- varcheck
|
||||||
|
# - varnamelen
|
||||||
|
- wastedassign
|
||||||
|
- whitespace
|
||||||
|
- wrapcheck
|
||||||
|
# - wsl
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
dupl:
|
||||||
|
threshold: 400
|
||||||
|
lll:
|
||||||
|
line-length: 170
|
||||||
|
gocyclo:
|
||||||
|
min-complexity: 30
|
||||||
|
revive:
|
||||||
|
rules:
|
||||||
|
- name: var-naming
|
||||||
|
arguments:
|
||||||
|
- [ "ID", "API", "JSON" ] # AllowList
|
||||||
|
- [ ] # DenyList
|
||||||
|
gomoddirectives:
|
||||||
|
replace-local: true
|
||||||
|
gosec:
|
||||||
|
config:
|
||||||
|
G306: "0644"
|
||||||
|
gomnd:
|
||||||
|
ignored-functions:
|
||||||
|
- ioutil.WriteFile
|
||||||
|
wrapcheck:
|
||||||
|
ignoreSigs:
|
||||||
|
# defaults
|
||||||
|
- .Errorf(
|
||||||
|
- errors.New(
|
||||||
|
- errors.Unwrap(
|
||||||
|
- .Wrap(
|
||||||
|
- .Wrapf(
|
||||||
|
- .WithMessage(
|
||||||
|
- .WithMessagef(
|
||||||
|
- .WithStack(
|
||||||
|
# from kyaml's errors package
|
||||||
|
- .WrapPrefixf(
|
||||||
|
|
||||||
|
issues:
|
||||||
|
new-from-rev: c94b5d8f2 # enables us to enforce a larger set of linters for new code than pass on existing code
|
||||||
|
max-same-issues: 0
|
||||||
|
exclude-rules:
|
||||||
|
- linters:
|
||||||
|
- revive
|
||||||
|
text: "don't use leading"
|
||||||
|
- linters:
|
||||||
|
- staticcheck
|
||||||
|
text: "SA1019: kioutil.Legacy"
|
||||||
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]
|
||||||
|
|||||||
397
Makefile
397
Makefile
@@ -3,10 +3,16 @@
|
|||||||
#
|
#
|
||||||
# Makefile for kustomize CLI and API.
|
# Makefile for kustomize CLI and API.
|
||||||
|
|
||||||
MYGOBIN := $(shell go env GOPATH)/bin
|
LATEST_V4_RELEASE=v4.5.4
|
||||||
SHELL := /bin/bash
|
|
||||||
|
SHELL := /usr/bin/env bash
|
||||||
|
GOOS = $(shell go env GOOS)
|
||||||
|
GOARCH = $(shell go env GOARCH)
|
||||||
|
MYGOBIN = $(shell go env GOBIN)
|
||||||
|
ifeq ($(MYGOBIN),)
|
||||||
|
MYGOBIN = $(shell go env GOPATH)/bin
|
||||||
|
endif
|
||||||
export PATH := $(MYGOBIN):$(PATH)
|
export PATH := $(MYGOBIN):$(PATH)
|
||||||
MODULES := '"cmd/config" "api/" "kustomize/" "kyaml/"'
|
|
||||||
|
|
||||||
# Provide defaults for REPO_OWNER and REPO_NAME if not present.
|
# Provide defaults for REPO_OWNER and REPO_NAME if not present.
|
||||||
# Typically these values would be provided by Prow.
|
# Typically these values would be provided by Prow.
|
||||||
@@ -18,245 +24,138 @@ ifndef REPO_NAME
|
|||||||
REPO_NAME := "kustomize"
|
REPO_NAME := "kustomize"
|
||||||
endif
|
endif
|
||||||
|
|
||||||
.PHONY: all
|
|
||||||
all: verify-kustomize
|
|
||||||
|
|
||||||
.PHONY: verify-kustomize
|
# --- Plugins ---
|
||||||
verify-kustomize: \
|
include Makefile-plugins.mk
|
||||||
lint-kustomize \
|
|
||||||
test-unit-kustomize-all \
|
|
||||||
test-examples-kustomize-against-HEAD \
|
|
||||||
test-examples-kustomize-against-3.8.6
|
|
||||||
|
|
||||||
# The following target referenced by a file in
|
|
||||||
# https://github.com/kubernetes/test-infra/tree/master/config/jobs/kubernetes-sigs/kustomize
|
|
||||||
.PHONY: prow-presubmit-check
|
|
||||||
prow-presubmit-check: \
|
|
||||||
lint-kustomize \
|
|
||||||
test-unit-kustomize-all \
|
|
||||||
test-unit-cmd-all \
|
|
||||||
test-go-mod \
|
|
||||||
test-examples-kustomize-against-HEAD \
|
|
||||||
test-examples-kustomize-against-3.8.6
|
|
||||||
|
|
||||||
# test-multi-module \
|
# --- Tool management ---
|
||||||
# Temporarily removed from prow-presubmit-check
|
include Makefile-tools.mk
|
||||||
# See https://github.com/kubernetes-sigs/kustomize/issues/3191
|
|
||||||
|
|
||||||
.PHONY: verify-kustomize-e2e
|
.PHONY: install-tools
|
||||||
verify-kustomize-e2e: test-examples-e2e-kustomize
|
install-tools: \
|
||||||
|
install-local-tools \
|
||||||
|
install-out-of-tree-tools
|
||||||
|
|
||||||
# Other builds in this repo might want a different linter version.
|
.PHONY: uninstall-tools
|
||||||
# Without one Makefile to rule them all, the different makes
|
uninstall-tools: \
|
||||||
# cannot assume that golanci-lint is at the version they want
|
uninstall-local-tools \
|
||||||
# since everything uses the same implicit GOPATH.
|
uninstall-out-of-tree-tools
|
||||||
# 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:
|
|
||||||
( \
|
|
||||||
set -e; \
|
|
||||||
cd hack; \
|
|
||||||
GO111MODULE=on go build -tags=tools -o $(MYGOBIN)/golangci-lint-kustomize github.com/golangci/golangci-lint/cmd/golangci-lint; \
|
|
||||||
)
|
|
||||||
|
|
||||||
# Install from version specified in api/go.mod.
|
.PHONY: install-local-tools
|
||||||
$(MYGOBIN)/mdrip:
|
install-local-tools: \
|
||||||
cd api; \
|
$(MYGOBIN)/gorepomod \
|
||||||
go install github.com/monopole/mdrip
|
$(MYGOBIN)/k8scopy \
|
||||||
|
$(MYGOBIN)/pluginator
|
||||||
|
|
||||||
# Install from version specified in api/go.mod.
|
.PHONY: uninstall-local-tools
|
||||||
$(MYGOBIN)/stringer:
|
uninstall-local-tools:
|
||||||
cd api; \
|
rm -f $(MYGOBIN)/gorepomod
|
||||||
go install golang.org/x/tools/cmd/stringer
|
rm -f $(MYGOBIN)/k8scopy
|
||||||
|
rm -f $(MYGOBIN)/pluginator
|
||||||
# Install from version specified in api/go.mod.
|
|
||||||
$(MYGOBIN)/goimports:
|
|
||||||
cd api; \
|
|
||||||
go install golang.org/x/tools/cmd/goimports
|
|
||||||
|
|
||||||
# Build from local source.
|
# Build from local source.
|
||||||
$(MYGOBIN)/gorepomod:
|
$(MYGOBIN)/gorepomod:
|
||||||
cd cmd/gorepomod; \
|
cd cmd/gorepomod; \
|
||||||
go install .
|
go install .
|
||||||
|
|
||||||
|
# Build from local source.
|
||||||
|
$(MYGOBIN)/k8scopy:
|
||||||
|
cd cmd/k8scopy; \
|
||||||
|
go install .
|
||||||
|
|
||||||
# Build from local source.
|
# Build from local source.
|
||||||
$(MYGOBIN)/pluginator:
|
$(MYGOBIN)/pluginator:
|
||||||
cd cmd/pluginator; \
|
cd cmd/pluginator; \
|
||||||
go install .
|
go install .
|
||||||
|
|
||||||
# Build from local source.
|
|
||||||
$(MYGOBIN)/prchecker:
|
# --- Build targets ---
|
||||||
cd cmd/prchecker; \
|
|
||||||
go install .
|
|
||||||
|
|
||||||
# Build from local source.
|
# Build from local source.
|
||||||
$(MYGOBIN)/kustomize:
|
$(MYGOBIN)/kustomize: build-kustomize-api
|
||||||
cd kustomize; \
|
cd kustomize; \
|
||||||
go install .
|
go install .
|
||||||
|
|
||||||
.PHONY: install-tools
|
kustomize: $(MYGOBIN)/kustomize
|
||||||
install-tools: \
|
|
||||||
$(MYGOBIN)/goimports \
|
|
||||||
$(MYGOBIN)/golangci-lint-kustomize \
|
|
||||||
$(MYGOBIN)/gh \
|
|
||||||
$(MYGOBIN)/gorepomod \
|
|
||||||
$(MYGOBIN)/mdrip \
|
|
||||||
$(MYGOBIN)/pluginator \
|
|
||||||
$(MYGOBIN)/prchecker \
|
|
||||||
$(MYGOBIN)/stringer \
|
|
||||||
$(MYGOBIN)/helm
|
|
||||||
|
|
||||||
### Begin kustomize plugin rules.
|
|
||||||
#
|
|
||||||
# The rules to deal with builtin plugins are a bit
|
|
||||||
# complicated because
|
|
||||||
#
|
|
||||||
# - Every builtin plugin is a Go plugin -
|
|
||||||
# meaning it gets its own module directory
|
|
||||||
# (outside of the api module) with Go
|
|
||||||
# code in a 'main' package per Go plugin rules.
|
|
||||||
# - kustomize locates plugins using the
|
|
||||||
# 'apiVersion' and 'kind' fields from the
|
|
||||||
# plugin config file.
|
|
||||||
# - k8s wants CamelCase in 'kind' fields.
|
|
||||||
# - The module name (the last name in the path)
|
|
||||||
# must be the lowercased 'kind' of the
|
|
||||||
# plugin because Go and related tools
|
|
||||||
# demand lowercase in import paths, but
|
|
||||||
# allow CamelCase in file names.
|
|
||||||
# - the generated code must live in the api
|
|
||||||
# module (it's linked into the api).
|
|
||||||
|
|
||||||
# Where all generated builtin plugin code should go.
|
|
||||||
pGen=api/builtins
|
|
||||||
# Where the builtin Go plugin modules live.
|
|
||||||
pSrc=plugin/builtin
|
|
||||||
|
|
||||||
_builtinplugins = \
|
|
||||||
AnnotationsTransformer.go \
|
|
||||||
ConfigMapGenerator.go \
|
|
||||||
HashTransformer.go \
|
|
||||||
ImageTagTransformer.go \
|
|
||||||
LabelTransformer.go \
|
|
||||||
LegacyOrderTransformer.go \
|
|
||||||
NamespaceTransformer.go \
|
|
||||||
PatchJson6902Transformer.go \
|
|
||||||
PatchStrategicMergeTransformer.go \
|
|
||||||
PatchTransformer.go \
|
|
||||||
PrefixSuffixTransformer.go \
|
|
||||||
ReplicaCountTransformer.go \
|
|
||||||
SecretGenerator.go \
|
|
||||||
ValueAddTransformer.go \
|
|
||||||
HelmChartInflationGenerator.go
|
|
||||||
|
|
||||||
# Maintaining this explicit list of generated files, and
|
|
||||||
# adding it as a dependency to a few targets, to assure
|
|
||||||
# they get recreated if deleted. The rules below on how
|
|
||||||
# to make them don't, by themselves, assure they will be
|
|
||||||
# recreated if deleted.
|
|
||||||
builtinplugins = $(patsubst %,$(pGen)/%,$(_builtinplugins))
|
|
||||||
|
|
||||||
# These rules are verbose, but assure that if a source file
|
|
||||||
# is modified, the corresponding generated file, and only
|
|
||||||
# that file, will be recreated.
|
|
||||||
$(pGen)/AnnotationsTransformer.go: $(pSrc)/annotationstransformer/AnnotationsTransformer.go
|
|
||||||
$(pGen)/ConfigMapGenerator.go: $(pSrc)/configmapgenerator/ConfigMapGenerator.go
|
|
||||||
$(pGen)/HashTransformer.go: $(pSrc)/hashtransformer/HashTransformer.go
|
|
||||||
$(pGen)/ImageTagTransformer.go: $(pSrc)/imagetagtransformer/ImageTagTransformer.go
|
|
||||||
$(pGen)/LabelTransformer.go: $(pSrc)/labeltransformer/LabelTransformer.go
|
|
||||||
$(pGen)/LegacyOrderTransformer.go: $(pSrc)/legacyordertransformer/LegacyOrderTransformer.go
|
|
||||||
$(pGen)/NamespaceTransformer.go: $(pSrc)/namespacetransformer/NamespaceTransformer.go
|
|
||||||
$(pGen)/PatchJson6902Transformer.go: $(pSrc)/patchjson6902transformer/PatchJson6902Transformer.go
|
|
||||||
$(pGen)/PatchStrategicMergeTransformer.go: $(pSrc)/patchstrategicmergetransformer/PatchStrategicMergeTransformer.go
|
|
||||||
$(pGen)/PatchTransformer.go: $(pSrc)/patchtransformer/PatchTransformer.go
|
|
||||||
$(pGen)/PrefixSuffixTransformer.go: $(pSrc)/prefixsuffixtransformer/PrefixSuffixTransformer.go
|
|
||||||
$(pGen)/ReplicaCountTransformer.go: $(pSrc)/replicacounttransformer/ReplicaCountTransformer.go
|
|
||||||
$(pGen)/SecretGenerator.go: $(pSrc)/secretgenerator/SecretGenerator.go
|
|
||||||
$(pGen)/ValueAddTransformer.go: $(pSrc)/valueaddtransformer/ValueAddTransformer.go
|
|
||||||
$(pGen)/HelmChartInflationGenerator.go: $(pSrc)/helmchartinflationgenerator/HelmChartInflationGenerator.go
|
|
||||||
|
|
||||||
# The (verbose but portable) Makefile way to convert to lowercase.
|
|
||||||
toLowerCase = $(subst A,a,$(subst B,b,$(subst C,c,$(subst D,d,$(subst E,e,$(subst F,f,$(subst G,g,$(subst H,h,$(subst I,i,$(subst J,j,$(subst K,k,$(subst L,l,$(subst M,m,$(subst N,n,$(subst O,o,$(subst P,p,$(subst Q,q,$(subst R,r,$(subst S,s,$(subst T,t,$(subst U,u,$(subst V,v,$(subst W,w,$(subst X,x,$(subst Y,y,$(subst Z,z,$1))))))))))))))))))))))))))
|
|
||||||
|
|
||||||
$(pGen)/%.go: $(MYGOBIN)/pluginator
|
|
||||||
@echo "generating $*"
|
|
||||||
( \
|
|
||||||
set -e; \
|
|
||||||
cd $(pSrc)/$(call toLowerCase,$*); \
|
|
||||||
go generate .; \
|
|
||||||
cd ../../../$(pGen); \
|
|
||||||
$(MYGOBIN)/goimports -w $*.go \
|
|
||||||
)
|
|
||||||
|
|
||||||
# Target is for debugging.
|
|
||||||
.PHONY: generate-kustomize-builtin-plugins
|
|
||||||
generate-kustomize-builtin-plugins: $(builtinplugins)
|
|
||||||
|
|
||||||
.PHONY: kustomize-external-go-plugin-build
|
|
||||||
kustomize-external-go-plugin-build:
|
|
||||||
./hack/buildExternalGoPlugins.sh ./plugin
|
|
||||||
|
|
||||||
.PHONY: kustomize-external-go-plugin-clean
|
|
||||||
kustomize-external-go-plugin-clean:
|
|
||||||
./hack/buildExternalGoPlugins.sh ./plugin clean
|
|
||||||
|
|
||||||
### End kustomize plugin rules.
|
|
||||||
|
|
||||||
.PHONY: lint-kustomize
|
|
||||||
lint-kustomize: install-tools $(builtinplugins)
|
|
||||||
cd api; \
|
|
||||||
$(MYGOBIN)/golangci-lint-kustomize -c ../.golangci-kustomize.yml run ./...
|
|
||||||
cd kustomize; \
|
|
||||||
$(MYGOBIN)/golangci-lint-kustomize -c ../.golangci-kustomize.yml run ./...
|
|
||||||
cd 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.
|
||||||
.PHONY: build-kustomize-api
|
.PHONY: build-kustomize-api
|
||||||
build-kustomize-api: $(builtinplugins)
|
build-kustomize-api: $(MYGOBIN)/goimports $(builtinplugins)
|
||||||
cd api; go build ./...
|
cd api; $(MAKE) build
|
||||||
|
|
||||||
.PHONY: test-unit-kustomize-api
|
.PHONY: generate-kustomize-api
|
||||||
test-unit-kustomize-api: build-kustomize-api
|
generate-kustomize-api:
|
||||||
cd api; go test ./... -ldflags "-X sigs.k8s.io/kustomize/api/provenance.version=v444.333.222"
|
cd api; $(MAKE) generate
|
||||||
|
|
||||||
|
|
||||||
|
# --- Verification targets ---
|
||||||
|
.PHONY: verify-kustomize-repo
|
||||||
|
verify-kustomize-repo: \
|
||||||
|
install-tools \
|
||||||
|
lint \
|
||||||
|
check-license \
|
||||||
|
test-unit-all \
|
||||||
|
build-non-plugin-all \
|
||||||
|
test-go-mod \
|
||||||
|
test-examples-kustomize-against-HEAD \
|
||||||
|
test-examples-kustomize-against-v4-release
|
||||||
|
|
||||||
|
# The following target referenced by a file in
|
||||||
|
# https://github.com/kubernetes/test-infra/tree/master/config/jobs/kubernetes-sigs/kustomize
|
||||||
|
.PHONY: prow-presubmit-check
|
||||||
|
prow-presubmit-check: \
|
||||||
|
install-tools \
|
||||||
|
test-unit-kustomize-plugins \
|
||||||
|
test-go-mod \
|
||||||
|
build-non-plugin-all \
|
||||||
|
test-examples-kustomize-against-HEAD \
|
||||||
|
test-examples-kustomize-against-v4-release
|
||||||
|
|
||||||
|
.PHONY: license
|
||||||
|
license: $(MYGOBIN)/addlicense
|
||||||
|
./hack/add-license.sh run
|
||||||
|
|
||||||
|
.PHONY: check-license
|
||||||
|
check-license: $(MYGOBIN)/addlicense
|
||||||
|
./hack/add-license.sh check
|
||||||
|
|
||||||
|
.PHONY: lint
|
||||||
|
lint: $(MYGOBIN)/golangci-lint $(MYGOBIN)/goimports $(builtinplugins)
|
||||||
|
./hack/for-each-module.sh "make lint"
|
||||||
|
|
||||||
|
.PHONY: test-unit-all
|
||||||
|
test-unit-all: \
|
||||||
|
test-unit-non-plugin \
|
||||||
|
test-unit-kustomize-plugins
|
||||||
|
|
||||||
|
# This target is used by our Github Actions CI to run unit tests for all non-plugin modules in multiple GOOS environments.
|
||||||
|
.PHONY: test-unit-non-plugin
|
||||||
|
test-unit-non-plugin:
|
||||||
|
./hack/for-each-module.sh "make test" "./plugin/*" 15
|
||||||
|
|
||||||
|
.PHONY: build-non-plugin-all
|
||||||
|
build-non-plugin-all:
|
||||||
|
./hack/for-each-module.sh "make build" "./plugin/*" 15
|
||||||
|
|
||||||
.PHONY: test-unit-kustomize-plugins
|
.PHONY: test-unit-kustomize-plugins
|
||||||
test-unit-kustomize-plugins:
|
test-unit-kustomize-plugins:
|
||||||
./hack/testUnitKustomizePlugins.sh
|
./hack/testUnitKustomizePlugins.sh
|
||||||
|
|
||||||
.PHONY: test-unit-kustomize-cli
|
.PHONY: functions-examples-all
|
||||||
test-unit-kustomize-cli:
|
functions-examples-all:
|
||||||
cd kustomize; go test ./...
|
for dir in $(abspath $(wildcard functions/examples/*/.)); do \
|
||||||
|
echo -e "\n---Running make tasks for function $$dir---"; \
|
||||||
.PHONY: test-unit-kustomize-all
|
set -e; \
|
||||||
test-unit-kustomize-all: \
|
cd $$dir; $(MAKE) all; \
|
||||||
test-unit-kustomize-api \
|
done
|
||||||
test-unit-kustomize-cli \
|
|
||||||
test-unit-kustomize-plugins
|
|
||||||
|
|
||||||
test-unit-cmd-all:
|
|
||||||
./scripts/kyaml-pre-commit.sh
|
|
||||||
|
|
||||||
test-go-mod:
|
test-go-mod:
|
||||||
./scripts/check-go-mod.sh
|
./hack/for-each-module.sh "go list -m -json all > /dev/null && go mod tidy -v"
|
||||||
|
|
||||||
# Environment variables are defined at
|
|
||||||
# https://github.com/kubernetes/test-infra/blob/master/prow/jobs.md#job-environment-variables
|
|
||||||
.PHONY: test-multi-module
|
|
||||||
test-multi-module: $(MYGOBIN)/prchecker
|
|
||||||
( \
|
|
||||||
export MYGOBIN=$(MYGOBIN); \
|
|
||||||
export REPO_OWNER=$(REPO_OWNER); \
|
|
||||||
export REPO_NAME=$(REPO_NAME); \
|
|
||||||
export PULL_NUMBER=$(PULL_NUMBER); \
|
|
||||||
export MODULES=$(MODULES); \
|
|
||||||
./scripts/check-multi-module.sh; \
|
|
||||||
)
|
|
||||||
|
|
||||||
.PHONY:
|
.PHONY:
|
||||||
test-examples-e2e-kustomize: $(MYGOBIN)/mdrip $(MYGOBIN)/kind
|
verify-kustomize-e2e: $(MYGOBIN)/mdrip $(MYGOBIN)/kind
|
||||||
( \
|
( \
|
||||||
set -e; \
|
set -e; \
|
||||||
/bin/rm -f $(MYGOBIN)/kustomize; \
|
/bin/rm -f $(MYGOBIN)/kustomize; \
|
||||||
@@ -270,96 +169,16 @@ test-examples-kustomize-against-HEAD: $(MYGOBIN)/kustomize $(MYGOBIN)/mdrip
|
|||||||
./hack/testExamplesAgainstKustomize.sh HEAD
|
./hack/testExamplesAgainstKustomize.sh HEAD
|
||||||
|
|
||||||
.PHONY:
|
.PHONY:
|
||||||
test-examples-kustomize-against-3.8.6: $(MYGOBIN)/mdrip
|
test-examples-kustomize-against-v4-release: $(MYGOBIN)/mdrip
|
||||||
( \
|
./hack/testExamplesAgainstKustomize.sh v4@$(LATEST_V4_RELEASE)
|
||||||
set -e; \
|
|
||||||
tag=v3.8.6; \
|
|
||||||
/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.
|
|
||||||
# This is for testing an example plugin that
|
|
||||||
# uses kubeval for validation.
|
|
||||||
# Don't want to add a hard dependence in go.mod file
|
|
||||||
# to github.com/instrumenta/kubeval.
|
|
||||||
# Instead, download the binary.
|
|
||||||
$(MYGOBIN)/kubeval:
|
|
||||||
( \
|
|
||||||
set -e; \
|
|
||||||
d=$(shell mktemp -d); cd $$d; \
|
|
||||||
wget https://github.com/instrumenta/kubeval/releases/latest/download/kubeval-linux-amd64.tar.gz; \
|
|
||||||
tar xf kubeval-linux-amd64.tar.gz; \
|
|
||||||
mv kubeval $(MYGOBIN); \
|
|
||||||
rm -rf $$d; \
|
|
||||||
)
|
|
||||||
|
|
||||||
# linux only.
|
|
||||||
# This is for testing an example plugin that uses helm to inflate a chart
|
|
||||||
# for subsequent kustomization.
|
|
||||||
# Don't want to add a hard dependence in go.mod file to helm.
|
|
||||||
# Instead, download the binaries.
|
|
||||||
$(MYGOBIN)/helmV2:
|
|
||||||
( \
|
|
||||||
set -e; \
|
|
||||||
d=$(shell mktemp -d); cd $$d; \
|
|
||||||
tgzFile=helm-v2.13.1-linux-amd64.tar.gz; \
|
|
||||||
wget https://storage.googleapis.com/kubernetes-helm/$$tgzFile; \
|
|
||||||
tar -xvzf $$tgzFile; \
|
|
||||||
mv linux-amd64/helm $(MYGOBIN)/helmV2; \
|
|
||||||
rm -rf $$d \
|
|
||||||
)
|
|
||||||
|
|
||||||
# Helm V3 differs from helm V2; downloading it to provide coverage for the
|
|
||||||
# chart inflator plugin under helm v3.
|
|
||||||
$(MYGOBIN)/helmV3:
|
|
||||||
( \
|
|
||||||
set -e; \
|
|
||||||
d=$(shell mktemp -d); cd $$d; \
|
|
||||||
tgzFile=helm-v3.4.0-linux-amd64.tar.gz; \
|
|
||||||
wget https://get.helm.sh/$$tgzFile; \
|
|
||||||
tar -xvzf $$tgzFile; \
|
|
||||||
mv linux-amd64/helm $(MYGOBIN)/helmV3; \
|
|
||||||
rm -rf $$d \
|
|
||||||
)
|
|
||||||
|
|
||||||
# Default version of helm is v3.
|
|
||||||
$(MYGOBIN)/helm: $(MYGOBIN)/helmV3
|
|
||||||
ln -s $(MYGOBIN)/helmV3 $(MYGOBIN)/helm
|
|
||||||
|
|
||||||
$(MYGOBIN)/kind:
|
|
||||||
( \
|
|
||||||
set -e; \
|
|
||||||
d=$(shell mktemp -d); cd $$d; \
|
|
||||||
wget -O ./kind https://github.com/kubernetes-sigs/kind/releases/download/v0.7.0/kind-$(shell uname)-amd64; \
|
|
||||||
chmod +x ./kind; \
|
|
||||||
mv ./kind $(MYGOBIN); \
|
|
||||||
rm -rf $$d; \
|
|
||||||
)
|
|
||||||
|
|
||||||
# linux only.
|
|
||||||
$(MYGOBIN)/gh:
|
|
||||||
( \
|
|
||||||
set -e; \
|
|
||||||
d=$(shell mktemp -d); cd $$d; \
|
|
||||||
tgzFile=gh_1.0.0_linux_amd64.tar.gz; \
|
|
||||||
wget https://github.com/cli/cli/releases/download/v1.0.0/$$tgzFile; \
|
|
||||||
tar -xvzf $$tgzFile; \
|
|
||||||
mv gh_1.0.0_linux_amd64/bin/gh $(MYGOBIN)/gh; \
|
|
||||||
rm -rf $$d \
|
|
||||||
)
|
|
||||||
|
|
||||||
|
# --- Cleanup targets ---
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean: kustomize-external-go-plugin-clean
|
clean: clean-kustomize-external-go-plugin uninstall-tools
|
||||||
go clean --cache
|
go clean --cache
|
||||||
rm -f $(builtinplugins)
|
rm -f $(builtinplugins)
|
||||||
rm -f $(MYGOBIN)/pluginator
|
|
||||||
rm -f $(MYGOBIN)/kustomize
|
rm -f $(MYGOBIN)/kustomize
|
||||||
rm -f $(MYGOBIN)/golangci-lint-kustomize
|
|
||||||
|
|
||||||
# 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
|
||||||
|
|||||||
38
Makefile-modules.mk
Normal file
38
Makefile-modules.mk
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# Copyright 2022 The Kubernetes Authors.
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
MYGOBIN = $(shell go env GOBIN)
|
||||||
|
ifeq ($(MYGOBIN),)
|
||||||
|
MYGOBIN = $(shell go env GOPATH)/bin
|
||||||
|
endif
|
||||||
|
export PATH := $(MYGOBIN):$(PATH)
|
||||||
|
|
||||||
|
# only set this if not already set, so importing makefiles can override it
|
||||||
|
export KUSTOMIZE_ROOT ?= $(shell pwd | sed -E 's|(.*\/kustomize)/(.*)|\1|')
|
||||||
|
include $(KUSTOMIZE_ROOT)/Makefile-tools.mk
|
||||||
|
|
||||||
|
.PHONY: lint test fix fmt tidy vet build
|
||||||
|
|
||||||
|
lint: $(MYGOBIN)/golangci-lint
|
||||||
|
$(MYGOBIN)/golangci-lint \
|
||||||
|
-c $$KUSTOMIZE_ROOT/.golangci.yml \
|
||||||
|
--path-prefix $(shell pwd | sed -E 's|(.*\/kustomize)/(.*)|\2|') \
|
||||||
|
run ./...
|
||||||
|
|
||||||
|
test:
|
||||||
|
go test -v -timeout 45m -cover ./...
|
||||||
|
|
||||||
|
fix:
|
||||||
|
go fix ./...
|
||||||
|
|
||||||
|
fmt:
|
||||||
|
go fmt ./...
|
||||||
|
|
||||||
|
tidy:
|
||||||
|
go mod tidy
|
||||||
|
|
||||||
|
vet:
|
||||||
|
go vet ./...
|
||||||
|
|
||||||
|
build:
|
||||||
|
go build -v -o $(MYGOBIN) ./...
|
||||||
102
Makefile-plugins.mk
Normal file
102
Makefile-plugins.mk
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
# Copyright 2022 The Kubernetes Authors.
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
### Kustomize plugin rules.
|
||||||
|
#
|
||||||
|
# The rules to deal with builtin plugins are a bit
|
||||||
|
# complicated because
|
||||||
|
#
|
||||||
|
# - Every builtin plugin is a Go plugin -
|
||||||
|
# meaning it gets its own module directory
|
||||||
|
# (outside of the api module) with Go
|
||||||
|
# code in a 'main' package per Go plugin rules.
|
||||||
|
# - kustomize locates plugins using the
|
||||||
|
# 'apiVersion' and 'kind' fields from the
|
||||||
|
# plugin config file.
|
||||||
|
# - k8s wants CamelCase in 'kind' fields.
|
||||||
|
# - The module name (the last name in the path)
|
||||||
|
# must be the lowercased 'kind' of the
|
||||||
|
# plugin because Go and related tools
|
||||||
|
# demand lowercase in import paths, but
|
||||||
|
# allow CamelCase in file names.
|
||||||
|
# - the generated code must live in the api
|
||||||
|
# module (it's linked into the api).
|
||||||
|
|
||||||
|
# Where all generated builtin plugin code should go.
|
||||||
|
pGen=api/internal/builtins
|
||||||
|
# Where the builtin Go plugin modules live.
|
||||||
|
pSrc=plugin/builtin
|
||||||
|
|
||||||
|
_builtinplugins = \
|
||||||
|
AnnotationsTransformer.go \
|
||||||
|
ConfigMapGenerator.go \
|
||||||
|
IAMPolicyGenerator.go \
|
||||||
|
HashTransformer.go \
|
||||||
|
ImageTagTransformer.go \
|
||||||
|
LabelTransformer.go \
|
||||||
|
LegacyOrderTransformer.go \
|
||||||
|
NamespaceTransformer.go \
|
||||||
|
PatchJson6902Transformer.go \
|
||||||
|
PatchStrategicMergeTransformer.go \
|
||||||
|
PatchTransformer.go \
|
||||||
|
PrefixTransformer.go \
|
||||||
|
SuffixTransformer.go \
|
||||||
|
ReplacementTransformer.go \
|
||||||
|
ReplicaCountTransformer.go \
|
||||||
|
SecretGenerator.go \
|
||||||
|
ValueAddTransformer.go \
|
||||||
|
HelmChartInflationGenerator.go
|
||||||
|
|
||||||
|
# Maintaining this explicit list of generated files, and
|
||||||
|
# adding it as a dependency to a few targets, to assure
|
||||||
|
# they get recreated if deleted. The rules below on how
|
||||||
|
# to make them don't, by themselves, assure they will be
|
||||||
|
# recreated if deleted.
|
||||||
|
builtinplugins = $(patsubst %,$(pGen)/%,$(_builtinplugins))
|
||||||
|
|
||||||
|
# These rules are verbose, but assure that if a source file
|
||||||
|
# is modified, the corresponding generated file, and only
|
||||||
|
# that file, will be recreated.
|
||||||
|
$(pGen)/AnnotationsTransformer.go: $(pSrc)/annotationstransformer/AnnotationsTransformer.go
|
||||||
|
$(pGen)/ConfigMapGenerator.go: $(pSrc)/configmapgenerator/ConfigMapGenerator.go
|
||||||
|
$(pGen)/GkeSaGenerator.go: $(pSrc)/gkesagenerator/GkeSaGenerator.go
|
||||||
|
$(pGen)/HashTransformer.go: $(pSrc)/hashtransformer/HashTransformer.go
|
||||||
|
$(pGen)/ImageTagTransformer.go: $(pSrc)/imagetagtransformer/ImageTagTransformer.go
|
||||||
|
$(pGen)/LabelTransformer.go: $(pSrc)/labeltransformer/LabelTransformer.go
|
||||||
|
$(pGen)/LegacyOrderTransformer.go: $(pSrc)/legacyordertransformer/LegacyOrderTransformer.go
|
||||||
|
$(pGen)/NamespaceTransformer.go: $(pSrc)/namespacetransformer/NamespaceTransformer.go
|
||||||
|
$(pGen)/PatchJson6902Transformer.go: $(pSrc)/patchjson6902transformer/PatchJson6902Transformer.go
|
||||||
|
$(pGen)/PatchStrategicMergeTransformer.go: $(pSrc)/patchstrategicmergetransformer/PatchStrategicMergeTransformer.go
|
||||||
|
$(pGen)/PatchTransformer.go: $(pSrc)/patchtransformer/PatchTransformer.go
|
||||||
|
$(pGen)/PrefixTransformer.go: $(pSrc)/prefixtransformer/PrefixTransformer.go
|
||||||
|
$(pGen)/SuffixTransformer.go: $(pSrc)/suffixtransformer/SuffixTransformer.go
|
||||||
|
$(pGen)/ReplacementTransformer.go: $(pSrc)/replacementtransformer/ReplacementTransformer.go
|
||||||
|
$(pGen)/ReplicaCountTransformer.go: $(pSrc)/replicacounttransformer/ReplicaCountTransformer.go
|
||||||
|
$(pGen)/SecretGenerator.go: $(pSrc)/secretgenerator/SecretGenerator.go
|
||||||
|
$(pGen)/ValueAddTransformer.go: $(pSrc)/valueaddtransformer/ValueAddTransformer.go
|
||||||
|
$(pGen)/HelmChartInflationGenerator.go: $(pSrc)/helmchartinflationgenerator/HelmChartInflationGenerator.go
|
||||||
|
|
||||||
|
# The (verbose but portable) Makefile way to convert to lowercase.
|
||||||
|
toLowerCase = $(subst A,a,$(subst B,b,$(subst C,c,$(subst D,d,$(subst E,e,$(subst F,f,$(subst G,g,$(subst H,h,$(subst I,i,$(subst J,j,$(subst K,k,$(subst L,l,$(subst M,m,$(subst N,n,$(subst O,o,$(subst P,p,$(subst Q,q,$(subst R,r,$(subst S,s,$(subst T,t,$(subst U,u,$(subst V,v,$(subst W,w,$(subst X,x,$(subst Y,y,$(subst Z,z,$1))))))))))))))))))))))))))
|
||||||
|
|
||||||
|
$(pGen)/%.go: $(MYGOBIN)/pluginator
|
||||||
|
@echo "generating $*"
|
||||||
|
( \
|
||||||
|
set -e; \
|
||||||
|
cd $(pSrc)/$(call toLowerCase,$*); \
|
||||||
|
go generate .; \
|
||||||
|
cd ../../../$(pGen); \
|
||||||
|
$(MYGOBIN)/goimports -w $*.go \
|
||||||
|
)
|
||||||
|
|
||||||
|
# Target is for debugging.
|
||||||
|
.PHONY: generate-kustomize-builtin-plugins
|
||||||
|
generate-kustomize-builtin-plugins: $(builtinplugins)
|
||||||
|
|
||||||
|
.PHONY: build-kustomize-external-go-plugin
|
||||||
|
build-kustomize-external-go-plugin:
|
||||||
|
./hack/buildExternalGoPlugins.sh ./plugin
|
||||||
|
|
||||||
|
.PHONY: clean-kustomize-external-go-plugin
|
||||||
|
clean-kustomize-external-go-plugin:
|
||||||
|
./hack/buildExternalGoPlugins.sh ./plugin clean
|
||||||
100
Makefile-tools.mk
Normal file
100
Makefile-tools.mk
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
# Copyright 2022 The Kubernetes Authors.
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
MYGOBIN = $(shell go env GOBIN)
|
||||||
|
ifeq ($(MYGOBIN),)
|
||||||
|
MYGOBIN = $(shell go env GOPATH)/bin
|
||||||
|
endif
|
||||||
|
export PATH := $(MYGOBIN):$(PATH)
|
||||||
|
|
||||||
|
.PHONY: install-out-of-tree-tools
|
||||||
|
install-out-of-tree-tools: \
|
||||||
|
$(MYGOBIN)/goimports \
|
||||||
|
$(MYGOBIN)/golangci-lint \
|
||||||
|
$(MYGOBIN)/helmV3 \
|
||||||
|
$(MYGOBIN)/mdrip \
|
||||||
|
$(MYGOBIN)/stringer \
|
||||||
|
$(MYGOBIN)/goimports
|
||||||
|
|
||||||
|
.PHONY: uninstall-out-of-tree-tools
|
||||||
|
uninstall-out-of-tree-tools:
|
||||||
|
rm -f $(MYGOBIN)/goimports
|
||||||
|
rm -f $(MYGOBIN)/golangci-lint
|
||||||
|
rm -f $(MYGOBIN)/helmV3
|
||||||
|
rm -f $(MYGOBIN)/mdrip
|
||||||
|
rm -f $(MYGOBIN)/stringer
|
||||||
|
|
||||||
|
$(MYGOBIN)/golangci-lint:
|
||||||
|
go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.45.2
|
||||||
|
|
||||||
|
$(MYGOBIN)/mdrip:
|
||||||
|
go install github.com/monopole/mdrip@v1.0.2
|
||||||
|
|
||||||
|
$(MYGOBIN)/stringer:
|
||||||
|
go install golang.org/x/tools/cmd/stringer@latest
|
||||||
|
|
||||||
|
$(MYGOBIN)/goimports:
|
||||||
|
go install golang.org/x/tools/cmd/goimports@latest
|
||||||
|
|
||||||
|
$(MYGOBIN)/mdtogo:
|
||||||
|
go install sigs.k8s.io/kustomize/cmd/mdtogo@latest
|
||||||
|
|
||||||
|
$(MYGOBIN)/addlicense:
|
||||||
|
go install github.com/google/addlicense@latest
|
||||||
|
|
||||||
|
$(MYGOBIN)/statik:
|
||||||
|
go install github.com/rakyll/statik@latest
|
||||||
|
|
||||||
|
$(MYGOBIN)/goreleaser:
|
||||||
|
go install github.com/goreleaser/goreleaser@v0.179.0 # https://github.com/kubernetes-sigs/kustomize/issues/4542
|
||||||
|
|
||||||
|
$(MYGOBIN)/kind:
|
||||||
|
( \
|
||||||
|
set -e; \
|
||||||
|
d=$(shell mktemp -d); cd $$d; \
|
||||||
|
wget -O ./kind https://github.com/kubernetes-sigs/kind/releases/download/v0.7.0/kind-$(GOOS)-$(GOARCH); \
|
||||||
|
chmod +x ./kind; \
|
||||||
|
mv ./kind $(MYGOBIN); \
|
||||||
|
rm -rf $$d; \
|
||||||
|
)
|
||||||
|
|
||||||
|
# linux only.
|
||||||
|
$(MYGOBIN)/gh:
|
||||||
|
( \
|
||||||
|
set -e; \
|
||||||
|
d=$(shell mktemp -d); cd $$d; \
|
||||||
|
tgzFile=gh_1.0.0_$(GOOS)_$(GOARCH).tar.gz; \
|
||||||
|
wget https://github.com/cli/cli/releases/download/v1.0.0/$$tgzFile; \
|
||||||
|
tar -xvzf $$tgzFile; \
|
||||||
|
mv gh_1.0.0_$(GOOS)_$(GOARCH)/bin/gh $(MYGOBIN)/gh; \
|
||||||
|
rm -rf $$d \
|
||||||
|
)
|
||||||
|
|
||||||
|
# linux only.
|
||||||
|
# This is for testing an example plugin that
|
||||||
|
# uses kubeval for validation.
|
||||||
|
# Don't want to add a hard dependence in go.mod file
|
||||||
|
# to github.com/instrumenta/kubeval.
|
||||||
|
# Instead, download the binary.
|
||||||
|
$(MYGOBIN)/kubeval:
|
||||||
|
( \
|
||||||
|
set -e; \
|
||||||
|
d=$(shell mktemp -d); cd $$d; \
|
||||||
|
wget https://github.com/instrumenta/kubeval/releases/latest/download/kubeval-$(GOOS)-$(GOARCH).tar.gz; \
|
||||||
|
tar xf kubeval-$(GOOS)-$(GOARCH).tar.gz; \
|
||||||
|
mv kubeval $(MYGOBIN); \
|
||||||
|
rm -rf $$d; \
|
||||||
|
)
|
||||||
|
|
||||||
|
# Helm V3 differs from helm V2; downloading it to provide coverage for the
|
||||||
|
# chart inflator plugin under helm v3.
|
||||||
|
$(MYGOBIN)/helmV3:
|
||||||
|
( \
|
||||||
|
set -e; \
|
||||||
|
d=$(shell mktemp -d); cd $$d; \
|
||||||
|
tgzFile=helm-v3.6.3-$(GOOS)-$(GOARCH).tar.gz; \
|
||||||
|
wget https://get.helm.sh/$$tgzFile; \
|
||||||
|
tar -xvzf $$tgzFile; \
|
||||||
|
mv $(GOOS)-$(GOARCH)/helm $(MYGOBIN)/helmV3; \
|
||||||
|
rm -rf $$d \
|
||||||
|
)
|
||||||
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,14 +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
|
- Shell32-Natsu
|
||||||
|
- justinsb
|
||||||
|
- monopole
|
||||||
|
- pwittrock
|
||||||
|
|||||||
68
README.md
68
README.md
@@ -11,8 +11,8 @@ and it's like [`sed`], in that it emits edited text.
|
|||||||
|
|
||||||
This tool is sponsored by [sig-cli] ([KEP]).
|
This tool is sponsored by [sig-cli] ([KEP]).
|
||||||
|
|
||||||
- [Installation instructions](https://kubernetes-sigs.github.io/kustomize/installation)
|
- [Installation instructions](https://kubectl.docs.kubernetes.io/installation/kustomize/)
|
||||||
- [General documentation](https://kubernetes-sigs.github.io/kustomize)
|
- [General documentation](https://kubectl.docs.kubernetes.io/references/kustomize/)
|
||||||
- [Examples](examples)
|
- [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)
|
||||||
@@ -20,15 +20,27 @@ This tool is sponsored by [sig-cli] ([KEP]).
|
|||||||
|
|
||||||
## 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]: https://github.com/kubernetes-sigs/kustomize/releases/tag/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 [kubernetes documentation].
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
@@ -129,8 +141,9 @@ The YAML can be directly [applied] to a cluster:
|
|||||||
|
|
||||||
## Community
|
## Community
|
||||||
|
|
||||||
- [file a bug](https://kubernetes-sigs.github.io/kustomize/contributing/bugs/) instructions
|
- [file a bug](https://kubectl.docs.kubernetes.io/contributing/kustomize/bugs/)
|
||||||
- [contribute a feature](https://kubernetes-sigs.github.io/kustomize/contributing/features/) instructions
|
- [contribute a feature](https://kubectl.docs.kubernetes.io/contributing/kustomize/features/)
|
||||||
|
- [propose a larger enhancement](https://github.com/kubernetes-sigs/kustomize/tree/master/proposals)
|
||||||
|
|
||||||
### Code of conduct
|
### Code of conduct
|
||||||
|
|
||||||
@@ -139,27 +152,22 @@ 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://kubectl.docs.kubernetes.io/references/kustomize/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://kubectl.docs.kubernetes.io/references/kustomize/glossary/#apply
|
||||||
[base]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#base
|
[base]: https://kubectl.docs.kubernetes.io/references/kustomize/glossary/#base
|
||||||
[declarative configuration]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#declarative-application-management
|
[declarative configuration]: https://kubectl.docs.kubernetes.io/references/kustomize/glossary/#declarative-application-management
|
||||||
[imageBase]: docs/images/base.jpg
|
[imageBase]: images/base.jpg
|
||||||
[imageOverlay]: docs/images/overlay.jpg
|
[imageOverlay]: 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
|
|
||||||
[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://kubectl.docs.kubernetes.io/references/kustomize/glossary/#kubernetes-style-object
|
||||||
[kustomization]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#kustomization
|
[kustomization]: https://kubectl.docs.kubernetes.io/references/kustomize/glossary/#kustomization
|
||||||
[overlay]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#overlay
|
[overlay]: https://kubectl.docs.kubernetes.io/references/kustomize/glossary/#overlay
|
||||||
[overlays]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#overlay
|
[overlays]: https://kubectl.docs.kubernetes.io/references/kustomize/glossary/#overlay
|
||||||
[release page]: https://github.com/kubernetes-sigs/kustomize/releases
|
[release page]: https://github.com/kubernetes-sigs/kustomize/releases
|
||||||
[resource]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#resource
|
[resource]: https://kubectl.docs.kubernetes.io/references/kustomize/glossary/#resource
|
||||||
[resources]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#resource
|
[resources]: https://kubectl.docs.kubernetes.io/references/kustomize/glossary/#resource
|
||||||
[sig-cli]: https://github.com/kubernetes/community/blob/master/sig-cli/README.md
|
[sig-cli]: https://github.com/kubernetes/community/blob/master/sig-cli/README.md
|
||||||
[variant]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#variant
|
[variants]: https://kubectl.docs.kubernetes.io/references/kustomize/glossary/#variant
|
||||||
[variants]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#variant
|
|
||||||
[v2.0.3]: https://github.com/kubernetes-sigs/kustomize/releases/tag/v2.0.3
|
|
||||||
[v2.1.0]: https://github.com/kubernetes-sigs/kustomize/releases/tag/v2.1.0
|
|
||||||
[workflows]: https://kubernetes-sigs.github.io/kustomize/guides
|
|
||||||
|
|||||||
112
ROADMAP.md
Normal file
112
ROADMAP.md
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
# Kustomize roadmap 2022
|
||||||
|
|
||||||
|
Presented at the [January 26, 2022, SIG-CLI meeting](https://youtu.be/l2plzJ9MRlk?t=1321)
|
||||||
|
|
||||||
|
kustomize maintainers: @knverey, @natasha41575
|
||||||
|
|
||||||
|
[Objective: Improve contributor community](#objective-improve-contributor-community)
|
||||||
|
|
||||||
|
[Objective: Improve end-user experience](#objective-improve-end-user-experience)
|
||||||
|
|
||||||
|
[Objective: Improve extension experience](#objective-improve-extension-experience)
|
||||||
|
|
||||||
|
## Objective: Improve contributor community
|
||||||
|
|
||||||
|
**_WHO: End user who also contributes source code._**
|
||||||
|
|
||||||
|
Top priority:
|
||||||
|
|
||||||
|
- Kustomization v1 (also end-user impact) ([PROJECT](https://github.com/kubernetes-sigs/kustomize/projects/12))
|
||||||
|
- Remove the following fields:
|
||||||
|
- [vars](https://github.com/kubernetes-sigs/kustomize/issues/2052)
|
||||||
|
- [patchesJson6902, patchesStrategicMerge (consolidate on \`patches)](https://github.com/kubernetes-sigs/kustomize/issues/4376)
|
||||||
|
- [helmChartInflationGenerator, helmCharts, helmGlobals](https://github.com/kubernetes-sigs/kustomize/issues/4401)
|
||||||
|
- all long-deprecated fields in Kustomization v1 such as \`bases\` and those being accommodate by kustomize edit \[[see code snippet](https://github.com/kubernetes-sigs/kustomize/blob/ee4b7847f0beb6c0d2070673b10f23f7b3e92e82/api/types/fix.go#L15)\]
|
||||||
|
- Ensure that \`kustomize edit fix\` handles migrations for all those, and that anything it changes is not still present in v1.
|
||||||
|
- [Add reorder field](https://github.com/kubernetes-sigs/kustomize/issues/3913). Default should be FIFO and legacy should also be supported (could add alphabetic and custom sort support eventually). Replaces -reorder flag.
|
||||||
|
- [Reconcile openapi and crds field](https://github.com/kubernetes-sigs/kustomize/issues/3944)
|
||||||
|
- [Consider deprecating configurations field](https://github.com/kubernetes-sigs/kustomize/issues/3945) (old, pre-plugin, pre-openapi global configuration)
|
||||||
|
- [Add a field to enable the managedby label](https://github.com/kubernetes-sigs/kustomize/issues/4047)
|
||||||
|
|
||||||
|
Second priority:
|
||||||
|
|
||||||
|
- Improve contributor documentation
|
||||||
|
- [Instructions to upgrade kustomize-in-kubectl](https://github.com/kubernetes-sigs/kustomize/issues/3951)
|
||||||
|
|
||||||
|
Also very valuable to the project:
|
||||||
|
|
||||||
|
- [Improve the release process](https://github.com/kubernetes-sigs/kustomize/issues/3952) to support regular biweekly releases [PROJECT](https://github.com/kubernetes-sigs/kustomize/projects/7)
|
||||||
|
- Release sigs.k8s.io/kustomize/api v1.0.0 [PROJECT](https://github.com/kubernetes-sigs/kustomize/projects/5)
|
||||||
|
- [Reduce the public surface of the API module](https://github.com/kubernetes-sigs/kustomize/issues/3942)
|
||||||
|
- [Vendor all transitive deps](https://github.com/kubernetes-sigs/kustomize/issues/3706). Since kustomize is in kubectl, we must do as kubectl does to manage deps, exposing new transitive deps in code review.
|
||||||
|
- Project administration
|
||||||
|
- [Rename master branch to main](https://github.com/kubernetes-sigs/kustomize/issues/3954)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Objective: Improve end-user experience
|
||||||
|
|
||||||
|
**_WHO: End user that wants kustomize build artifacts (binaries, containers)._**
|
||||||
|
|
||||||
|
Top priorities:
|
||||||
|
|
||||||
|
- Bug fixes:
|
||||||
|
- Fix bugs in basic anchor support: [issue query](https://github.com/kubernetes-sigs/kustomize/issues?q=is%3Aopen+is%3Aissue+label%3Aarea%2Fanchors)
|
||||||
|
- integer keys support: [#3446](https://github.com/kubernetes-sigs/kustomize/issues/3446)
|
||||||
|
- kyaml not respecting \`$patch replace|retainKeys\`: [#2037](https://github.com/kubernetes-sigs/kustomize/issues/2037)
|
||||||
|
- kustomize removing quotes from namespace field values: [#4146](https://github.com/kubernetes-sigs/kustomize/issues/4146)
|
||||||
|
- Kustomize doesn’t support metadata.generateName: [#641](https://github.com/kubernetes-sigs/kustomize/issues/641)
|
||||||
|
- Send kustomize CLI version number into kubectl ([kubectl issue](https://github.com/kubernetes/kubectl/issues/797) / [kustomize issue](https://github.com/kubernetes-sigs/kustomize/issues/1424))
|
||||||
|
- Kustomize performance investigations/improvements [PROJECT](https://github.com/kubernetes-sigs/kustomize/projects/13)
|
||||||
|
- [Support generic resource references in name reference tracking](https://github.com/kubernetes-sigs/kustomize/issues/3418)
|
||||||
|
- [KEP 4267: retain the resource origin and transformer data in annotations](https://github.com/kubernetes-sigs/kustomize/pull/4267)
|
||||||
|
|
||||||
|
Secondary priorities:
|
||||||
|
|
||||||
|
- kustomize cli v5 ([PROJECT](https://github.com/kubernetes-sigs/kustomize/projects/14))
|
||||||
|
- [Drop the --reorder flag](https://github.com/kubernetes-sigs/kustomize/issues/3947)
|
||||||
|
- [Graduate cfg read-only commands out of alpha](https://github.com/kubernetes-sigs/kustomize/issues/4090).
|
||||||
|
- [Drop the –enable-managedby-label](https://github.com/kubernetes-sigs/kustomize/issues/4047)
|
||||||
|
- Drop old plugin-related fields in favor of [the Catalog-style fields](https://github.com/kubernetes/enhancements/tree/master/keps/sig-cli/2906-kustomize-function-catalog).
|
||||||
|
- [Drop the helm flags](https://github.com/kubernetes-sigs/kustomize/issues/4401)
|
||||||
|
- [Confusion around namespace replacement](https://github.com/kubernetes-sigs/kustomize/issues/880).
|
||||||
|
|
||||||
|
Also very valuable to the project:
|
||||||
|
|
||||||
|
- [Overinclusion of root directory error in error messages](https://github.com/kubernetes-sigs/kustomize/issues/4348)
|
||||||
|
- [Add kustomize localize command](https://github.com/kubernetes-sigs/kustomize/issues/3980)
|
||||||
|
- [Fix Windows support in test suite](https://github.com/kubernetes-sigs/kustomize/issues/4001)
|
||||||
|
- Improve end-user documentation [PROJECT](https://github.com/kubernetes-sigs/kustomize/projects/9)
|
||||||
|
|
||||||
|
|
||||||
|
## Objective: Improve extension experience
|
||||||
|
|
||||||
|
**_WHO: Plugin developers: end users who extend kustomize, but don’t think about internals._**
|
||||||
|
|
||||||
|
This objective is described in detail in the [Kustomize Plugin Graduation KEP](https://github.com/kubernetes/enhancements/tree/master/keps/sig-cli/2953-kustomize-plugin-graduation) / [PROJECT](https://github.com/kubernetes-sigs/kustomize/projects/15) .
|
||||||
|
|
||||||
|
Top priorities:
|
||||||
|
|
||||||
|
- Fix core usability issues with KRM Function extensions:
|
||||||
|
- [Better errors for function config failures](https://github.com/kubernetes-sigs/kustomize/issues/4398)
|
||||||
|
- [Container KRM Mounts are not mounting via function parameters](https://github.com/kubernetes-sigs/kustomize/issues/4290)
|
||||||
|
- [Resolution of local file references in extensions transformer configuration](https://github.com/kubernetes-sigs/kustomize/issues/4154)
|
||||||
|
- [Do not silently ignore plugins when config has typo](https://github.com/kubernetes-sigs/kustomize/issues/4399)
|
||||||
|
- [KRM Exec Function can't locate executable when referencing a base](https://github.com/kubernetes-sigs/kustomize/issues/4347)
|
||||||
|
- Once core usability issues are fixed, [deprecate legacy exec and Go plugin support](https://github.com/kubernetes/enhancements/tree/master/keps/sig-cli/2953-kustomize-plugin-graduation)
|
||||||
|
- [Catalog KEP](https://github.com/kubernetes/enhancements/tree/master/keps/sig-cli/2906-kustomize-function-catalog)
|
||||||
|
|
||||||
|
Secondary priorities:
|
||||||
|
|
||||||
|
- [Remove Starlark support](https://github.com/kubernetes-sigs/kustomize/issues/4349)
|
||||||
|
- [Composition KEP](https://github.com/kubernetes/enhancements/pull/2300). The implementation is complete in [#4223](https://github.com/kubernetes-sigs/kustomize/pull/4323), but depends on:
|
||||||
|
- [Convert resources and components to be backed by a reusable generator](https://github.com/kubernetes-sigs/kustomize/issues/4402)
|
||||||
|
- [Enable explicitly invoked transformers to use default fieldSpecs](https://github.com/kubernetes-sigs/kustomize/issues/4404)
|
||||||
|
- [Enable built-in generators to be used in the transformers field ](https://github.com/kubernetes-sigs/kustomize/issues/4403)
|
||||||
|
|
||||||
|
|
||||||
|
Also very valuable to the project:
|
||||||
|
|
||||||
|
- [Improve docs for kyaml libraries](https://github.com/kubernetes-sigs/kustomize/issues/3950), especially by adding examples.
|
||||||
|
- [Create a reserved field for plugin runtime information](https://github.com/kubernetes-sigs/kustomize/issues/4405)
|
||||||
|
- [Develop new standard process for implementing builtin transformers](https://github.com/kubernetes-sigs/kustomize/issues/4400)
|
||||||
14
api/Makefile
Normal file
14
api/Makefile
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# Copyright 2022 The Kubernetes Authors.
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
include ../Makefile-modules.mk
|
||||||
|
|
||||||
|
test:
|
||||||
|
go test -v -timeout 45m -cover ./... -ldflags "-X sigs.k8s.io/kustomize/api/provenance.version=v444.333.222"
|
||||||
|
cd krusty/openapitests; OPENAPI_TEST=true go test -v -timeout 45m -p 1 -cover ./...
|
||||||
|
|
||||||
|
build:
|
||||||
|
go build -ldflags "-X sigs.k8s.io/kustomize/api/provenance.version=v444.333.222" ./...
|
||||||
|
|
||||||
|
generate: $(MYGOBIN)/k8scopy $(MYGOBIN)/stringer
|
||||||
|
go generate ./...
|
||||||
@@ -1,178 +0,0 @@
|
|||||||
// Code generated by pluginator on HelmChartInflationGenerator; DO NOT EDIT.
|
|
||||||
// pluginator {unknown 1970-01-01T00:00:00Z }
|
|
||||||
|
|
||||||
package builtins
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"sigs.k8s.io/kustomize/api/filesys"
|
|
||||||
"sigs.k8s.io/kustomize/api/resmap"
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
|
||||||
"sigs.k8s.io/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
// HelmChartInflationGeneratorPlugin is a plugin to generate resources
|
|
||||||
// from a remote or local helm chart.
|
|
||||||
type HelmChartInflationGeneratorPlugin struct {
|
|
||||||
h *resmap.PluginHelpers
|
|
||||||
types.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
|
||||||
runHelmCommand func([]string) ([]byte, error)
|
|
||||||
types.HelmChartArgs
|
|
||||||
tmpDir string
|
|
||||||
}
|
|
||||||
|
|
||||||
var KustomizePlugin HelmChartInflationGeneratorPlugin
|
|
||||||
|
|
||||||
// Config uses the input plugin configurations `config` to setup the generator
|
|
||||||
// options
|
|
||||||
func (p *HelmChartInflationGeneratorPlugin) Config(h *resmap.PluginHelpers, config []byte) error {
|
|
||||||
p.h = h
|
|
||||||
err := yaml.Unmarshal(config, p)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
tmpDir, err := filesys.NewTmpConfirmedDir()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
p.tmpDir = string(tmpDir)
|
|
||||||
if p.ChartName == "" {
|
|
||||||
return fmt.Errorf("chartName cannot be empty")
|
|
||||||
}
|
|
||||||
if p.ChartHome == "" {
|
|
||||||
p.ChartHome = path.Join(p.tmpDir, "chart")
|
|
||||||
}
|
|
||||||
if p.ChartRepoName == "" {
|
|
||||||
p.ChartRepoName = "stable"
|
|
||||||
}
|
|
||||||
if p.HelmBin == "" {
|
|
||||||
p.HelmBin = "helm"
|
|
||||||
}
|
|
||||||
if p.HelmHome == "" {
|
|
||||||
p.HelmHome = path.Join(p.tmpDir, ".helm")
|
|
||||||
}
|
|
||||||
if p.Values == "" {
|
|
||||||
p.Values = path.Join(p.ChartHome, p.ChartName, "values.yaml")
|
|
||||||
}
|
|
||||||
// runHelmCommand will run `helm` command with args provided. Return stdout
|
|
||||||
// and error if there is any.
|
|
||||||
p.runHelmCommand = func(args []string) ([]byte, error) {
|
|
||||||
stdout := new(bytes.Buffer)
|
|
||||||
stderr := new(bytes.Buffer)
|
|
||||||
cmd := exec.Command(p.HelmBin, args...)
|
|
||||||
cmd.Stdout = stdout
|
|
||||||
cmd.Stderr = stderr
|
|
||||||
cmd.Env = append(cmd.Env,
|
|
||||||
fmt.Sprintf("HELM_CONFIG_HOME=%s", p.HelmHome),
|
|
||||||
fmt.Sprintf("HELM_CACHE_HOME=%s/.cache", p.HelmHome),
|
|
||||||
fmt.Sprintf("HELM_DATA_HOME=%s/.data", p.HelmHome),
|
|
||||||
)
|
|
||||||
err := cmd.Run()
|
|
||||||
if err != nil {
|
|
||||||
return stdout.Bytes(),
|
|
||||||
errors.Wrap(
|
|
||||||
fmt.Errorf("failed to run command %s %s", p.HelmBin, strings.Join(args, " ")),
|
|
||||||
stderr.String(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return stdout.Bytes(), nil
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate implements generator
|
|
||||||
func (p *HelmChartInflationGeneratorPlugin) Generate() (resmap.ResMap, error) {
|
|
||||||
// cleanup
|
|
||||||
defer os.RemoveAll(p.tmpDir)
|
|
||||||
// check helm version. we only support V3
|
|
||||||
err := p.checkHelmVersion()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// pull the chart
|
|
||||||
if !p.checkLocalChart() {
|
|
||||||
_, err := p.runHelmCommand(p.getPullCommandArgs())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// render the charts
|
|
||||||
stdout, err := p.runHelmCommand(p.getTemplateCommandArgs())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return p.h.ResmapFactory().NewResMapFromBytes(stdout)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *HelmChartInflationGeneratorPlugin) getTemplateCommandArgs() []string {
|
|
||||||
args := []string{"template"}
|
|
||||||
if p.ReleaseName != "" {
|
|
||||||
args = append(args, p.ReleaseName)
|
|
||||||
}
|
|
||||||
args = append(args, path.Join(p.ChartHome, p.ChartName))
|
|
||||||
if p.ReleaseNamespace != "" {
|
|
||||||
args = append(args, "--namespace", p.ReleaseNamespace)
|
|
||||||
}
|
|
||||||
if p.Values != "" {
|
|
||||||
args = append(args, "--values", p.Values)
|
|
||||||
}
|
|
||||||
return args
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *HelmChartInflationGeneratorPlugin) getPullCommandArgs() []string {
|
|
||||||
args := []string{"pull", "--untar", "--untardir", p.ChartHome}
|
|
||||||
chartName := fmt.Sprintf("%s/%s", p.ChartRepoName, p.ChartName)
|
|
||||||
if p.ChartVersion != "" {
|
|
||||||
args = append(args, "--version", p.ChartVersion)
|
|
||||||
}
|
|
||||||
if p.ChartRepoURL != "" {
|
|
||||||
args = append(args, "--repo", p.ChartRepoURL)
|
|
||||||
chartName = p.ChartName
|
|
||||||
}
|
|
||||||
|
|
||||||
args = append(args, chartName)
|
|
||||||
|
|
||||||
return args
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkLocalChart will return true if the chart does exist in
|
|
||||||
// local chart home.
|
|
||||||
func (p *HelmChartInflationGeneratorPlugin) checkLocalChart() bool {
|
|
||||||
path := path.Join(p.ChartHome, p.ChartName)
|
|
||||||
s, err := os.Stat(path)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return 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 := string(r.Find(stdout))[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{}
|
|
||||||
}
|
|
||||||
@@ -1,188 +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/filters/imagetag"
|
|
||||||
"sigs.k8s.io/kustomize/api/resmap"
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/filtersutil"
|
|
||||||
"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() {
|
|
||||||
// traverse all fields at first
|
|
||||||
err := filtersutil.ApplyToJSON(imagetag.LegacyFilter{
|
|
||||||
ImageTag: p.ImageTag,
|
|
||||||
}, r)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// then use user specified field specs
|
|
||||||
err = filtersutil.ApplyToJSON(imagetag.Filter{
|
|
||||||
ImageTag: p.ImageTag,
|
|
||||||
FsSlice: p.FieldSpecs,
|
|
||||||
}, r)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ImageTagTransformerPlugin) mutateImage(in interface{}) (interface{}, error) {
|
|
||||||
original, ok := in.(string)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("image path is not of type string but %T", in)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isImageMatched(original, p.ImageTag.Name) {
|
|
||||||
return original, nil
|
|
||||||
}
|
|
||||||
name, tag := split(original)
|
|
||||||
if p.ImageTag.NewName != "" {
|
|
||||||
name = p.ImageTag.NewName
|
|
||||||
}
|
|
||||||
if p.ImageTag.NewTag != "" {
|
|
||||||
tag = ":" + p.ImageTag.NewTag
|
|
||||||
}
|
|
||||||
if p.ImageTag.Digest != "" {
|
|
||||||
tag = "@" + p.ImageTag.Digest
|
|
||||||
}
|
|
||||||
return name + tag, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// findAndReplaceImage replaces the image name and
|
|
||||||
// tags inside one object.
|
|
||||||
// It searches the object for container session
|
|
||||||
// then loops though all images inside containers
|
|
||||||
// session, finds matched ones and update the
|
|
||||||
// image name and tag name
|
|
||||||
func (p *ImageTagTransformerPlugin) findAndReplaceImage(obj map[string]interface{}) error {
|
|
||||||
paths := []string{"containers", "initContainers"}
|
|
||||||
updated := false
|
|
||||||
for _, path := range paths {
|
|
||||||
containers, found := obj[path]
|
|
||||||
if found && containers != nil {
|
|
||||||
if _, err := p.updateContainers(containers); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
updated = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !updated {
|
|
||||||
return p.findContainers(obj)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ImageTagTransformerPlugin) updateContainers(in interface{}) (interface{}, error) {
|
|
||||||
containers, ok := in.([]interface{})
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"containers path is not of type []interface{} but %T", in)
|
|
||||||
}
|
|
||||||
for i := range containers {
|
|
||||||
container := containers[i].(map[string]interface{})
|
|
||||||
containerImage, found := container["image"]
|
|
||||||
if !found {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
imageName := containerImage.(string)
|
|
||||||
if isImageMatched(imageName, p.ImageTag.Name) {
|
|
||||||
newImage, err := p.mutateImage(imageName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
container["image"] = newImage
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return containers, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ImageTagTransformerPlugin) findContainers(obj map[string]interface{}) error {
|
|
||||||
for key := range obj {
|
|
||||||
switch typedV := obj[key].(type) {
|
|
||||||
case map[string]interface{}:
|
|
||||||
err := p.findAndReplaceImage(typedV)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case []interface{}:
|
|
||||||
for i := range typedV {
|
|
||||||
item := typedV[i]
|
|
||||||
typedItem, ok := item.(map[string]interface{})
|
|
||||||
if ok {
|
|
||||||
err := p.findAndReplaceImage(typedItem)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isImageMatched(s, t string) bool {
|
|
||||||
// Tag values are limited to [a-zA-Z0-9_.{}-].
|
|
||||||
// Some tools like Bazel rules_k8s allow tag patterns with {} characters.
|
|
||||||
// More info: https://github.com/bazelbuild/rules_k8s/pull/423
|
|
||||||
pattern, _ := regexp.Compile("^" + t + "(@sha256)?(:[a-zA-Z0-9_.{}-]*)?$")
|
|
||||||
return pattern.MatchString(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// split separates and returns the name and tag parts
|
|
||||||
// from the image string using either colon `:` or at `@` separators.
|
|
||||||
// Note that the returned tag keeps its separator.
|
|
||||||
func split(imageName string) (name string, tag string) {
|
|
||||||
// check if image name contains a domain
|
|
||||||
// if domain is present, ignore domain and check for `:`
|
|
||||||
ic := -1
|
|
||||||
if slashIndex := strings.Index(imageName, "/"); slashIndex < 0 {
|
|
||||||
ic = strings.LastIndex(imageName, ":")
|
|
||||||
} else {
|
|
||||||
lastIc := strings.LastIndex(imageName[slashIndex:], ":")
|
|
||||||
// set ic only if `:` is present
|
|
||||||
if lastIc > 0 {
|
|
||||||
ic = slashIndex + lastIc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ia := strings.LastIndex(imageName, "@")
|
|
||||||
if ic < 0 && ia < 0 {
|
|
||||||
return imageName, ""
|
|
||||||
}
|
|
||||||
|
|
||||||
i := ic
|
|
||||||
if ia > 0 {
|
|
||||||
i = ia
|
|
||||||
}
|
|
||||||
|
|
||||||
name = imageName[:i]
|
|
||||||
tag = imageName[i:]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewImageTagTransformerPlugin() resmap.TransformerPlugin {
|
|
||||||
return &ImageTagTransformerPlugin{}
|
|
||||||
}
|
|
||||||
@@ -1,120 +0,0 @@
|
|||||||
// Code generated by pluginator on PatchStrategicMergeTransformer; DO NOT EDIT.
|
|
||||||
// pluginator {unknown 1970-01-01T00:00:00Z }
|
|
||||||
|
|
||||||
package builtins
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"sigs.k8s.io/kustomize/api/filters/patchstrategicmerge"
|
|
||||||
"sigs.k8s.io/kustomize/api/resmap"
|
|
||||||
"sigs.k8s.io/kustomize/api/resource"
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/filtersutil"
|
|
||||||
"sigs.k8s.io/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PatchStrategicMergeTransformerPlugin struct {
|
|
||||||
h *resmap.PluginHelpers
|
|
||||||
loadedPatches []*resource.Resource
|
|
||||||
Paths []types.PatchStrategicMerge `json:"paths,omitempty" yaml:"paths,omitempty"`
|
|
||||||
Patches string `json:"patches,omitempty" yaml:"patches,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PatchStrategicMergeTransformerPlugin) Config(
|
|
||||||
h *resmap.PluginHelpers, c []byte) (err error) {
|
|
||||||
p.h = h
|
|
||||||
err = yaml.Unmarshal(c, p)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(p.Paths) == 0 && p.Patches == "" {
|
|
||||||
return fmt.Errorf("empty file path and empty patch content")
|
|
||||||
}
|
|
||||||
if len(p.Paths) != 0 {
|
|
||||||
for _, onePath := range p.Paths {
|
|
||||||
res, err := p.h.ResmapFactory().RF().SliceFromBytes([]byte(onePath))
|
|
||||||
if err == nil {
|
|
||||||
p.loadedPatches = append(p.loadedPatches, res...)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
res, err = p.h.ResmapFactory().RF().SliceFromPatches(
|
|
||||||
p.h.Loader(), []types.PatchStrategicMerge{onePath})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
p.loadedPatches = append(p.loadedPatches, res...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if p.Patches != "" {
|
|
||||||
res, err := p.h.ResmapFactory().RF().SliceFromBytes([]byte(p.Patches))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
p.loadedPatches = append(p.loadedPatches, res...)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(p.loadedPatches) == 0 {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"patch appears to be empty; files=%v, Patch=%s", p.Paths, p.Patches)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PatchStrategicMergeTransformerPlugin) Transform(m resmap.ResMap) error {
|
|
||||||
patches, err := p.h.ResmapFactory().Merge(p.loadedPatches)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, patch := range patches.Resources() {
|
|
||||||
target, err := m.GetById(patch.OrgId())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
patchCopy := patch.DeepCopy()
|
|
||||||
patchCopy.SetName(target.GetName())
|
|
||||||
patchCopy.SetNamespace(target.GetNamespace())
|
|
||||||
patchCopy.SetGvk(target.GetGvk())
|
|
||||||
node, err := filtersutil.GetRNode(patchCopy)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = filtersutil.ApplyToJSON(patchstrategicmerge.Filter{
|
|
||||||
Patch: node,
|
|
||||||
}, target)
|
|
||||||
if err != nil {
|
|
||||||
// Check for an error string from UnmarshalJSON that's indicative
|
|
||||||
// of an object that's missing basic KRM fields, and thus may have been
|
|
||||||
// entirely deleted (an acceptable outcome). This error handling should
|
|
||||||
// be deleted along with use of ResMap and apimachinery functions like
|
|
||||||
// UnmarshalJSON.
|
|
||||||
if !strings.Contains(err.Error(), "Object 'Kind' is missing") {
|
|
||||||
// Some unknown error, let it through.
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !target.IsEmpty() {
|
|
||||||
return errors.Wrapf(
|
|
||||||
err, "with unexpectedly non-empty object map of size %d",
|
|
||||||
len(target.Map()))
|
|
||||||
}
|
|
||||||
// Fall through to handle deleted object.
|
|
||||||
}
|
|
||||||
if target.IsEmpty() {
|
|
||||||
// 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,195 +0,0 @@
|
|||||||
// Code generated by pluginator on PatchTransformer; DO NOT EDIT.
|
|
||||||
// pluginator {unknown 1970-01-01T00:00:00Z }
|
|
||||||
|
|
||||||
package builtins
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
jsonpatch "github.com/evanphx/json-patch"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"sigs.k8s.io/kustomize/api/filters/patchjson6902"
|
|
||||||
"sigs.k8s.io/kustomize/api/filters/patchstrategicmerge"
|
|
||||||
"sigs.k8s.io/kustomize/api/resmap"
|
|
||||||
"sigs.k8s.io/kustomize/api/resource"
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/filtersutil"
|
|
||||||
"sigs.k8s.io/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PatchTransformerPlugin struct {
|
|
||||||
loadedPatch *resource.Resource
|
|
||||||
decodedPatch jsonpatch.Patch
|
|
||||||
Path string `json:"path,omitempty" yaml:"path,omitempty"`
|
|
||||||
Patch string `json:"patch,omitempty" yaml:"patch,omitempty"`
|
|
||||||
Target *types.Selector `json:"target,omitempty" yaml:"target,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PatchTransformerPlugin) Config(
|
|
||||||
h *resmap.PluginHelpers, c []byte) error {
|
|
||||||
err := yaml.Unmarshal(c, p)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
p.Patch = strings.TrimSpace(p.Patch)
|
|
||||||
if p.Patch == "" && p.Path == "" {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"must specify one of patch and path in\n%s", string(c))
|
|
||||||
}
|
|
||||||
if p.Patch != "" && p.Path != "" {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"patch and path can't be set at the same time\n%s", string(c))
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.Path != "" {
|
|
||||||
loaded, loadErr := h.Loader().Load(p.Path)
|
|
||||||
if loadErr != nil {
|
|
||||||
return loadErr
|
|
||||||
}
|
|
||||||
p.Patch = string(loaded)
|
|
||||||
}
|
|
||||||
|
|
||||||
patchSM, errSM := h.ResmapFactory().RF().FromBytes([]byte(p.Patch))
|
|
||||||
patchJson, errJson := jsonPatchFromBytes([]byte(p.Patch))
|
|
||||||
if (errSM == nil && errJson == nil) ||
|
|
||||||
(patchSM != nil && patchJson != nil) {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"illegally qualifies as both an SM and JSON patch: [%v]",
|
|
||||||
p.Patch)
|
|
||||||
}
|
|
||||||
if errSM != nil && errJson != nil {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"unable to parse SM or JSON patch from [%v]", p.Patch)
|
|
||||||
}
|
|
||||||
if errSM == nil {
|
|
||||||
p.loadedPatch = patchSM
|
|
||||||
} else {
|
|
||||||
p.decodedPatch = patchJson
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PatchTransformerPlugin) Transform(m resmap.ResMap) error {
|
|
||||||
if p.loadedPatch == nil {
|
|
||||||
return p.transformJson6902(m, p.decodedPatch)
|
|
||||||
} else {
|
|
||||||
// The patch was a strategic merge patch
|
|
||||||
return p.transformStrategicMerge(m, p.loadedPatch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// transformStrategicMerge applies the provided strategic merge patch
|
|
||||||
// to all the resources in the ResMap that match either the Target or
|
|
||||||
// the identifier of the patch.
|
|
||||||
func (p *PatchTransformerPlugin) transformStrategicMerge(m resmap.ResMap, patch *resource.Resource) error {
|
|
||||||
if p.Target == nil {
|
|
||||||
target, err := m.GetById(patch.OrgId())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return p.applySMPatch(target, patch)
|
|
||||||
}
|
|
||||||
|
|
||||||
resources, err := m.Select(*p.Target)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, res := range resources {
|
|
||||||
patchCopy := patch.DeepCopy()
|
|
||||||
patchCopy.SetName(res.GetName())
|
|
||||||
patchCopy.SetNamespace(res.GetNamespace())
|
|
||||||
patchCopy.SetGvk(res.GetGvk())
|
|
||||||
err := p.applySMPatch(res, patchCopy)
|
|
||||||
if err != nil {
|
|
||||||
// Check for an error string from UnmarshalJSON that's indicative
|
|
||||||
// of an object that's missing basic KRM fields, and thus may have been
|
|
||||||
// entirely deleted (an acceptable outcome). This error handling should
|
|
||||||
// be deleted along with use of ResMap and apimachinery functions like
|
|
||||||
// UnmarshalJSON.
|
|
||||||
if !strings.Contains(err.Error(), "Object 'Kind' is missing") {
|
|
||||||
// Some unknown error, let it through.
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !res.IsEmpty() {
|
|
||||||
return errors.Wrapf(
|
|
||||||
err, "with unexpectedly non-empty object map of size %d",
|
|
||||||
len(res.Map()))
|
|
||||||
}
|
|
||||||
// Fall through to handle deleted object.
|
|
||||||
}
|
|
||||||
if res.IsEmpty() {
|
|
||||||
// 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(res.CurId())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// applySMPatch applies the provided strategic merge patch to the
|
|
||||||
// given resource.
|
|
||||||
func (p *PatchTransformerPlugin) applySMPatch(resource, patch *resource.Resource) error {
|
|
||||||
node, err := filtersutil.GetRNode(patch)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
n, ns := resource.GetName(), resource.GetNamespace()
|
|
||||||
err = filtersutil.ApplyToJSON(patchstrategicmerge.Filter{
|
|
||||||
Patch: node,
|
|
||||||
}, resource)
|
|
||||||
if !resource.IsEmpty() {
|
|
||||||
resource.SetName(n)
|
|
||||||
resource.SetNamespace(ns)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// transformJson6902 applies the provided json6902 patch
|
|
||||||
// to all the resources in the ResMap that match the Target.
|
|
||||||
func (p *PatchTransformerPlugin) transformJson6902(m resmap.ResMap, patch jsonpatch.Patch) error {
|
|
||||||
if p.Target == nil {
|
|
||||||
return fmt.Errorf("must specify a target for patch %s", p.Patch)
|
|
||||||
}
|
|
||||||
resources, err := m.Select(*p.Target)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, res := range resources {
|
|
||||||
err = filtersutil.ApplyToJSON(patchjson6902.Filter{
|
|
||||||
Patch: p.Patch,
|
|
||||||
}, res)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// jsonPatchFromBytes loads a Json 6902 patch from
|
|
||||||
// a bytes input
|
|
||||||
func jsonPatchFromBytes(
|
|
||||||
in []byte) (jsonpatch.Patch, error) {
|
|
||||||
ops := string(in)
|
|
||||||
if ops == "" {
|
|
||||||
return nil, fmt.Errorf("empty json patch operations")
|
|
||||||
}
|
|
||||||
|
|
||||||
if ops[0] != '[' {
|
|
||||||
jsonOps, err := yaml.YAMLToJSON(in)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ops = string(jsonOps)
|
|
||||||
}
|
|
||||||
return jsonpatch.DecodePatch([]byte(ops))
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPatchTransformerPlugin() resmap.TransformerPlugin {
|
|
||||||
return &PatchTransformerPlugin{}
|
|
||||||
}
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
// Code generated by pluginator on PrefixSuffixTransformer; DO NOT EDIT.
|
|
||||||
// pluginator {unknown 1970-01-01T00:00:00Z }
|
|
||||||
|
|
||||||
package builtins
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/filters/prefixsuffix"
|
|
||||||
"sigs.k8s.io/kustomize/api/resid"
|
|
||||||
"sigs.k8s.io/kustomize/api/resmap"
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/filtersutil"
|
|
||||||
"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.FsSlice `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Gvk skip list for prefix/suffix modification.
|
|
||||||
// hard coded for now - eventually should be part of config.
|
|
||||||
var prefixSuffixFieldSpecsToSkip = types.FsSlice{
|
|
||||||
{Gvk: resid.Gvk{Kind: "CustomResourceDefinition"}},
|
|
||||||
{Gvk: resid.Gvk{Group: "apiregistration.k8s.io", Kind: "APIService"}},
|
|
||||||
{Gvk: resid.Gvk{Kind: "Namespace"}},
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PrefixSuffixTransformerPlugin) Config(
|
|
||||||
_ *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() {
|
|
||||||
// TODO: move this test into the filter (i.e. make a better filter)
|
|
||||||
if p.shouldSkip(r.OrgId()) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
id := r.OrgId()
|
|
||||||
// current default configuration contains
|
|
||||||
// only one entry: "metadata/name" with no GVK
|
|
||||||
for _, fs := range p.FieldSpecs {
|
|
||||||
// TODO: this is redundant to filter (but needed for now)
|
|
||||||
if !id.IsSelected(&fs.Gvk) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// TODO: move this test into the filter.
|
|
||||||
if smellsLikeANameChange(&fs) {
|
|
||||||
// "metadata/name" is the only field.
|
|
||||||
// this will add a prefix and a suffix
|
|
||||||
// to the resource even if those are
|
|
||||||
// empty
|
|
||||||
r.AddNamePrefix(p.Prefix)
|
|
||||||
r.AddNameSuffix(p.Suffix)
|
|
||||||
}
|
|
||||||
err := filtersutil.ApplyToJSON(prefixsuffix.Filter{
|
|
||||||
Prefix: p.Prefix,
|
|
||||||
Suffix: p.Suffix,
|
|
||||||
FieldSpec: fs,
|
|
||||||
}, r)
|
|
||||||
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 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
|
|
||||||
}
|
|
||||||
@@ -1,165 +0,0 @@
|
|||||||
// Copyright 2019 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package filesys_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
. "sigs.k8s.io/kustomize/api/filesys"
|
|
||||||
)
|
|
||||||
|
|
||||||
func makeTestDir(t *testing.T) (FileSystem, string) {
|
|
||||||
fSys := MakeFsOnDisk()
|
|
||||||
td, err := ioutil.TempDir("", "kustomize_testing_dir")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error %s", err)
|
|
||||||
}
|
|
||||||
testDir, err := filepath.EvalSymlinks(td)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error %s", err)
|
|
||||||
}
|
|
||||||
if !fSys.Exists(testDir) {
|
|
||||||
t.Fatalf("expected existence")
|
|
||||||
}
|
|
||||||
if !fSys.IsDir(testDir) {
|
|
||||||
t.Fatalf("expected directory")
|
|
||||||
}
|
|
||||||
return fSys, testDir
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCleanedAbs_1(t *testing.T) {
|
|
||||||
fSys, testDir := makeTestDir(t)
|
|
||||||
defer os.RemoveAll(testDir)
|
|
||||||
|
|
||||||
d, f, err := fSys.CleanedAbs("")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected err=%v", err)
|
|
||||||
}
|
|
||||||
wd, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected err=%v", err)
|
|
||||||
}
|
|
||||||
if d.String() != wd {
|
|
||||||
t.Fatalf("unexpected d=%s", d)
|
|
||||||
}
|
|
||||||
if f != "" {
|
|
||||||
t.Fatalf("unexpected f=%s", f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCleanedAbs_2(t *testing.T) {
|
|
||||||
fSys, testDir := makeTestDir(t)
|
|
||||||
defer os.RemoveAll(testDir)
|
|
||||||
|
|
||||||
d, f, err := fSys.CleanedAbs("/")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected err=%v", err)
|
|
||||||
}
|
|
||||||
if d != "/" {
|
|
||||||
t.Fatalf("unexpected d=%s", d)
|
|
||||||
}
|
|
||||||
if f != "" {
|
|
||||||
t.Fatalf("unexpected f=%s", f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCleanedAbs_3(t *testing.T) {
|
|
||||||
fSys, testDir := makeTestDir(t)
|
|
||||||
defer os.RemoveAll(testDir)
|
|
||||||
|
|
||||||
err := fSys.WriteFile(
|
|
||||||
filepath.Join(testDir, "foo"), []byte(`foo`))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected err=%v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
d, f, err := fSys.CleanedAbs(filepath.Join(testDir, "foo"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected err=%v", err)
|
|
||||||
}
|
|
||||||
if d.String() != testDir {
|
|
||||||
t.Fatalf("unexpected d=%s", d)
|
|
||||||
}
|
|
||||||
if f != "foo" {
|
|
||||||
t.Fatalf("unexpected f=%s", f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCleanedAbs_4(t *testing.T) {
|
|
||||||
fSys, testDir := makeTestDir(t)
|
|
||||||
defer os.RemoveAll(testDir)
|
|
||||||
|
|
||||||
err := fSys.MkdirAll(filepath.Join(testDir, "d1", "d2"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected err=%v", err)
|
|
||||||
}
|
|
||||||
err = fSys.WriteFile(
|
|
||||||
filepath.Join(testDir, "d1", "d2", "bar"),
|
|
||||||
[]byte(`bar`))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected err=%v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
d, f, err := fSys.CleanedAbs(
|
|
||||||
filepath.Join(testDir, "d1", "d2"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected err=%v", err)
|
|
||||||
}
|
|
||||||
if d.String() != filepath.Join(testDir, "d1", "d2") {
|
|
||||||
t.Fatalf("unexpected d=%s", d)
|
|
||||||
}
|
|
||||||
if f != "" {
|
|
||||||
t.Fatalf("unexpected f=%s", f)
|
|
||||||
}
|
|
||||||
|
|
||||||
d, f, err = fSys.CleanedAbs(
|
|
||||||
filepath.Join(testDir, "d1", "d2", "bar"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected err=%v", err)
|
|
||||||
}
|
|
||||||
if d.String() != filepath.Join(testDir, "d1", "d2") {
|
|
||||||
t.Fatalf("unexpected d=%s", d)
|
|
||||||
}
|
|
||||||
if f != "bar" {
|
|
||||||
t.Fatalf("unexpected f=%s", f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReadFilesRealFS(t *testing.T) {
|
|
||||||
fSys, testDir := makeTestDir(t)
|
|
||||||
defer os.RemoveAll(testDir)
|
|
||||||
|
|
||||||
err := fSys.WriteFile(path.Join(testDir, "foo"), []byte(`foo`))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error %s", err)
|
|
||||||
}
|
|
||||||
if !fSys.Exists(path.Join(testDir, "foo")) {
|
|
||||||
t.Fatalf("expected foo")
|
|
||||||
}
|
|
||||||
if fSys.IsDir(path.Join(testDir, "foo")) {
|
|
||||||
t.Fatalf("expected foo not to be a directory")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = fSys.WriteFile(path.Join(testDir, "bar"), []byte(`bar`))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
files, err := fSys.Glob(path.Join("testDir", "*"))
|
|
||||||
expected := []string{
|
|
||||||
path.Join(testDir, "bar"),
|
|
||||||
path.Join(testDir, "foo"),
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("expected no error")
|
|
||||||
}
|
|
||||||
if reflect.DeepEqual(files, expected) {
|
|
||||||
t.Fatalf("incorrect files found by glob: %v", files)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
// Copyright 2019 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package filesys
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RootedPath returns a rooted path, e.g. "/foo/bar" as
|
|
||||||
// opposed to "foo/bar".
|
|
||||||
func RootedPath(elem ...string) string {
|
|
||||||
return Separator + filepath.Join(elem...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StripTrailingSeps trims trailing filepath separators from input.
|
|
||||||
func StripTrailingSeps(s string) string {
|
|
||||||
k := len(s)
|
|
||||||
for k > 0 && s[k-1] == filepath.Separator {
|
|
||||||
k--
|
|
||||||
}
|
|
||||||
return s[:k]
|
|
||||||
}
|
|
||||||
|
|
||||||
// StripLeadingSeps trims leading filepath separators from input.
|
|
||||||
func StripLeadingSeps(s string) string {
|
|
||||||
k := 0
|
|
||||||
for k < len(s) && s[k] == filepath.Separator {
|
|
||||||
k++
|
|
||||||
}
|
|
||||||
return s[k:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// PathSplit converts a file path to a slice of string.
|
|
||||||
// If the path is absolute (if the path has a leading slash),
|
|
||||||
// then the first entry in the result is an empty string.
|
|
||||||
// Desired: path == PathJoin(PathSplit(path))
|
|
||||||
func PathSplit(incoming string) []string {
|
|
||||||
if incoming == "" {
|
|
||||||
return []string{}
|
|
||||||
}
|
|
||||||
dir, path := filepath.Split(incoming)
|
|
||||||
if dir == string(os.PathSeparator) {
|
|
||||||
if path == "" {
|
|
||||||
return []string{""}
|
|
||||||
}
|
|
||||||
return []string{"", path}
|
|
||||||
}
|
|
||||||
dir = strings.TrimSuffix(dir, string(os.PathSeparator))
|
|
||||||
if dir == "" {
|
|
||||||
return []string{path}
|
|
||||||
}
|
|
||||||
return append(PathSplit(dir), path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PathJoin converts a slice of string to a file path.
|
|
||||||
// If the first entry is an empty string, then the returned
|
|
||||||
// path is absolute (it has a leading slash).
|
|
||||||
// Desired: path == PathJoin(PathSplit(path))
|
|
||||||
func PathJoin(incoming []string) string {
|
|
||||||
if len(incoming) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
if incoming[0] == "" {
|
|
||||||
return string(os.PathSeparator) + filepath.Join(incoming[1:]...)
|
|
||||||
}
|
|
||||||
return filepath.Join(incoming...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// InsertPathPart inserts 'part' at position 'pos' in the given filepath.
|
|
||||||
// The first position is 0.
|
|
||||||
//
|
|
||||||
// E.g. if part == 'PEACH'
|
|
||||||
//
|
|
||||||
// OLD : NEW : POS
|
|
||||||
// --------------------------------------------------------
|
|
||||||
// {empty} : PEACH : irrelevant
|
|
||||||
// / : /PEACH : irrelevant
|
|
||||||
// pie : PEACH/pie : 0 (or negative)
|
|
||||||
// /pie : /PEACH/pie : 0 (or negative)
|
|
||||||
// raw : raw/PEACH : 1 (or larger)
|
|
||||||
// /raw : /raw/PEACH : 1 (or larger)
|
|
||||||
// a/nice/warm/pie : a/nice/warm/PEACH/pie : 3
|
|
||||||
// /a/nice/warm/pie : /a/nice/warm/PEACH/pie : 3
|
|
||||||
//
|
|
||||||
// * An empty part results in no change.
|
|
||||||
//
|
|
||||||
// * Absolute paths get their leading '/' stripped, treated like
|
|
||||||
// relative paths, and the leading '/' is re-added on output.
|
|
||||||
// The meaning of pos is intentionally the same in either absolute or
|
|
||||||
// relative paths; if it weren't, this function could convert absolute
|
|
||||||
// paths to relative paths, which is not desirable.
|
|
||||||
//
|
|
||||||
// * For robustness (liberal input, conservative output) Pos values that
|
|
||||||
// that are too small (large) to index the split filepath result in a
|
|
||||||
// prefix (postfix) rather than an error. Use extreme position values
|
|
||||||
// to assure a prefix or postfix (e.g. 0 will always prefix, and
|
|
||||||
// 9999 will presumably always postfix).
|
|
||||||
func InsertPathPart(path string, pos int, part string) string {
|
|
||||||
if part == "" {
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
parts := PathSplit(path)
|
|
||||||
if pos < 0 {
|
|
||||||
pos = 0
|
|
||||||
} else if pos > len(parts) {
|
|
||||||
pos = len(parts)
|
|
||||||
}
|
|
||||||
if len(parts) > 0 && parts[0] == "" && pos < len(parts) {
|
|
||||||
// An empty string at 0 indicates an absolute path, and means
|
|
||||||
// we must increment pos. This change means that a position
|
|
||||||
// specification has the same meaning in relative and absolute paths.
|
|
||||||
// E.g. in either the path 'a/b/c' or the path '/a/b/c',
|
|
||||||
// 'a' is at 0, 'b' is at 1 and 'c' is at 2, and inserting at
|
|
||||||
// zero means a new first field _without_ changing an absolute
|
|
||||||
// path to a relative path.
|
|
||||||
pos++
|
|
||||||
}
|
|
||||||
result := make([]string, len(parts)+1)
|
|
||||||
copy(result, parts[0:pos])
|
|
||||||
result[pos] = part
|
|
||||||
return PathJoin(append(result, parts[pos:]...))
|
|
||||||
}
|
|
||||||
@@ -1,374 +0,0 @@
|
|||||||
package filesys_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
. "sigs.k8s.io/kustomize/api/filesys"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Confirm behavior of filepath.Match
|
|
||||||
func TestFilePathMatch(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
pattern string
|
|
||||||
path string
|
|
||||||
expected bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
pattern: "*e*",
|
|
||||||
path: "hey",
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pattern: "*e*",
|
|
||||||
path: "hay",
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pattern: "*e*",
|
|
||||||
path: filepath.Join("h", "e", "y"),
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pattern: "*/e/*",
|
|
||||||
path: filepath.Join("h", "e", "y"),
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pattern: "h/e/*",
|
|
||||||
path: filepath.Join("h", "e", "y"),
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pattern: "*/e/y",
|
|
||||||
path: filepath.Join("h", "e", "y"),
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pattern: "*/*/*",
|
|
||||||
path: filepath.Join("h", "e", "y"),
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pattern: "*/*/*",
|
|
||||||
path: filepath.Join("h", "e", "y", "there"),
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pattern: "*/*/*/t*e",
|
|
||||||
path: filepath.Join("h", "e", "y", "there"),
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, item := range cases {
|
|
||||||
match, err := filepath.Match(item.pattern, item.path)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected err: %v", err)
|
|
||||||
}
|
|
||||||
if match != item.expected {
|
|
||||||
t.Fatalf("'%s' '%s' %v\n", item.pattern, item.path, match)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Confirm behavior of filepath.Split
|
|
||||||
func TestFilePathSplit(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
full string
|
|
||||||
dir string
|
|
||||||
file string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
full: "",
|
|
||||||
dir: "",
|
|
||||||
file: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
full: SelfDir,
|
|
||||||
dir: "",
|
|
||||||
file: SelfDir,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
full: "rabbit.jpg",
|
|
||||||
dir: "",
|
|
||||||
file: "rabbit.jpg",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
full: "/",
|
|
||||||
dir: "/",
|
|
||||||
file: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
full: "/beans",
|
|
||||||
dir: "/",
|
|
||||||
file: "beans",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
full: "/home/foo/bar",
|
|
||||||
dir: "/home/foo/",
|
|
||||||
file: "bar",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
full: "/usr/local/",
|
|
||||||
dir: "/usr/local/",
|
|
||||||
file: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
full: "/usr//local//go",
|
|
||||||
dir: "/usr//local//",
|
|
||||||
file: "go",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, p := range cases {
|
|
||||||
dir, file := filepath.Split(p.full)
|
|
||||||
if dir != p.dir || file != p.file {
|
|
||||||
t.Fatalf(
|
|
||||||
"in '%s',\ngot dir='%s' (expected '%s'),\n got file='%s' (expected %s).",
|
|
||||||
p.full, dir, p.dir, file, p.file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPathSplitAndJoin(t *testing.T) {
|
|
||||||
cases := map[string]struct {
|
|
||||||
original string
|
|
||||||
expected []string
|
|
||||||
}{
|
|
||||||
"Empty": {
|
|
||||||
original: "",
|
|
||||||
expected: []string{},
|
|
||||||
},
|
|
||||||
"One": {
|
|
||||||
original: "hello",
|
|
||||||
expected: []string{"hello"},
|
|
||||||
},
|
|
||||||
"Two": {
|
|
||||||
original: "hello/there",
|
|
||||||
expected: []string{"hello", "there"},
|
|
||||||
},
|
|
||||||
"Three": {
|
|
||||||
original: "hello/my/friend",
|
|
||||||
expected: []string{"hello", "my", "friend"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for n, c := range cases {
|
|
||||||
f := func(t *testing.T, original string, expected []string) {
|
|
||||||
actual := PathSplit(original)
|
|
||||||
if len(actual) != len(expected) {
|
|
||||||
t.Fatalf(
|
|
||||||
"expected len %d, got len %d",
|
|
||||||
len(expected), len(actual))
|
|
||||||
}
|
|
||||||
for i := range expected {
|
|
||||||
if expected[i] != actual[i] {
|
|
||||||
t.Fatalf(
|
|
||||||
"at i=%d, expected '%s', got '%s'",
|
|
||||||
i, expected[i], actual[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
joined := PathJoin(actual)
|
|
||||||
if joined != original {
|
|
||||||
t.Fatalf(
|
|
||||||
"when rejoining, expected '%s', got '%s'",
|
|
||||||
original, joined)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.Run("relative"+n, func(t *testing.T) {
|
|
||||||
f(t, c.original, c.expected)
|
|
||||||
})
|
|
||||||
t.Run("absolute"+n, func(t *testing.T) {
|
|
||||||
f(t,
|
|
||||||
string(os.PathSeparator)+c.original,
|
|
||||||
append([]string{""}, c.expected...))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInsertPathPart(t *testing.T) {
|
|
||||||
cases := map[string]struct {
|
|
||||||
original string
|
|
||||||
pos int
|
|
||||||
part string
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
"rootOne": {
|
|
||||||
original: "/",
|
|
||||||
pos: 0,
|
|
||||||
part: "___",
|
|
||||||
expected: "/___",
|
|
||||||
},
|
|
||||||
"rootTwo": {
|
|
||||||
original: "/",
|
|
||||||
pos: 444,
|
|
||||||
part: "___",
|
|
||||||
expected: "/___",
|
|
||||||
},
|
|
||||||
"rootedFirst": {
|
|
||||||
original: "/apple",
|
|
||||||
pos: 0,
|
|
||||||
part: "___",
|
|
||||||
expected: "/___/apple",
|
|
||||||
},
|
|
||||||
"rootedSecond": {
|
|
||||||
original: "/apple",
|
|
||||||
pos: 444,
|
|
||||||
part: "___",
|
|
||||||
expected: "/apple/___",
|
|
||||||
},
|
|
||||||
"rootedThird": {
|
|
||||||
original: "/apple/banana",
|
|
||||||
pos: 444,
|
|
||||||
part: "___",
|
|
||||||
expected: "/apple/banana/___",
|
|
||||||
},
|
|
||||||
"emptyLow": {
|
|
||||||
original: "",
|
|
||||||
pos: -3,
|
|
||||||
part: "___",
|
|
||||||
expected: "___",
|
|
||||||
},
|
|
||||||
"emptyHigh": {
|
|
||||||
original: "",
|
|
||||||
pos: 444,
|
|
||||||
part: "___",
|
|
||||||
expected: "___",
|
|
||||||
},
|
|
||||||
"peachPie": {
|
|
||||||
original: "a/nice/warm/pie",
|
|
||||||
pos: 3,
|
|
||||||
part: "PEACH",
|
|
||||||
expected: "a/nice/warm/PEACH/pie",
|
|
||||||
},
|
|
||||||
"rootedPeachPie": {
|
|
||||||
original: "/a/nice/warm/pie",
|
|
||||||
pos: 3,
|
|
||||||
part: "PEACH",
|
|
||||||
expected: "/a/nice/warm/PEACH/pie",
|
|
||||||
},
|
|
||||||
"longStart": {
|
|
||||||
original: "a/b/c/d/e/f",
|
|
||||||
pos: 0,
|
|
||||||
part: "___",
|
|
||||||
expected: "___/a/b/c/d/e/f",
|
|
||||||
},
|
|
||||||
"rootedLongStart": {
|
|
||||||
original: "/a/b/c/d/e/f",
|
|
||||||
pos: 0,
|
|
||||||
part: "___",
|
|
||||||
expected: "/___/a/b/c/d/e/f",
|
|
||||||
},
|
|
||||||
"longMiddle": {
|
|
||||||
original: "a/b/c/d/e/f",
|
|
||||||
pos: 3,
|
|
||||||
part: "___",
|
|
||||||
expected: "a/b/c/___/d/e/f",
|
|
||||||
},
|
|
||||||
"rootedLongMiddle": {
|
|
||||||
original: "/a/b/c/d/e/f",
|
|
||||||
pos: 3,
|
|
||||||
part: "___",
|
|
||||||
expected: "/a/b/c/___/d/e/f",
|
|
||||||
},
|
|
||||||
"longEnd": {
|
|
||||||
original: "a/b/c/d/e/f",
|
|
||||||
pos: 444,
|
|
||||||
part: "___",
|
|
||||||
expected: "a/b/c/d/e/f/___",
|
|
||||||
},
|
|
||||||
"rootedLongEnd": {
|
|
||||||
original: "/a/b/c/d/e/f",
|
|
||||||
pos: 444,
|
|
||||||
part: "___",
|
|
||||||
expected: "/a/b/c/d/e/f/___",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for n, c := range cases {
|
|
||||||
t.Run(n, func(t *testing.T) {
|
|
||||||
actual := InsertPathPart(c.original, c.pos, c.part)
|
|
||||||
if actual != c.expected {
|
|
||||||
t.Fatalf("expected '%s', got '%s'", c.expected, actual)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStripTrailingSeps(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
full string
|
|
||||||
rem string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
full: "foo",
|
|
||||||
rem: "foo",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
full: "",
|
|
||||||
rem: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
full: "foo/",
|
|
||||||
rem: "foo",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
full: "foo///bar///",
|
|
||||||
rem: "foo///bar",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
full: "/////",
|
|
||||||
rem: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
full: "/",
|
|
||||||
rem: "",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, p := range cases {
|
|
||||||
dir := StripTrailingSeps(p.full)
|
|
||||||
if dir != p.rem {
|
|
||||||
t.Fatalf(
|
|
||||||
"in '%s', got dir='%s' (expected '%s')",
|
|
||||||
p.full, dir, p.rem)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStripLeadingSeps(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
full string
|
|
||||||
rem string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
full: "foo",
|
|
||||||
rem: "foo",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
full: "",
|
|
||||||
rem: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
full: "/foo",
|
|
||||||
rem: "foo",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
full: "///foo///bar///",
|
|
||||||
rem: "foo///bar///",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
full: "/////",
|
|
||||||
rem: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
full: "/",
|
|
||||||
rem: "",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, p := range cases {
|
|
||||||
dir := StripLeadingSeps(p.full)
|
|
||||||
if dir != p.rem {
|
|
||||||
t.Fatalf(
|
|
||||||
"in '%s', got dir='%s' (expected '%s')",
|
|
||||||
p.full, dir, p.rem)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -19,18 +19,26 @@ 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: filtersutil.SetEntry(
|
SetValue: f.trackableSetter.SetEntry(
|
||||||
k, f.Annotations[k], yaml.NodeTagString),
|
k, f.Annotations[k], yaml.NodeTagString),
|
||||||
CreateKind: yaml.MappingNode, // Annotations are MappingNodes.
|
CreateKind: yaml.MappingNode, // Annotations are MappingNodes.
|
||||||
CreateTag: yaml.NodeTagMap,
|
CreateTag: yaml.NodeTagMap,
|
||||||
|
|||||||
@@ -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.FsSlice = append(annosFs, tc.fsslice...)
|
filter.WithMutationTracker(tc.setEntryCallback)
|
||||||
|
filter.FsSlice = append(annosFs, tc.fsslice...) //nolint:gocritic
|
||||||
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"
|
||||||
}
|
}
|
||||||
|
|||||||
8
api/filters/doc.go
Normal file
8
api/filters/doc.go
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
// Copyright 2022 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package filters
|
||||||
|
|
||||||
|
// Package filters collects various implementations
|
||||||
|
// sigs.k8s.io/kustomize/kyaml/kio.Filter used by kustomize
|
||||||
|
// transformers to modify kubernetes objects.
|
||||||
@@ -4,17 +4,29 @@
|
|||||||
package fieldspec
|
package fieldspec
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/filters/filtersutil"
|
"sigs.k8s.io/kustomize/api/filters/filtersutil"
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
"sigs.k8s.io/kustomize/kyaml/errors"
|
"sigs.k8s.io/kustomize/kyaml/errors"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/resid"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/utils"
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ yaml.Filter = Filter{}
|
var _ yaml.Filter = Filter{}
|
||||||
|
|
||||||
// Filter applies a single fieldSpec to a single object
|
// Filter possibly mutates its object argument using a FieldSpec.
|
||||||
|
// If the object matches the FieldSpec, and the node found
|
||||||
|
// by following the fieldSpec's path is non-null, this filter calls
|
||||||
|
// the setValue function on the node at the end of the path.
|
||||||
|
// If any part of the path doesn't exist, the filter returns
|
||||||
|
// without doing anything and without error, unless it was set
|
||||||
|
// to create the path. If set to create, it creates a tree of maps
|
||||||
|
// along the path, and the leaf node gets the setValue called on it.
|
||||||
|
// Error on GVK mismatch, empty or poorly formed path.
|
||||||
|
// Filter expect kustomize style paths, not JSON paths.
|
||||||
// Filter stores internal state and should not be reused
|
// Filter stores internal state and should not be reused
|
||||||
type Filter struct {
|
type Filter struct {
|
||||||
// FieldSpec contains the path to the value to set.
|
// FieldSpec contains the path to the value to set.
|
||||||
@@ -34,47 +46,52 @@ type Filter struct {
|
|||||||
|
|
||||||
func (fltr Filter) Filter(obj *yaml.RNode) (*yaml.RNode, error) {
|
func (fltr Filter) Filter(obj *yaml.RNode) (*yaml.RNode, error) {
|
||||||
// check if the FieldSpec applies to the object
|
// check if the FieldSpec applies to the object
|
||||||
if match, err := isMatchGVK(fltr.FieldSpec, obj); !match || err != nil {
|
if match := isMatchGVK(fltr.FieldSpec, obj); !match {
|
||||||
return obj, errors.Wrap(err)
|
return obj, nil
|
||||||
}
|
}
|
||||||
fltr.path = splitPath(fltr.FieldSpec.Path)
|
fltr.path = utils.PathSplitter(fltr.FieldSpec.Path, "/")
|
||||||
if err := fltr.filter(obj); err != nil {
|
if err := fltr.filter(obj); err != nil {
|
||||||
s, _ := obj.String()
|
|
||||||
return nil, errors.WrapPrefixf(err,
|
return nil, errors.WrapPrefixf(err,
|
||||||
"obj '%s' at path '%v'", s, fltr.FieldSpec.Path)
|
"considering field '%s' of object %s", fltr.FieldSpec.Path, resid.FromRNode(obj))
|
||||||
}
|
}
|
||||||
return obj, nil
|
return obj, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Recursively called.
|
||||||
func (fltr Filter) filter(obj *yaml.RNode) error {
|
func (fltr Filter) filter(obj *yaml.RNode) error {
|
||||||
if len(fltr.path) == 0 {
|
if len(fltr.path) == 0 {
|
||||||
// found the field -- set its value
|
// found the field -- set its value
|
||||||
return fltr.SetValue(obj)
|
return fltr.SetValue(obj)
|
||||||
}
|
}
|
||||||
if obj.IsTaggedNull() {
|
if obj.IsTaggedNull() || obj.IsNil() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
switch obj.YNode().Kind {
|
switch obj.YNode().Kind {
|
||||||
case yaml.SequenceNode:
|
case yaml.SequenceNode:
|
||||||
return fltr.seq(obj)
|
return fltr.handleSequence(obj)
|
||||||
case yaml.MappingNode:
|
case yaml.MappingNode:
|
||||||
return fltr.field(obj)
|
return fltr.handleMap(obj)
|
||||||
|
case yaml.AliasNode:
|
||||||
|
return fltr.filter(yaml.NewRNode(obj.YNode().Alias))
|
||||||
default:
|
default:
|
||||||
return errors.Errorf("expected sequence or mapping node")
|
return errors.Errorf("expected sequence or mapping node")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// field calls filter on the field matching the next path element
|
// handleMap calls filter on the map field matching the next path element
|
||||||
func (fltr Filter) field(obj *yaml.RNode) error {
|
func (fltr Filter) handleMap(obj *yaml.RNode) error {
|
||||||
fieldName, isSeq := isSequenceField(fltr.path[0])
|
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
|
// lookup the field matching the next path element
|
||||||
var lookupField yaml.Filter
|
var operation yaml.Filter
|
||||||
var kind yaml.Kind
|
var kind yaml.Kind
|
||||||
tag := yaml.NodeTagEmpty
|
tag := yaml.NodeTagEmpty
|
||||||
switch {
|
switch {
|
||||||
case !fltr.FieldSpec.CreateIfNotPresent || fltr.CreateKind == 0 || isSeq:
|
case !fltr.FieldSpec.CreateIfNotPresent || fltr.CreateKind == 0 || isSeq:
|
||||||
// dont' create the field if we don't find it
|
// don't create the field if we don't find it
|
||||||
lookupField = yaml.Lookup(fieldName)
|
operation = yaml.Lookup(fieldName)
|
||||||
if isSeq {
|
if isSeq {
|
||||||
// The query path thinks this field should be a sequence;
|
// The query path thinks this field should be a sequence;
|
||||||
// accept this hint for use later if the tag is NodeTagNull.
|
// accept this hint for use later if the tag is NodeTagNull.
|
||||||
@@ -82,21 +99,25 @@ func (fltr Filter) field(obj *yaml.RNode) error {
|
|||||||
}
|
}
|
||||||
case len(fltr.path) <= 1:
|
case len(fltr.path) <= 1:
|
||||||
// create the field if it is missing: use the provided node kind
|
// create the field if it is missing: use the provided node kind
|
||||||
lookupField = yaml.LookupCreate(fltr.CreateKind, fieldName)
|
operation = yaml.LookupCreate(fltr.CreateKind, fieldName)
|
||||||
kind = fltr.CreateKind
|
kind = fltr.CreateKind
|
||||||
tag = fltr.CreateTag
|
tag = fltr.CreateTag
|
||||||
default:
|
default:
|
||||||
// create the field if it is missing: must be a mapping node
|
// create the field if it is missing: must be a mapping node
|
||||||
lookupField = yaml.LookupCreate(yaml.MappingNode, fieldName)
|
operation = yaml.LookupCreate(yaml.MappingNode, fieldName)
|
||||||
kind = yaml.MappingNode
|
kind = yaml.MappingNode
|
||||||
tag = yaml.NodeTagMap
|
tag = yaml.NodeTagMap
|
||||||
}
|
}
|
||||||
|
|
||||||
// locate (or maybe create) the field
|
// locate (or maybe create) the field
|
||||||
field, err := obj.Pipe(lookupField)
|
field, err := obj.Pipe(operation)
|
||||||
if err != nil || field == nil {
|
if err != nil {
|
||||||
return errors.WrapPrefixf(err, "fieldName: %s", fieldName)
|
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,
|
// if the value exists, but is null and kind is set,
|
||||||
// then change it to the creation type
|
// then change it to the creation type
|
||||||
@@ -114,8 +135,10 @@ func (fltr Filter) field(obj *yaml.RNode) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// seq calls filter on all sequence elements
|
// seq calls filter on all sequence elements
|
||||||
func (fltr Filter) seq(obj *yaml.RNode) error {
|
func (fltr Filter) handleSequence(obj *yaml.RNode) error {
|
||||||
if err := obj.VisitElements(func(node *yaml.RNode) error {
|
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
|
// recurse on each element -- re-allocating a Filter is
|
||||||
// not strictly required, but is more consistent with field
|
// not strictly required, but is more consistent with field
|
||||||
// and less likely to have side effects
|
// and less likely to have side effects
|
||||||
@@ -125,56 +148,35 @@ func (fltr Filter) seq(obj *yaml.RNode) error {
|
|||||||
return errors.WrapPrefixf(err,
|
return errors.WrapPrefixf(err,
|
||||||
"visit traversal on path: %v", fltr.path)
|
"visit traversal on path: %v", fltr.path)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// isSequenceField returns true if the path element is for a sequence field.
|
// isSequenceField returns true if the path element is for a sequence field.
|
||||||
// isSequence also returns the path element with the '[]' suffix trimmed
|
// isSequence also returns the path element with the '[]' suffix trimmed
|
||||||
func isSequenceField(name string) (string, bool) {
|
func isSequenceField(name string) (string, bool) {
|
||||||
isSeq := strings.HasSuffix(name, "[]")
|
shorter := strings.TrimSuffix(name, "[]")
|
||||||
name = strings.TrimSuffix(name, "[]")
|
return shorter, shorter != name
|
||||||
return name, isSeq
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// isMatchGVK returns true if the fs.GVK matches the obj GVK.
|
// isMatchGVK returns true if the fs.GVK matches the obj GVK.
|
||||||
func isMatchGVK(fs types.FieldSpec, obj *yaml.RNode) (bool, error) {
|
func isMatchGVK(fs types.FieldSpec, obj *yaml.RNode) bool {
|
||||||
meta, err := obj.GetMeta()
|
if kind := obj.GetKind(); fs.Kind != "" && fs.Kind != kind {
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
if fs.Kind != "" && fs.Kind != meta.Kind {
|
|
||||||
// kind doesn't match
|
// kind doesn't match
|
||||||
return false, err
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse the group and version from the apiVersion field
|
// parse the group and version from the apiVersion field
|
||||||
group, version := parseGV(meta.APIVersion)
|
group, version := resid.ParseGroupVersion(obj.GetApiVersion())
|
||||||
|
|
||||||
if fs.Group != "" && fs.Group != group {
|
if fs.Group != "" && fs.Group != group {
|
||||||
// group doesn't match
|
// group doesn't match
|
||||||
return false, nil
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if fs.Version != "" && fs.Version != version {
|
if fs.Version != "" && fs.Version != version {
|
||||||
// version doesn't match
|
// version doesn't match
|
||||||
return false, nil
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, nil
|
return true
|
||||||
}
|
|
||||||
|
|
||||||
func splitPath(path string) []string {
|
|
||||||
ps := strings.Split(path, "/")
|
|
||||||
var res []string
|
|
||||||
res = append(res, ps[0])
|
|
||||||
for i := 1; i < len(ps); i++ {
|
|
||||||
lastIndex := len(res) - 1
|
|
||||||
if strings.HasSuffix(res[lastIndex], "\\") {
|
|
||||||
res[lastIndex] = strings.TrimSuffix(res[lastIndex], "\\") + "/" + ps[i]
|
|
||||||
} else {
|
|
||||||
res = append(res, ps[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,206 +15,239 @@ import (
|
|||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TestCase struct {
|
func TestFilter_Filter(t *testing.T) {
|
||||||
name string
|
testCases := map[string]struct {
|
||||||
input string
|
input string
|
||||||
expected string
|
expected string
|
||||||
filter fieldspec.Filter
|
filter fieldspec.Filter
|
||||||
fieldSpec string
|
fieldSpec string
|
||||||
error string
|
error string
|
||||||
}
|
}{
|
||||||
|
"path not found": {
|
||||||
var tests = []TestCase{
|
fieldSpec: `
|
||||||
{
|
|
||||||
name: "update",
|
|
||||||
fieldSpec: `
|
|
||||||
path: a/b
|
path: a/b
|
||||||
group: foo
|
group: foo
|
||||||
kind: Bar
|
kind: Bar
|
||||||
`,
|
`,
|
||||||
input: `
|
input: `
|
||||||
|
apiVersion: foo
|
||||||
|
kind: Bar
|
||||||
|
xxx:
|
||||||
|
`,
|
||||||
|
expected: `
|
||||||
|
apiVersion: foo
|
||||||
|
kind: Bar
|
||||||
|
xxx:
|
||||||
|
`,
|
||||||
|
filter: fieldspec.Filter{
|
||||||
|
SetValue: filtersutil.SetScalar("e"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"empty path": {
|
||||||
|
fieldSpec: `
|
||||||
|
group: foo
|
||||||
|
version: v1
|
||||||
|
kind: Bar
|
||||||
|
`,
|
||||||
|
input: `
|
||||||
|
apiVersion: foo/v1
|
||||||
|
kind: Bar
|
||||||
|
xxx:
|
||||||
|
`,
|
||||||
|
expected: `
|
||||||
|
apiVersion: foo
|
||||||
|
kind: Bar
|
||||||
|
xxx:
|
||||||
|
`,
|
||||||
|
error: `considering field '' of object 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
|
apiVersion: foo/v1beta1
|
||||||
kind: Bar
|
kind: Bar
|
||||||
a:
|
a:
|
||||||
b: c
|
b: c
|
||||||
`,
|
`,
|
||||||
expected: `
|
expected: `
|
||||||
apiVersion: foo/v1beta1
|
apiVersion: foo/v1beta1
|
||||||
kind: Bar
|
kind: Bar
|
||||||
a:
|
a:
|
||||||
b: e
|
b: e
|
||||||
`,
|
`,
|
||||||
filter: fieldspec.Filter{
|
filter: fieldspec.Filter{
|
||||||
SetValue: filtersutil.SetScalar("e"),
|
SetValue: filtersutil.SetScalar("e"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
|
|
||||||
{
|
"update-kind-not-match": {
|
||||||
name: "update-kind-not-match",
|
fieldSpec: `
|
||||||
fieldSpec: `
|
|
||||||
path: a/b
|
path: a/b
|
||||||
group: foo
|
group: foo
|
||||||
kind: Bar1
|
kind: Bar1
|
||||||
`,
|
`,
|
||||||
input: `
|
input: `
|
||||||
apiVersion: foo/v1beta1
|
apiVersion: foo/v1beta1
|
||||||
kind: Bar2
|
kind: Bar2
|
||||||
a:
|
a:
|
||||||
b: c
|
b: c
|
||||||
`,
|
`,
|
||||||
expected: `
|
expected: `
|
||||||
apiVersion: foo/v1beta1
|
apiVersion: foo/v1beta1
|
||||||
kind: Bar2
|
kind: Bar2
|
||||||
a:
|
a:
|
||||||
b: c
|
b: c
|
||||||
`,
|
`,
|
||||||
filter: fieldspec.Filter{
|
filter: fieldspec.Filter{
|
||||||
SetValue: filtersutil.SetScalar("e"),
|
SetValue: filtersutil.SetScalar("e"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
|
|
||||||
{
|
"update-group-not-match": {
|
||||||
name: "update-group-not-match",
|
fieldSpec: `
|
||||||
fieldSpec: `
|
|
||||||
path: a/b
|
path: a/b
|
||||||
group: foo1
|
group: foo1
|
||||||
kind: Bar
|
kind: Bar
|
||||||
`,
|
`,
|
||||||
input: `
|
input: `
|
||||||
apiVersion: foo2/v1beta1
|
apiVersion: foo2/v1beta1
|
||||||
kind: Bar
|
kind: Bar
|
||||||
a:
|
a:
|
||||||
b: c
|
b: c
|
||||||
`,
|
`,
|
||||||
expected: `
|
expected: `
|
||||||
apiVersion: foo2/v1beta1
|
apiVersion: foo2/v1beta1
|
||||||
kind: Bar
|
kind: Bar
|
||||||
a:
|
a:
|
||||||
b: c
|
b: c
|
||||||
`,
|
`,
|
||||||
filter: fieldspec.Filter{
|
filter: fieldspec.Filter{
|
||||||
SetValue: filtersutil.SetScalar("e"),
|
SetValue: filtersutil.SetScalar("e"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
|
|
||||||
{
|
"update-version-not-match": {
|
||||||
name: "update-version-not-match",
|
fieldSpec: `
|
||||||
fieldSpec: `
|
|
||||||
path: a/b
|
path: a/b
|
||||||
group: foo
|
group: foo
|
||||||
version: v1beta1
|
version: v1beta1
|
||||||
kind: Bar
|
kind: Bar
|
||||||
`,
|
`,
|
||||||
input: `
|
input: `
|
||||||
apiVersion: foo/v1beta2
|
apiVersion: foo/v1beta2
|
||||||
kind: Bar
|
kind: Bar
|
||||||
a:
|
a:
|
||||||
b: c
|
b: c
|
||||||
`,
|
`,
|
||||||
expected: `
|
expected: `
|
||||||
apiVersion: foo/v1beta2
|
apiVersion: foo/v1beta2
|
||||||
kind: Bar
|
kind: Bar
|
||||||
a:
|
a:
|
||||||
b: c
|
b: c
|
||||||
`,
|
`,
|
||||||
filter: fieldspec.Filter{
|
filter: fieldspec.Filter{
|
||||||
SetValue: filtersutil.SetScalar("e"),
|
SetValue: filtersutil.SetScalar("e"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
|
|
||||||
{
|
"bad-version": {
|
||||||
name: "bad-version",
|
fieldSpec: `
|
||||||
fieldSpec: `
|
|
||||||
path: a/b
|
path: a/b
|
||||||
group: foo
|
group: foo
|
||||||
version: v1beta1
|
version: v1beta1
|
||||||
kind: Bar
|
kind: Bar
|
||||||
`,
|
`,
|
||||||
input: `
|
input: `
|
||||||
apiVersion: foo/v1beta2/something
|
apiVersion: foo/v1beta2/something
|
||||||
kind: Bar
|
kind: Bar
|
||||||
a:
|
a:
|
||||||
b: c
|
b: c
|
||||||
`,
|
`,
|
||||||
expected: `
|
expected: `
|
||||||
apiVersion: foo/v1beta2/something
|
apiVersion: foo/v1beta2/something
|
||||||
kind: Bar
|
kind: Bar
|
||||||
a:
|
a:
|
||||||
b: c
|
b: c
|
||||||
`,
|
`,
|
||||||
filter: fieldspec.Filter{
|
filter: fieldspec.Filter{
|
||||||
SetValue: filtersutil.SetScalar("e"),
|
SetValue: filtersutil.SetScalar("e"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
|
|
||||||
{
|
"bad-meta": {
|
||||||
name: "bad-meta",
|
fieldSpec: `
|
||||||
fieldSpec: `
|
|
||||||
path: a/b
|
path: a/b
|
||||||
group: foo
|
group: foo
|
||||||
version: v1beta1
|
version: v1beta1
|
||||||
kind: Bar
|
kind: Bar
|
||||||
`,
|
`,
|
||||||
input: `
|
input: `
|
||||||
a:
|
a:
|
||||||
b: c
|
b: c
|
||||||
`,
|
`,
|
||||||
filter: fieldspec.Filter{
|
expected: `
|
||||||
SetValue: filtersutil.SetScalar("e"),
|
a:
|
||||||
|
b: c
|
||||||
|
`,
|
||||||
|
filter: fieldspec.Filter{
|
||||||
|
SetValue: filtersutil.SetScalar("e"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
error: "missing Resource metadata",
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
"miss-match-type": {
|
||||||
name: "miss-match-type",
|
fieldSpec: `
|
||||||
fieldSpec: `
|
|
||||||
path: a/b/c
|
path: a/b/c
|
||||||
kind: Bar
|
kind: Bar
|
||||||
`,
|
`,
|
||||||
input: `
|
input: `
|
||||||
kind: Bar
|
kind: Bar
|
||||||
a:
|
a:
|
||||||
b: a
|
b: a
|
||||||
`,
|
`,
|
||||||
error: "obj 'kind: Bar\na:\n b: a\n' at path 'a/b/c': " +
|
error: `considering field 'a/b/c' of object Bar.[noVer].[noGrp]/[noName].[noNs]: expected sequence or mapping node`,
|
||||||
"expected sequence or mapping node",
|
filter: fieldspec.Filter{
|
||||||
filter: fieldspec.Filter{
|
SetValue: filtersutil.SetScalar("e"),
|
||||||
SetValue: filtersutil.SetScalar("e"),
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
|
|
||||||
{
|
"add": {
|
||||||
name: "add",
|
fieldSpec: `
|
||||||
fieldSpec: `
|
|
||||||
path: a/b/c/d
|
path: a/b/c/d
|
||||||
group: foo
|
group: foo
|
||||||
create: true
|
create: true
|
||||||
kind: Bar
|
kind: Bar
|
||||||
`,
|
`,
|
||||||
input: `
|
input: `
|
||||||
apiVersion: foo/v1beta1
|
apiVersion: foo/v1beta1
|
||||||
kind: Bar
|
kind: Bar
|
||||||
a: {}
|
a: {}
|
||||||
`,
|
`,
|
||||||
expected: `
|
expected: `
|
||||||
apiVersion: foo/v1beta1
|
apiVersion: foo/v1beta1
|
||||||
kind: Bar
|
kind: Bar
|
||||||
a: {b: {c: {d: e}}}
|
a: {b: {c: {d: e}}}
|
||||||
`,
|
`,
|
||||||
filter: fieldspec.Filter{
|
filter: fieldspec.Filter{
|
||||||
SetValue: filtersutil.SetScalar("e"),
|
SetValue: filtersutil.SetScalar("e"),
|
||||||
CreateKind: yaml.ScalarNode,
|
CreateKind: yaml.ScalarNode,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
|
|
||||||
{
|
"update-in-sequence": {
|
||||||
name: "update-in-sequence",
|
fieldSpec: `
|
||||||
fieldSpec: `
|
|
||||||
path: a/b[]/c/d
|
path: a/b[]/c/d
|
||||||
group: foo
|
group: foo
|
||||||
kind: Bar
|
kind: Bar
|
||||||
`,
|
`,
|
||||||
input: `
|
input: `
|
||||||
apiVersion: foo/v1beta1
|
apiVersion: foo/v1beta1
|
||||||
kind: Bar
|
kind: Bar
|
||||||
a:
|
a:
|
||||||
@@ -222,7 +255,7 @@ a:
|
|||||||
- c:
|
- c:
|
||||||
d: a
|
d: a
|
||||||
`,
|
`,
|
||||||
expected: `
|
expected: `
|
||||||
apiVersion: foo/v1beta1
|
apiVersion: foo/v1beta1
|
||||||
kind: Bar
|
kind: Bar
|
||||||
a:
|
a:
|
||||||
@@ -230,244 +263,237 @@ a:
|
|||||||
- c:
|
- c:
|
||||||
d: e
|
d: e
|
||||||
`,
|
`,
|
||||||
filter: fieldspec.Filter{
|
filter: fieldspec.Filter{
|
||||||
SetValue: filtersutil.SetScalar("e"),
|
SetValue: filtersutil.SetScalar("e"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
|
|
||||||
// Don't create a sequence
|
// Don't create a sequence
|
||||||
{
|
"empty-sequence-no-create": {
|
||||||
name: "empty-sequence-no-create",
|
fieldSpec: `
|
||||||
fieldSpec: `
|
|
||||||
path: a/b[]/c/d
|
path: a/b[]/c/d
|
||||||
group: foo
|
group: foo
|
||||||
create: true
|
create: true
|
||||||
kind: Bar
|
kind: Bar
|
||||||
`,
|
`,
|
||||||
input: `
|
input: `
|
||||||
apiVersion: foo/v1beta1
|
apiVersion: foo/v1beta1
|
||||||
kind: Bar
|
kind: Bar
|
||||||
a: {}
|
a: {}
|
||||||
`,
|
`,
|
||||||
expected: `
|
expected: `
|
||||||
apiVersion: foo/v1beta1
|
apiVersion: foo/v1beta1
|
||||||
kind: Bar
|
kind: Bar
|
||||||
a: {}
|
a: {}
|
||||||
`,
|
`,
|
||||||
filter: fieldspec.Filter{
|
filter: fieldspec.Filter{
|
||||||
SetValue: filtersutil.SetScalar("e"),
|
SetValue: filtersutil.SetScalar("e"),
|
||||||
CreateKind: yaml.ScalarNode,
|
CreateKind: yaml.ScalarNode,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
|
|
||||||
// Create a new field for an element in a sequence
|
// Create a new field for an element in a sequence
|
||||||
{
|
"empty-sequence-create": {
|
||||||
name: "empty-sequence-create",
|
fieldSpec: `
|
||||||
fieldSpec: `
|
|
||||||
path: a/b[]/c/d
|
path: a/b[]/c/d
|
||||||
group: foo
|
group: foo
|
||||||
create: true
|
create: true
|
||||||
kind: Bar
|
kind: Bar
|
||||||
`,
|
`,
|
||||||
input: `
|
input: `
|
||||||
apiVersion: foo/v1beta1
|
apiVersion: foo/v1beta1
|
||||||
kind: Bar
|
kind: Bar
|
||||||
a:
|
a:
|
||||||
b:
|
b:
|
||||||
- c: {}
|
- c: {}
|
||||||
`,
|
`,
|
||||||
expected: `
|
expected: `
|
||||||
apiVersion: foo/v1beta1
|
apiVersion: foo/v1beta1
|
||||||
kind: Bar
|
kind: Bar
|
||||||
a:
|
a:
|
||||||
b:
|
b:
|
||||||
- c: {d: e}
|
- c: {d: e}
|
||||||
`,
|
`,
|
||||||
filter: fieldspec.Filter{
|
filter: fieldspec.Filter{
|
||||||
SetValue: filtersutil.SetScalar("e"),
|
SetValue: filtersutil.SetScalar("e"),
|
||||||
CreateKind: yaml.ScalarNode,
|
CreateKind: yaml.ScalarNode,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
|
|
||||||
{
|
"group v1": {
|
||||||
name: "group v1",
|
fieldSpec: `
|
||||||
fieldSpec: `
|
|
||||||
path: a/b
|
path: a/b
|
||||||
group: v1
|
group: v1
|
||||||
create: true
|
create: true
|
||||||
kind: Bar
|
kind: Bar
|
||||||
`,
|
`,
|
||||||
input: `
|
input: `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Bar
|
kind: Bar
|
||||||
`,
|
`,
|
||||||
expected: `
|
expected: `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Bar
|
kind: Bar
|
||||||
`,
|
`,
|
||||||
filter: fieldspec.Filter{
|
filter: fieldspec.Filter{
|
||||||
SetValue: filtersutil.SetScalar("e"),
|
SetValue: filtersutil.SetScalar("e"),
|
||||||
CreateKind: yaml.ScalarNode,
|
CreateKind: yaml.ScalarNode,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
|
|
||||||
{
|
"version v1": {
|
||||||
name: "version v1",
|
fieldSpec: `
|
||||||
fieldSpec: `
|
|
||||||
path: a/b
|
path: a/b
|
||||||
version: v1
|
version: v1
|
||||||
create: true
|
create: true
|
||||||
kind: Bar
|
kind: Bar
|
||||||
`,
|
`,
|
||||||
input: `
|
input: `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Bar
|
kind: Bar
|
||||||
`,
|
`,
|
||||||
expected: `
|
expected: `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Bar
|
kind: Bar
|
||||||
a:
|
a:
|
||||||
b: e
|
b: e
|
||||||
`,
|
`,
|
||||||
filter: fieldspec.Filter{
|
filter: fieldspec.Filter{
|
||||||
SetValue: filtersutil.SetScalar("e"),
|
SetValue: filtersutil.SetScalar("e"),
|
||||||
CreateKind: yaml.ScalarNode,
|
CreateKind: yaml.ScalarNode,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
{
|
"successfully set field on array entry no sequence hint": {
|
||||||
name: "successfully set field on array entry no sequence hint",
|
fieldSpec: `
|
||||||
fieldSpec: `
|
|
||||||
path: spec/containers/image
|
path: spec/containers/image
|
||||||
version: v1
|
version: v1
|
||||||
kind: Bar
|
kind: Bar
|
||||||
`,
|
`,
|
||||||
input: `
|
input: `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Bar
|
kind: Bar
|
||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- image: foo
|
- image: foo
|
||||||
`,
|
`,
|
||||||
expected: `
|
expected: `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Bar
|
kind: Bar
|
||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- image: bar
|
- image: bar
|
||||||
`,
|
`,
|
||||||
filter: fieldspec.Filter{
|
filter: fieldspec.Filter{
|
||||||
SetValue: filtersutil.SetScalar("bar"),
|
SetValue: filtersutil.SetScalar("bar"),
|
||||||
CreateKind: yaml.ScalarNode,
|
CreateKind: yaml.ScalarNode,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
{
|
"successfully set field on array entry with sequence hint": {
|
||||||
name: "successfully set field on array entry with sequence hint",
|
fieldSpec: `
|
||||||
fieldSpec: `
|
|
||||||
path: spec/containers[]/image
|
path: spec/containers[]/image
|
||||||
version: v1
|
version: v1
|
||||||
kind: Bar
|
kind: Bar
|
||||||
`,
|
`,
|
||||||
input: `
|
input: `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Bar
|
kind: Bar
|
||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- image: foo
|
- image: foo
|
||||||
`,
|
`,
|
||||||
expected: `
|
expected: `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Bar
|
kind: Bar
|
||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- image: bar
|
- image: bar
|
||||||
`,
|
`,
|
||||||
filter: fieldspec.Filter{
|
filter: fieldspec.Filter{
|
||||||
SetValue: filtersutil.SetScalar("bar"),
|
SetValue: filtersutil.SetScalar("bar"),
|
||||||
CreateKind: yaml.ScalarNode,
|
CreateKind: yaml.ScalarNode,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
"failure to set field on array entry with sequence hint in path": {
|
||||||
{
|
fieldSpec: `
|
||||||
name: "failure to set field on array entry with sequence hint in path",
|
|
||||||
fieldSpec: `
|
|
||||||
path: spec/containers[]/image
|
path: spec/containers[]/image
|
||||||
version: v1
|
version: v1
|
||||||
kind: Bar
|
kind: Bar
|
||||||
`,
|
`,
|
||||||
input: `
|
input: `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Bar
|
kind: Bar
|
||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
`,
|
`,
|
||||||
expected: `
|
expected: `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Bar
|
kind: Bar
|
||||||
spec:
|
spec:
|
||||||
containers: []
|
containers: []
|
||||||
`,
|
`,
|
||||||
filter: fieldspec.Filter{
|
filter: fieldspec.Filter{
|
||||||
SetValue: filtersutil.SetScalar("bar"),
|
SetValue: filtersutil.SetScalar("bar"),
|
||||||
CreateKind: yaml.ScalarNode,
|
CreateKind: yaml.ScalarNode,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
{
|
"failure to set field on array entry, no sequence hint in path": {
|
||||||
name: "failure to set field on array entry, no sequence hint in path",
|
fieldSpec: `
|
||||||
fieldSpec: `
|
|
||||||
path: spec/containers/image
|
path: spec/containers/image
|
||||||
version: v1
|
version: v1
|
||||||
kind: Bar
|
kind: Bar
|
||||||
`,
|
`,
|
||||||
input: `
|
input: `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Bar
|
kind: Bar
|
||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
`,
|
`,
|
||||||
expected: `
|
expected: `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Bar
|
kind: Bar
|
||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
`,
|
`,
|
||||||
filter: fieldspec.Filter{
|
filter: fieldspec.Filter{
|
||||||
SetValue: filtersutil.SetScalar("bar"),
|
SetValue: filtersutil.SetScalar("bar"),
|
||||||
CreateKind: yaml.ScalarNode,
|
CreateKind: yaml.ScalarNode,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
"fieldname with slash '/'": {
|
||||||
{
|
fieldSpec: `
|
||||||
name: "filedname with slash '/'",
|
|
||||||
fieldSpec: `
|
|
||||||
path: a/b\/c/d
|
path: a/b\/c/d
|
||||||
version: v1
|
version: v1
|
||||||
kind: Bar
|
kind: Bar
|
||||||
`,
|
`,
|
||||||
input: `
|
input: `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Bar
|
kind: Bar
|
||||||
a:
|
a:
|
||||||
b/c:
|
b/c:
|
||||||
d: foo
|
d: foo
|
||||||
`,
|
`,
|
||||||
expected: `
|
expected: `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Bar
|
kind: Bar
|
||||||
a:
|
a:
|
||||||
b/c:
|
b/c:
|
||||||
d: bar
|
d: bar
|
||||||
`,
|
`,
|
||||||
filter: fieldspec.Filter{
|
filter: fieldspec.Filter{
|
||||||
SetValue: filtersutil.SetScalar("bar"),
|
SetValue: filtersutil.SetScalar("bar"),
|
||||||
CreateKind: yaml.ScalarNode,
|
CreateKind: yaml.ScalarNode,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
"fieldname with multiple '/'": {
|
||||||
{
|
fieldSpec: `
|
||||||
name: "filedname with multiple '/'",
|
|
||||||
fieldSpec: `
|
|
||||||
path: a/b\/c/d\/e/f
|
path: a/b\/c/d\/e/f
|
||||||
version: v1
|
version: v1
|
||||||
kind: Bar
|
kind: Bar
|
||||||
`,
|
`,
|
||||||
input: `
|
input: `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Bar
|
kind: Bar
|
||||||
a:
|
a:
|
||||||
@@ -475,7 +501,7 @@ a:
|
|||||||
d/e:
|
d/e:
|
||||||
f: foo
|
f: foo
|
||||||
`,
|
`,
|
||||||
expected: `
|
expected: `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Bar
|
kind: Bar
|
||||||
a:
|
a:
|
||||||
@@ -483,25 +509,24 @@ a:
|
|||||||
d/e:
|
d/e:
|
||||||
f: bar
|
f: bar
|
||||||
`,
|
`,
|
||||||
filter: fieldspec.Filter{
|
filter: fieldspec.Filter{
|
||||||
SetValue: filtersutil.SetScalar("bar"),
|
SetValue: filtersutil.SetScalar("bar"),
|
||||||
CreateKind: yaml.ScalarNode,
|
CreateKind: yaml.ScalarNode,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func TestFilter_Filter(t *testing.T) {
|
for n := range testCases {
|
||||||
for i := range tests {
|
tc := testCases[n]
|
||||||
test := tests[i]
|
t.Run(n, func(t *testing.T) {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
err := yaml.Unmarshal([]byte(tc.fieldSpec), &tc.filter.FieldSpec)
|
||||||
err := yaml.Unmarshal([]byte(test.fieldSpec), &test.filter.FieldSpec)
|
|
||||||
if !assert.NoError(t, err) {
|
if !assert.NoError(t, err) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
out := &bytes.Buffer{}
|
out := &bytes.Buffer{}
|
||||||
rw := &kio.ByteReadWriter{
|
rw := &kio.ByteReadWriter{
|
||||||
Reader: bytes.NewBufferString(test.input),
|
Reader: bytes.NewBufferString(tc.input),
|
||||||
Writer: out,
|
Writer: out,
|
||||||
OmitReaderAnnotations: true,
|
OmitReaderAnnotations: true,
|
||||||
}
|
}
|
||||||
@@ -509,11 +534,11 @@ func TestFilter_Filter(t *testing.T) {
|
|||||||
// run the filter
|
// run the filter
|
||||||
err = kio.Pipeline{
|
err = kio.Pipeline{
|
||||||
Inputs: []kio.Reader{rw},
|
Inputs: []kio.Reader{rw},
|
||||||
Filters: []kio.Filter{kio.FilterAll(test.filter)},
|
Filters: []kio.Filter{kio.FilterAll(tc.filter)},
|
||||||
Outputs: []kio.Writer{rw},
|
Outputs: []kio.Writer{rw},
|
||||||
}.Execute()
|
}.Execute()
|
||||||
if test.error != "" {
|
if tc.error != "" {
|
||||||
if !assert.EqualError(t, err, test.error) {
|
if !assert.EqualError(t, err, tc.error) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
// stop rest of test
|
// stop rest of test
|
||||||
@@ -526,10 +551,92 @@ func TestFilter_Filter(t *testing.T) {
|
|||||||
|
|
||||||
// check results
|
// check results
|
||||||
if !assert.Equal(t,
|
if !assert.Equal(t,
|
||||||
strings.TrimSpace(test.expected),
|
strings.TrimSpace(tc.expected),
|
||||||
strings.TrimSpace(out.String())) {
|
strings.TrimSpace(out.String())) {
|
||||||
t.FailNow()
|
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,49 +0,0 @@
|
|||||||
// Copyright 2019 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
package fieldspec
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/resid"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Return true for 'v' followed by a 1 or 2, and don't look at rest.
|
|
||||||
// I.e. 'v1', 'v1beta1', 'v2', would return true.
|
|
||||||
func looksLikeACoreApiVersion(s string) bool {
|
|
||||||
if len(s) < 2 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if s[0:1] != "v" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return s[1:2] == "1" || s[1:2] == "2"
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseGV parses apiVersion field into group and version.
|
|
||||||
func parseGV(apiVersion string) (group, version string) {
|
|
||||||
// parse the group and version from the apiVersion field
|
|
||||||
parts := strings.SplitN(apiVersion, "/", 2)
|
|
||||||
group = parts[0]
|
|
||||||
if len(parts) > 1 {
|
|
||||||
version = parts[1]
|
|
||||||
}
|
|
||||||
// Special case the original "apiVersion" of what
|
|
||||||
// we now call the "core" (empty) group.
|
|
||||||
if version == "" && looksLikeACoreApiVersion(group) {
|
|
||||||
version = group
|
|
||||||
group = ""
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetGVK parses the metadata into a GVK
|
|
||||||
func GetGVK(meta yaml.ResourceMeta) resid.Gvk {
|
|
||||||
group, version := parseGV(meta.APIVersion)
|
|
||||||
return resid.Gvk{
|
|
||||||
Group: group,
|
|
||||||
Version: version,
|
|
||||||
Kind: meta.Kind,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
// Copyright 2019 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
package fieldspec
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"sigs.k8s.io/kustomize/api/resid"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestParseGV(t *testing.T) {
|
|
||||||
testCases := map[string]struct {
|
|
||||||
input string
|
|
||||||
expectedGroup string
|
|
||||||
expectedVersion string
|
|
||||||
}{
|
|
||||||
"empty": {
|
|
||||||
input: "",
|
|
||||||
expectedGroup: "",
|
|
||||||
expectedVersion: "",
|
|
||||||
},
|
|
||||||
"certSigning": {
|
|
||||||
input: "certificates.k8s.io/v1beta1",
|
|
||||||
expectedGroup: "certificates.k8s.io",
|
|
||||||
expectedVersion: "v1beta1",
|
|
||||||
},
|
|
||||||
"extensions": {
|
|
||||||
input: "extensions/v1beta1",
|
|
||||||
expectedGroup: "extensions",
|
|
||||||
expectedVersion: "v1beta1",
|
|
||||||
},
|
|
||||||
"normal": {
|
|
||||||
input: "apps/v1",
|
|
||||||
expectedGroup: "apps",
|
|
||||||
expectedVersion: "v1",
|
|
||||||
},
|
|
||||||
"justApps": {
|
|
||||||
input: "apps",
|
|
||||||
expectedGroup: "apps",
|
|
||||||
expectedVersion: "",
|
|
||||||
},
|
|
||||||
"coreV1": {
|
|
||||||
input: "v1",
|
|
||||||
expectedGroup: "",
|
|
||||||
expectedVersion: "v1",
|
|
||||||
},
|
|
||||||
"coreV2": {
|
|
||||||
input: "v2",
|
|
||||||
expectedGroup: "",
|
|
||||||
expectedVersion: "v2",
|
|
||||||
},
|
|
||||||
"coreV2Beta1": {
|
|
||||||
input: "v2beta1",
|
|
||||||
expectedGroup: "",
|
|
||||||
expectedVersion: "v2beta1",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for tn, tc := range testCases {
|
|
||||||
t.Run(tn, func(t *testing.T) {
|
|
||||||
group, version := parseGV(tc.input)
|
|
||||||
if !assert.Equal(t, tc.expectedGroup, group) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
if !assert.Equal(t, tc.expectedVersion, version) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetGVK(t *testing.T) {
|
|
||||||
testCases := map[string]struct {
|
|
||||||
input string
|
|
||||||
expected resid.Gvk
|
|
||||||
parseError string
|
|
||||||
metaError string
|
|
||||||
}{
|
|
||||||
"empty": {
|
|
||||||
input: `
|
|
||||||
`,
|
|
||||||
parseError: "EOF",
|
|
||||||
},
|
|
||||||
"junk": {
|
|
||||||
input: `
|
|
||||||
congress: effective
|
|
||||||
`,
|
|
||||||
metaError: "missing Resource metadata",
|
|
||||||
},
|
|
||||||
"normal": {
|
|
||||||
input: `
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
`,
|
|
||||||
expected: resid.Gvk{Group: "apps", Version: "v1", Kind: "Deployment"},
|
|
||||||
},
|
|
||||||
"apiVersionOnlyWithSlash": {
|
|
||||||
input: `
|
|
||||||
apiVersion: apps/v1
|
|
||||||
`,
|
|
||||||
expected: resid.Gvk{Group: "apps", Version: "v1", Kind: ""},
|
|
||||||
},
|
|
||||||
"apiVersionOnlyNoSlash1": {
|
|
||||||
input: `
|
|
||||||
apiVersion: apps
|
|
||||||
`,
|
|
||||||
expected: resid.Gvk{Group: "apps", Version: "", Kind: ""},
|
|
||||||
},
|
|
||||||
"apiVersionOnlyNoSlash2": {
|
|
||||||
input: `
|
|
||||||
apiVersion: v1
|
|
||||||
`,
|
|
||||||
expected: resid.Gvk{Group: "", Version: "v1", Kind: ""},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for tn, tc := range testCases {
|
|
||||||
t.Run(tn, func(t *testing.T) {
|
|
||||||
obj, err := yaml.Parse(tc.input)
|
|
||||||
if len(tc.parseError) != 0 {
|
|
||||||
if err == nil {
|
|
||||||
t.Error("expected parse error")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !strings.Contains(err.Error(), tc.parseError) {
|
|
||||||
t.Errorf("expected parse err '%s', got '%v'", tc.parseError, err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !assert.NoError(t, err) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
meta, err := obj.GetMeta()
|
|
||||||
if len(tc.metaError) != 0 {
|
|
||||||
if err == nil {
|
|
||||||
t.Error("expected meta error")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !strings.Contains(err.Error(), tc.metaError) {
|
|
||||||
t.Errorf("expected meta err '%s', got '%v'", tc.metaError, err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !assert.NoError(t, err) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
gvk := GetGVK(meta)
|
|
||||||
if !assert.Equal(t, tc.expected, gvk) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
package filtersutil_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"sigs.k8s.io/kustomize/api/filters/filtersutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSortedKeys(t *testing.T) {
|
|
||||||
testCases := map[string]struct {
|
|
||||||
input map[string]string
|
|
||||||
expected []string
|
|
||||||
}{
|
|
||||||
"empty": {
|
|
||||||
input: map[string]string{},
|
|
||||||
expected: []string{}},
|
|
||||||
"one": {
|
|
||||||
input: map[string]string{"a": "aaa"},
|
|
||||||
expected: []string{"a"}},
|
|
||||||
"three": {
|
|
||||||
input: map[string]string{"c": "ccc", "b": "bbb", "a": "aaa"},
|
|
||||||
expected: []string{"a", "b", "c"}},
|
|
||||||
}
|
|
||||||
for tn, tc := range testCases {
|
|
||||||
t.Run(tn, func(t *testing.T) {
|
|
||||||
if !assert.Equal(t,
|
|
||||||
filtersutil.SortedMapKeys(tc.input),
|
|
||||||
tc.expected) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// Copyright 2022 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
package filtersutil
|
package filtersutil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -21,9 +24,6 @@ func SetEntry(key, value, tag string) SetFn {
|
|||||||
Value: value,
|
Value: value,
|
||||||
Tag: tag,
|
Tag: tag,
|
||||||
}
|
}
|
||||||
if tag == yaml.NodeTagString && yaml.IsYaml1_1NonString(n) {
|
|
||||||
n.Style = yaml.DoubleQuotedStyle
|
|
||||||
}
|
|
||||||
return func(node *yaml.RNode) error {
|
return func(node *yaml.RNode) error {
|
||||||
return node.PipeE(yaml.FieldSetter{
|
return node.PipeE(yaml.FieldSetter{
|
||||||
Name: key,
|
Name: key,
|
||||||
@@ -31,3 +31,39 @@ func SetEntry(key, value, tag string) SetFn {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TrackableSetter struct {
|
||||||
|
// SetValueCallback will be invoked each time a field is set
|
||||||
|
setValueCallback func(key, value, tag string, node *yaml.RNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMutationTracker registers a callback which will be invoked each time a field is mutated
|
||||||
|
func (s *TrackableSetter) WithMutationTracker(callback func(key, value, tag string, node *yaml.RNode)) {
|
||||||
|
s.setValueCallback = callback
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetScalar returns a SetFn to set a scalar value
|
||||||
|
// if a mutation tracker has been registered, the tracker will be invoked each
|
||||||
|
// time a scalar is set
|
||||||
|
func (s TrackableSetter) SetScalar(value string) SetFn {
|
||||||
|
origSetScalar := SetScalar(value)
|
||||||
|
return func(node *yaml.RNode) error {
|
||||||
|
if s.setValueCallback != nil {
|
||||||
|
s.setValueCallback("", value, "", node)
|
||||||
|
}
|
||||||
|
return origSetScalar(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetEntry returns a SetFn to set an entry in a map
|
||||||
|
// if a mutation tracker has been registered, the tracker will be invoked each
|
||||||
|
// time an entry is set
|
||||||
|
func (s TrackableSetter) SetEntry(key, value, tag string) SetFn {
|
||||||
|
origSetEntry := SetEntry(key, value, tag)
|
||||||
|
return func(node *yaml.RNode) error {
|
||||||
|
if s.setValueCallback != nil {
|
||||||
|
s.setValueCallback(key, value, tag, node)
|
||||||
|
}
|
||||||
|
return origSetEntry(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
6
api/filters/iampolicygenerator/doc.go
Normal file
6
api/filters/iampolicygenerator/doc.go
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
// Copyright 2022 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
// 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 += 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()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,9 +23,17 @@ type Filter struct {
|
|||||||
// FsSlice contains the FieldSpecs to locate an image field,
|
// FsSlice contains the FieldSpecs to locate an image field,
|
||||||
// e.g. Path: "spec/myContainers[]/image"
|
// 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)
|
||||||
@@ -40,8 +48,11 @@ func (f Filter) filter(node *yaml.RNode) (*yaml.RNode, error) {
|
|||||||
return node, nil
|
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
|
||||||
}
|
}
|
||||||
@@ -59,11 +70,3 @@ func (f Filter) isOnDenyList(node *yaml.RNode) bool {
|
|||||||
// https://github.com/kubernetes-sigs/kustomize/issues/890
|
// https://github.com/kubernetes-sigs/kustomize/issues/890
|
||||||
return meta.Kind == `CustomResourceDefinition`
|
return meta.Kind == `CustomResourceDefinition`
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateImageTagFn(imageTag types.Image) filtersutil.SetFn {
|
|
||||||
return func(node *yaml.RNode) error {
|
|
||||||
return node.PipeE(imageTagUpdater{
|
|
||||||
ImageTag: imageTag,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -10,18 +10,22 @@ 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": {
|
"ignore CustomResourceDefinition": {
|
||||||
input: `
|
input: `
|
||||||
apiVersion: apiextensions.k8s.io/v1beta1
|
apiVersion: apiextensions.k8s.io/v1
|
||||||
kind: CustomResourceDefinition
|
kind: CustomResourceDefinition
|
||||||
metadata:
|
metadata:
|
||||||
name: whatever
|
name: whatever
|
||||||
@@ -30,7 +34,7 @@ spec:
|
|||||||
- image: whatever
|
- image: whatever
|
||||||
`,
|
`,
|
||||||
expectedOutput: `
|
expectedOutput: `
|
||||||
apiVersion: apiextensions.k8s.io/v1beta1
|
apiVersion: apiextensions.k8s.io/v1
|
||||||
kind: CustomResourceDefinition
|
kind: CustomResourceDefinition
|
||||||
metadata:
|
metadata:
|
||||||
name: whatever
|
name: whatever
|
||||||
@@ -658,17 +662,237 @@ spec:
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"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"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"image with tag and digest new name": {
|
||||||
|
input: `
|
||||||
|
apiVersion: example.com/v1
|
||||||
|
kind: Foo
|
||||||
|
metadata:
|
||||||
|
name: instance
|
||||||
|
spec:
|
||||||
|
image: nginx:1.2.1@sha256:46d5b90a7f4e9996351ad893a26bcbd27216676ad4d5316088ce351fb2c2c3dd
|
||||||
|
`,
|
||||||
|
expectedOutput: `
|
||||||
|
apiVersion: example.com/v1
|
||||||
|
kind: Foo
|
||||||
|
metadata:
|
||||||
|
name: instance
|
||||||
|
spec:
|
||||||
|
image: apache:1.2.1@sha256:46d5b90a7f4e9996351ad893a26bcbd27216676ad4d5316088ce351fb2c2c3dd
|
||||||
|
`,
|
||||||
|
filter: Filter{
|
||||||
|
ImageTag: types.Image{
|
||||||
|
Name: "nginx",
|
||||||
|
NewName: "apache",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fsSlice: []types.FieldSpec{
|
||||||
|
{
|
||||||
|
Path: "spec/image",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"image with tag and digest new name new tag": {
|
||||||
|
input: `
|
||||||
|
apiVersion: example.com/v1
|
||||||
|
kind: Foo
|
||||||
|
metadata:
|
||||||
|
name: instance
|
||||||
|
spec:
|
||||||
|
image: nginx:1.2.1@sha256:46d5b90a7f4e9996351ad893a26bcbd27216676ad4d5316088ce351fb2c2c3dd
|
||||||
|
`,
|
||||||
|
expectedOutput: `
|
||||||
|
apiVersion: example.com/v1
|
||||||
|
kind: Foo
|
||||||
|
metadata:
|
||||||
|
name: instance
|
||||||
|
spec:
|
||||||
|
image: apache:1.3.0
|
||||||
|
`,
|
||||||
|
filter: Filter{
|
||||||
|
ImageTag: types.Image{
|
||||||
|
Name: "nginx",
|
||||||
|
NewName: "apache",
|
||||||
|
NewTag: "1.3.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fsSlice: []types.FieldSpec{
|
||||||
|
{
|
||||||
|
Path: "spec/image",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"image with tag and digest new name new tag and digest": {
|
||||||
|
input: `
|
||||||
|
apiVersion: example.com/v1
|
||||||
|
kind: Foo
|
||||||
|
metadata:
|
||||||
|
name: instance
|
||||||
|
spec:
|
||||||
|
image: nginx:1.2.1@sha256:46d5b90a7f4e9996351ad893a26bcbd27216676ad4d5316088ce351fb2c2c3dd
|
||||||
|
`,
|
||||||
|
expectedOutput: `
|
||||||
|
apiVersion: example.com/v1
|
||||||
|
kind: Foo
|
||||||
|
metadata:
|
||||||
|
name: instance
|
||||||
|
spec:
|
||||||
|
image: apache:1.3.0@sha256:xyz
|
||||||
|
`,
|
||||||
|
filter: Filter{
|
||||||
|
ImageTag: types.Image{
|
||||||
|
Name: "nginx",
|
||||||
|
NewName: "apache",
|
||||||
|
NewTag: "1.3.0",
|
||||||
|
Digest: "sha256:xyz",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fsSlice: []types.FieldSpec{
|
||||||
|
{
|
||||||
|
Path: "spec/image",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"updateimagesuffix": {
|
||||||
|
input: `
|
||||||
|
group: apps
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: deploysuffix
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- image: redis:6.2.6
|
||||||
|
name: redis
|
||||||
|
`,
|
||||||
|
expectedOutput: `
|
||||||
|
group: apps
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: deploysuffix
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- image: redis:6.2.6-alpine
|
||||||
|
name: redis
|
||||||
|
`,
|
||||||
|
filter: Filter{
|
||||||
|
ImageTag: types.Image{
|
||||||
|
Name: "redis",
|
||||||
|
TagSuffix: "-alpine",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fsSlice: []types.FieldSpec{
|
||||||
|
{
|
||||||
|
Path: "spec/template/spec/containers[]/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,7 +4,9 @@
|
|||||||
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/errors"
|
||||||
"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,28 +76,17 @@ 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
|
||||||
})
|
})
|
||||||
case yaml.SequenceNode:
|
case yaml.SequenceNode:
|
||||||
return node.VisitElements(func(n *yaml.RNode) error {
|
return errors.Wrap(node.VisitElements(f.walk))
|
||||||
return f.walk(n)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
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,8 @@
|
|||||||
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,31 +15,57 @@ 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, digest := image.Split(value)
|
||||||
if u.ImageTag.NewName != "" {
|
if u.ImageTag.NewName != "" {
|
||||||
name = u.ImageTag.NewName
|
name = u.ImageTag.NewName
|
||||||
}
|
}
|
||||||
if u.ImageTag.NewTag != "" {
|
|
||||||
tag = ":" + u.ImageTag.NewTag
|
// overriding tag or digest will replace both original tag and digest values
|
||||||
}
|
switch {
|
||||||
if u.ImageTag.Digest != "" {
|
case u.ImageTag.NewTag != "" && u.ImageTag.Digest != "":
|
||||||
tag = "@" + u.ImageTag.Digest
|
tag = u.ImageTag.NewTag
|
||||||
|
digest = u.ImageTag.Digest
|
||||||
|
case u.ImageTag.NewTag != "":
|
||||||
|
tag = u.ImageTag.NewTag
|
||||||
|
digest = ""
|
||||||
|
case u.ImageTag.Digest != "":
|
||||||
|
tag = ""
|
||||||
|
digest = u.ImageTag.Digest
|
||||||
|
case u.ImageTag.TagSuffix != "":
|
||||||
|
tag += u.ImageTag.TagSuffix
|
||||||
|
digest = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
return rn.Pipe(yaml.FieldSetter{StringValue: name + tag})
|
// build final image name
|
||||||
|
if tag != "" {
|
||||||
|
name += ":" + tag
|
||||||
|
}
|
||||||
|
if digest != "" {
|
||||||
|
name += "@" + digest
|
||||||
|
}
|
||||||
|
|
||||||
|
return u.trackableSetter.SetScalar(name)(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,18 +20,26 @@ 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: filtersutil.SetEntry(
|
SetValue: f.trackableSetter.SetEntry(
|
||||||
k, f.Labels[k], yaml.NodeTagString),
|
k, f.Labels[k], yaml.NodeTagString),
|
||||||
CreateKind: yaml.MappingNode, // Labels are MappingNodes.
|
CreateKind: yaml.MappingNode, // Labels are MappingNodes.
|
||||||
CreateTag: yaml.NodeTagMap,
|
CreateTag: yaml.NodeTagMap,
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// Copyright 2022 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
// Package nameref contains a kio.Filter implementation of the kustomize
|
// Package nameref contains a kio.Filter implementation of the kustomize
|
||||||
// name reference transformer.
|
// name reference transformer.
|
||||||
package nameref
|
package nameref
|
||||||
|
|||||||
@@ -1,107 +1,194 @@
|
|||||||
|
// Copyright 2022 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
package nameref
|
package nameref
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
"sigs.k8s.io/kustomize/api/filters/fieldspec"
|
"sigs.k8s.io/kustomize/api/filters/fieldspec"
|
||||||
"sigs.k8s.io/kustomize/api/filters/filtersutil"
|
|
||||||
"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/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
kyaml_filtersutil "sigs.k8s.io/kustomize/kyaml/filtersutil"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/resid"
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Filter will update the name reference
|
// Filter updates a name references.
|
||||||
type Filter struct {
|
type Filter struct {
|
||||||
FieldSpec types.FieldSpec `json:"fieldSpec,omitempty" yaml:"fieldSpec,omitempty"`
|
// Referrer refers to another resource X by X's name.
|
||||||
Referrer *resource.Resource
|
// E.g. A Deployment can refer to a ConfigMap.
|
||||||
Target resid.Gvk
|
// 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
|
ReferralCandidates resmap.ResMap
|
||||||
isRoleRef bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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) {
|
func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||||
return kio.FilterAll(yaml.FilterFunc(f.run)).Filter(nodes)
|
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) {
|
func (f Filter) run(node *yaml.RNode) (*yaml.RNode, error) {
|
||||||
err := node.PipeE(fieldspec.Filter{
|
if err := f.confirmNodeMatchesReferrer(node); err != nil {
|
||||||
FieldSpec: f.FieldSpec,
|
// sanity check.
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
f.NameFieldToUpdate.Gvk = f.Referrer.GetGvk()
|
||||||
|
if err := node.PipeE(fieldspec.Filter{
|
||||||
|
FieldSpec: f.NameFieldToUpdate,
|
||||||
SetValue: f.set,
|
SetValue: f.set,
|
||||||
})
|
}); err != nil {
|
||||||
return node, err
|
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 {
|
func (f Filter) set(node *yaml.RNode) error {
|
||||||
if yaml.IsMissingOrNull(node) {
|
if yaml.IsMissingOrNull(node) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if strings.HasSuffix(f.FieldSpec.Path, "roleRef/name") {
|
|
||||||
f.isRoleRef = true
|
|
||||||
}
|
|
||||||
switch node.YNode().Kind {
|
switch node.YNode().Kind {
|
||||||
case yaml.ScalarNode:
|
case yaml.ScalarNode:
|
||||||
return f.setScalar(node)
|
return f.setScalar(node)
|
||||||
case yaml.MappingNode:
|
case yaml.MappingNode:
|
||||||
// Kind: ValidatingWebhookConfiguration
|
|
||||||
// FieldSpec is webhooks/clientConfig/service
|
|
||||||
return f.setMapping(node)
|
return f.setMapping(node)
|
||||||
case yaml.SequenceNode:
|
case yaml.SequenceNode:
|
||||||
return f.setSequence(node)
|
return applyFilterToSeq(seqFilter{
|
||||||
|
setScalarFn: f.setScalar,
|
||||||
|
setMappingFn: f.setMapping,
|
||||||
|
}, node)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf(
|
return fmt.Errorf("node must be a scalar, sequence or map")
|
||||||
"node is expected to be either a string or a slice of string or a map of string")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f Filter) setSequence(node *yaml.RNode) error {
|
// This method used when NameFieldToUpdate doesn't lead to
|
||||||
return applyFilterToSeq(seqFilter{
|
// one scalar field (typically called 'name'), but rather
|
||||||
setScalarFn: f.setScalar,
|
// leads to a map field (called anything). In this case we
|
||||||
setMappingFn: f.setMapping,
|
// must complete the field path, looking for both a 'name'
|
||||||
}, node)
|
// 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) setMapping(node *yaml.RNode) error {
|
func (f Filter) filterMapCandidatesByNamespace(
|
||||||
return setNameAndNs(
|
node *yaml.RNode) ([]*resource.Resource, error) {
|
||||||
node,
|
namespaceNode, err := node.Pipe(yaml.FieldMatcher{Name: "namespace"})
|
||||||
f.Referrer,
|
if err != nil {
|
||||||
f.Target,
|
return nil, errors.Wrap(err, "trying to match 'namespace' field")
|
||||||
f.ReferralCandidates,
|
}
|
||||||
f.isRoleRef,
|
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 {
|
func (f Filter) setScalar(node *yaml.RNode) error {
|
||||||
newValue, err := getSimpleNameField(
|
referral, err := f.selectReferral(
|
||||||
node.YNode().Value,
|
node.YNode().Value, f.ReferralCandidates.Resources())
|
||||||
f.Referrer,
|
if err != nil || referral == nil {
|
||||||
f.Target,
|
// Nil referral means nothing to do.
|
||||||
f.ReferralCandidates,
|
|
||||||
f.ReferralCandidates.Resources(),
|
|
||||||
f.isRoleRef,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = filtersutil.SetScalar(newValue)(node)
|
f.recordTheReferral(referral)
|
||||||
if err != nil {
|
if referral.GetName() == node.YNode().Value {
|
||||||
return err
|
// The name has not changed, nothing to do.
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
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
|
// getRoleRefGvk returns a Gvk in the roleRef field. Return error
|
||||||
// if the roleRef, roleRef/apiGroup or roleRef/kind is missing.
|
// if the roleRef, roleRef/apiGroup or roleRef/kind is missing.
|
||||||
func getRoleRefGvk(res json.Marshaler) (*resid.Gvk, error) {
|
func getRoleRefGvk(n *resource.Resource) (*resid.Gvk, error) {
|
||||||
n, err := kyaml_filtersutil.GetRNode(res)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
roleRef, err := n.Pipe(yaml.Lookup("roleRef"))
|
roleRef, err := n.Pipe(yaml.Lookup("roleRef"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -114,14 +201,16 @@ func getRoleRefGvk(res json.Marshaler) (*resid.Gvk, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if apiGroup.IsNil() {
|
if apiGroup.IsNil() {
|
||||||
return nil, fmt.Errorf("apiGroup cannot be found in roleRef %s", roleRef.MustString())
|
return nil, fmt.Errorf(
|
||||||
|
"apiGroup cannot be found in roleRef %s", roleRef.MustString())
|
||||||
}
|
}
|
||||||
kind, err := roleRef.Pipe(yaml.Lookup("kind"))
|
kind, err := roleRef.Pipe(yaml.Lookup("kind"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if kind.IsNil() {
|
if kind.IsNil() {
|
||||||
return nil, fmt.Errorf("kind cannot be found in roleRef %s", roleRef.MustString())
|
return nil, fmt.Errorf(
|
||||||
|
"kind cannot be found in roleRef %s", roleRef.MustString())
|
||||||
}
|
}
|
||||||
return &resid.Gvk{
|
return &resid.Gvk{
|
||||||
Group: apiGroup.YNode().Value,
|
Group: apiGroup.YNode().Value,
|
||||||
@@ -129,169 +218,182 @@ func getRoleRefGvk(res json.Marshaler) (*resid.Gvk, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func filterReferralCandidates(
|
// sieveFunc returns true if the resource argument satisfies some criteria.
|
||||||
referrer *resource.Resource,
|
type sieveFunc func(*resource.Resource) bool
|
||||||
matches []*resource.Resource,
|
|
||||||
target resid.Gvk,
|
// doSieve uses a function to accept or ignore resources from a list.
|
||||||
) []*resource.Resource {
|
// If list is nil, returns immediately.
|
||||||
var ret []*resource.Resource
|
// It's a filter obviously, but that term is overloaded here.
|
||||||
for _, m := range matches {
|
func doSieve(list []*resource.Resource, fn sieveFunc) (s []*resource.Resource) {
|
||||||
// If target kind is not ServiceAccount, we shouldn't consider condidates which
|
for _, r := range list {
|
||||||
// doesn't have same namespace.
|
if fn(r) {
|
||||||
if target.Kind != "ServiceAccount" && m.GetNamespace() != referrer.GetNamespace() {
|
s = append(s, r)
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
if !referrer.PrefixesSuffixesEquals(m) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
ret = append(ret, m)
|
|
||||||
}
|
}
|
||||||
return ret
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// selectReferral picks the referral among a subset of candidates.
|
func acceptAll(r *resource.Resource) bool {
|
||||||
// It returns the current name and namespace of the selected candidate.
|
return true
|
||||||
// Note that the content of the referricalCandidateSubset slice is most of the time
|
}
|
||||||
// identical to the referralCandidates resmap. Still in some cases, such
|
|
||||||
// as ClusterRoleBinding, the subset only contains the resources of a specific
|
func previousNameMatches(name string) sieveFunc {
|
||||||
// namespace.
|
return func(r *resource.Resource) bool {
|
||||||
func selectReferral(
|
for _, id := range r.PrevIds() {
|
||||||
oldName string,
|
if id.Name == name {
|
||||||
referrer *resource.Resource,
|
return true
|
||||||
target resid.Gvk,
|
|
||||||
referralCandidates resmap.ResMap,
|
|
||||||
referralCandidateSubset []*resource.Resource,
|
|
||||||
isRoleRef bool) (string, string, error) {
|
|
||||||
var roleRefGvk *resid.Gvk
|
|
||||||
if isRoleRef {
|
|
||||||
var err error
|
|
||||||
roleRefGvk, err = getRoleRefGvk(referrer)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, res := range referralCandidateSubset {
|
|
||||||
id := res.OrgId()
|
|
||||||
// If the we are processing a roleRef, the apiGroup and Kind in the
|
|
||||||
// roleRef are needed to be considered.
|
|
||||||
if (!isRoleRef || id.IsSelected(roleRefGvk)) &&
|
|
||||||
id.IsSelected(&target) && res.GetOriginalName() == oldName {
|
|
||||||
matches := referralCandidates.GetMatchingResourcesByOriginalId(id.Equals)
|
|
||||||
// If there's more than one match,
|
|
||||||
// filter the matches by prefix and suffix
|
|
||||||
if len(matches) > 1 {
|
|
||||||
filteredMatches := filterReferralCandidates(referrer, matches, target)
|
|
||||||
if len(filteredMatches) > 1 {
|
|
||||||
return "", "", fmt.Errorf(
|
|
||||||
"multiple matches for %s:\n %v",
|
|
||||||
id, getIds(filteredMatches))
|
|
||||||
}
|
|
||||||
// Check is the match the resource we are working on
|
|
||||||
if len(filteredMatches) == 0 || res != filteredMatches[0] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// In the resource, note that it is referenced
|
}
|
||||||
// by the referrer.
|
return false
|
||||||
res.AppendRefBy(referrer.CurId())
|
}
|
||||||
// Return transformed name of the object,
|
}
|
||||||
// complete with prefixes, hashes, etc.
|
|
||||||
return res.GetName(), res.GetNamespace(), nil
|
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
|
||||||
return oldName, "", nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// utility function to replace a simple string by the new name
|
func getIds(rs []*resource.Resource) string {
|
||||||
func getSimpleNameField(
|
|
||||||
oldName string,
|
|
||||||
referrer *resource.Resource,
|
|
||||||
target resid.Gvk,
|
|
||||||
referralCandidates resmap.ResMap,
|
|
||||||
referralCandidateSubset []*resource.Resource,
|
|
||||||
isRoleRef bool) (string, error) {
|
|
||||||
|
|
||||||
newName, _, err := selectReferral(oldName, referrer, target,
|
|
||||||
referralCandidates, referralCandidateSubset, isRoleRef)
|
|
||||||
|
|
||||||
return newName, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func getIds(rs []*resource.Resource) []string {
|
|
||||||
var result []string
|
var result []string
|
||||||
for _, r := range rs {
|
for _, r := range rs {
|
||||||
result = append(result, r.CurId().String()+"\n")
|
result = append(result, r.CurId().String())
|
||||||
}
|
}
|
||||||
return result
|
return strings.Join(result, ", ")
|
||||||
}
|
}
|
||||||
|
|
||||||
// utility function to replace name field within a map RNode
|
func checkEqual(k, a, b string) error {
|
||||||
// and leverage the namespace field.
|
if a != b {
|
||||||
func setNameAndNs(
|
return fmt.Errorf(
|
||||||
in *yaml.RNode,
|
"node-referrerOriginal '%s' mismatch '%s' != '%s'",
|
||||||
referrer *resource.Resource,
|
k, a, b)
|
||||||
target resid.Gvk,
|
}
|
||||||
referralCandidates resmap.ResMap,
|
return nil
|
||||||
isRoleRef bool) error {
|
}
|
||||||
|
|
||||||
if in.YNode().Kind != yaml.MappingNode {
|
func (f Filter) confirmNodeMatchesReferrer(node *yaml.RNode) error {
|
||||||
return fmt.Errorf("expect a mapping node")
|
meta, err := node.GetMeta()
|
||||||
}
|
if err != nil {
|
||||||
|
return err
|
||||||
// Get name field
|
}
|
||||||
nameNode, err := in.Pipe(yaml.FieldMatcher{
|
gvk := f.Referrer.GetGvk()
|
||||||
Name: "name",
|
if err = checkEqual(
|
||||||
})
|
"APIVersion", meta.APIVersion, gvk.ApiVersion()); err != nil {
|
||||||
if err != nil || nameNode == nil {
|
return err
|
||||||
return fmt.Errorf("cannot find field 'name' in node")
|
}
|
||||||
}
|
if err = checkEqual(
|
||||||
|
"Kind", meta.Kind, gvk.Kind); err != nil {
|
||||||
// Get namespace field
|
return err
|
||||||
namespaceNode, err := in.Pipe(yaml.FieldMatcher{
|
}
|
||||||
Name: "namespace",
|
if err = checkEqual(
|
||||||
})
|
"Name", meta.Name, f.Referrer.GetName()); err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
return fmt.Errorf("error when find field 'namespace'")
|
}
|
||||||
}
|
if err = checkEqual(
|
||||||
|
"Namespace", meta.Namespace, f.Referrer.GetNamespace()); err != nil {
|
||||||
// check is namespace matched
|
return err
|
||||||
// name will bot be updated if the namespace doesn't match
|
|
||||||
subset := referralCandidates.Resources()
|
|
||||||
if namespaceNode != nil {
|
|
||||||
namespace := namespaceNode.YNode().Value
|
|
||||||
bynamespace := referralCandidates.GroupedByOriginalNamespace()
|
|
||||||
if _, ok := bynamespace[namespace]; !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
subset = bynamespace[namespace]
|
|
||||||
}
|
|
||||||
|
|
||||||
oldName := nameNode.YNode().Value
|
|
||||||
newname, newnamespace, err := selectReferral(oldName, referrer, target,
|
|
||||||
referralCandidates, subset, isRoleRef)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newname == oldName) && (newnamespace == "") {
|
|
||||||
// no candidate found.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// set name
|
|
||||||
in.Pipe(yaml.FieldSetter{
|
|
||||||
Name: "name",
|
|
||||||
StringValue: 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.
|
|
||||||
in.Pipe(yaml.FieldSetter{
|
|
||||||
Name: "namespace",
|
|
||||||
StringValue: newnamespace,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// Copyright 2022 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
package nameref
|
package nameref
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -5,24 +8,23 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"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"
|
|
||||||
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"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNamerefFilter(t *testing.T) {
|
func TestNamerefFilter(t *testing.T) {
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
input string
|
referrerOriginal string
|
||||||
candidates string
|
candidates string
|
||||||
expected string
|
referrerFinal string
|
||||||
filter Filter
|
filter Filter
|
||||||
originalNames []string
|
originalNames []string
|
||||||
}{
|
}{
|
||||||
"simple scalar": {
|
"simple scalar": {
|
||||||
input: `
|
referrerOriginal: `
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
@@ -41,8 +43,8 @@ kind: NotSecret
|
|||||||
metadata:
|
metadata:
|
||||||
name: newName2
|
name: newName2
|
||||||
`,
|
`,
|
||||||
originalNames: []string{"oldName", ""},
|
originalNames: []string{"oldName", "newName2"},
|
||||||
expected: `
|
referrerFinal: `
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
@@ -51,8 +53,8 @@ ref:
|
|||||||
name: newName
|
name: newName
|
||||||
`,
|
`,
|
||||||
filter: Filter{
|
filter: Filter{
|
||||||
FieldSpec: types.FieldSpec{Path: "ref/name"},
|
NameFieldToUpdate: types.FieldSpec{Path: "ref/name"},
|
||||||
Target: resid.Gvk{
|
ReferralTarget: resid.Gvk{
|
||||||
Group: "apps",
|
Group: "apps",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
Kind: "Secret",
|
Kind: "Secret",
|
||||||
@@ -60,7 +62,7 @@ ref:
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
"sequence": {
|
"sequence": {
|
||||||
input: `
|
referrerOriginal: `
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
@@ -80,8 +82,8 @@ kind: NotSecret
|
|||||||
metadata:
|
metadata:
|
||||||
name: newName2
|
name: newName2
|
||||||
`,
|
`,
|
||||||
originalNames: []string{"oldName1", ""},
|
originalNames: []string{"oldName1", "newName2"},
|
||||||
expected: `
|
referrerFinal: `
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
@@ -91,8 +93,8 @@ seq:
|
|||||||
- oldName2
|
- oldName2
|
||||||
`,
|
`,
|
||||||
filter: Filter{
|
filter: Filter{
|
||||||
FieldSpec: types.FieldSpec{Path: "seq"},
|
NameFieldToUpdate: types.FieldSpec{Path: "seq"},
|
||||||
Target: resid.Gvk{
|
ReferralTarget: resid.Gvk{
|
||||||
Group: "apps",
|
Group: "apps",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
Kind: "Secret",
|
Kind: "Secret",
|
||||||
@@ -100,7 +102,7 @@ seq:
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
"mapping": {
|
"mapping": {
|
||||||
input: `
|
referrerOriginal: `
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
@@ -119,8 +121,8 @@ kind: NotSecret
|
|||||||
metadata:
|
metadata:
|
||||||
name: newName2
|
name: newName2
|
||||||
`,
|
`,
|
||||||
originalNames: []string{"oldName", ""},
|
originalNames: []string{"oldName", "newName2"},
|
||||||
expected: `
|
referrerFinal: `
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
@@ -129,8 +131,8 @@ map:
|
|||||||
name: newName
|
name: newName
|
||||||
`,
|
`,
|
||||||
filter: Filter{
|
filter: Filter{
|
||||||
FieldSpec: types.FieldSpec{Path: "map"},
|
NameFieldToUpdate: types.FieldSpec{Path: "map"},
|
||||||
Target: resid.Gvk{
|
ReferralTarget: resid.Gvk{
|
||||||
Group: "apps",
|
Group: "apps",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
Kind: "Secret",
|
Kind: "Secret",
|
||||||
@@ -138,40 +140,47 @@ map:
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
"mapping with namespace": {
|
"mapping with namespace": {
|
||||||
input: `
|
referrerOriginal: `
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
name: dep
|
name: dep
|
||||||
|
namespace: someNs
|
||||||
map:
|
map:
|
||||||
name: oldName
|
name: oldName
|
||||||
namespace: oldNs
|
namespace: someNs
|
||||||
`,
|
`,
|
||||||
candidates: `
|
candidates: `
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Secret
|
kind: Secret
|
||||||
metadata:
|
metadata:
|
||||||
name: newName
|
name: newName
|
||||||
namespace: oldNs
|
namespace: someNs
|
||||||
---
|
---
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: NotSecret
|
kind: NotSecret
|
||||||
metadata:
|
metadata:
|
||||||
name: newName2
|
name: newName2
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: thirdName
|
||||||
`,
|
`,
|
||||||
originalNames: []string{"oldName", ""},
|
originalNames: []string{"oldName", "oldName", "oldName"},
|
||||||
expected: `
|
referrerFinal: `
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
name: dep
|
name: dep
|
||||||
|
namespace: someNs
|
||||||
map:
|
map:
|
||||||
name: newName
|
name: newName
|
||||||
namespace: oldNs
|
namespace: someNs
|
||||||
`,
|
`,
|
||||||
filter: Filter{
|
filter: Filter{
|
||||||
FieldSpec: types.FieldSpec{Path: "map"},
|
NameFieldToUpdate: types.FieldSpec{Path: "map"},
|
||||||
Target: resid.Gvk{
|
ReferralTarget: resid.Gvk{
|
||||||
Group: "apps",
|
Group: "apps",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
Kind: "Secret",
|
Kind: "Secret",
|
||||||
@@ -179,7 +188,7 @@ map:
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
"null value": {
|
"null value": {
|
||||||
input: `
|
referrerOriginal: `
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
@@ -198,8 +207,8 @@ kind: NotSecret
|
|||||||
metadata:
|
metadata:
|
||||||
name: newName2
|
name: newName2
|
||||||
`,
|
`,
|
||||||
originalNames: []string{"oldName", ""},
|
originalNames: []string{"oldName", "newName2"},
|
||||||
expected: `
|
referrerFinal: `
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
@@ -208,8 +217,8 @@ map:
|
|||||||
name: null
|
name: null
|
||||||
`,
|
`,
|
||||||
filter: Filter{
|
filter: Filter{
|
||||||
FieldSpec: types.FieldSpec{Path: "map"},
|
NameFieldToUpdate: types.FieldSpec{Path: "map"},
|
||||||
Target: resid.Gvk{
|
ReferralTarget: resid.Gvk{
|
||||||
Group: "apps",
|
Group: "apps",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
Kind: "Secret",
|
Kind: "Secret",
|
||||||
@@ -220,14 +229,14 @@ map:
|
|||||||
|
|
||||||
for tn, tc := range testCases {
|
for tn, tc := range testCases {
|
||||||
t.Run(tn, func(t *testing.T) {
|
t.Run(tn, func(t *testing.T) {
|
||||||
factory := resource.NewFactory(kunstruct.NewKunstructuredFactoryImpl())
|
factory := provider.NewDefaultDepProvider().GetResourceFactory()
|
||||||
referrer, err := factory.FromBytes([]byte(tc.input))
|
referrer, err := factory.FromBytes([]byte(tc.referrerOriginal))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
tc.filter.Referrer = referrer
|
tc.filter.Referrer = referrer
|
||||||
|
|
||||||
resMapFactory := resmap.NewFactory(factory, nil)
|
resMapFactory := resmap.NewFactory(factory)
|
||||||
candidatesRes, err := factory.SliceFromBytesWithNames(
|
candidatesRes, err := factory.SliceFromBytesWithNames(
|
||||||
tc.originalNames, []byte(tc.candidates))
|
tc.originalNames, []byte(tc.candidates))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -237,10 +246,10 @@ map:
|
|||||||
candidates := resMapFactory.FromResourceSlice(candidatesRes)
|
candidates := resMapFactory.FromResourceSlice(candidatesRes)
|
||||||
tc.filter.ReferralCandidates = candidates
|
tc.filter.ReferralCandidates = candidates
|
||||||
|
|
||||||
|
result := filtertest_test.RunFilter(t, tc.referrerOriginal, tc.filter)
|
||||||
if !assert.Equal(t,
|
if !assert.Equal(t,
|
||||||
strings.TrimSpace(tc.expected),
|
strings.TrimSpace(tc.referrerFinal),
|
||||||
strings.TrimSpace(
|
strings.TrimSpace(result)) {
|
||||||
filtertest_test.RunFilter(t, tc.input, tc.filter))) {
|
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -249,14 +258,14 @@ map:
|
|||||||
|
|
||||||
func TestNamerefFilterUnhappy(t *testing.T) {
|
func TestNamerefFilterUnhappy(t *testing.T) {
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
input string
|
referrerOriginal string
|
||||||
candidates string
|
candidates string
|
||||||
expected string
|
referrerFinal string
|
||||||
filter Filter
|
filter Filter
|
||||||
originalNames []string
|
originalNames []string
|
||||||
}{
|
}{
|
||||||
"multiple match": {
|
"multiple match": {
|
||||||
input: `
|
referrerOriginal: `
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
@@ -276,10 +285,10 @@ metadata:
|
|||||||
name: newName2
|
name: newName2
|
||||||
`,
|
`,
|
||||||
originalNames: []string{"oldName", "oldName"},
|
originalNames: []string{"oldName", "oldName"},
|
||||||
expected: "",
|
referrerFinal: "",
|
||||||
filter: Filter{
|
filter: Filter{
|
||||||
FieldSpec: types.FieldSpec{Path: "ref/name"},
|
NameFieldToUpdate: types.FieldSpec{Path: "ref/name"},
|
||||||
Target: resid.Gvk{
|
ReferralTarget: resid.Gvk{
|
||||||
Group: "apps",
|
Group: "apps",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
Kind: "Secret",
|
Kind: "Secret",
|
||||||
@@ -287,7 +296,7 @@ metadata:
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
"no name": {
|
"no name": {
|
||||||
input: `
|
referrerOriginal: `
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
@@ -307,10 +316,10 @@ metadata:
|
|||||||
name: newName2
|
name: newName2
|
||||||
`,
|
`,
|
||||||
originalNames: []string{"oldName", "oldName"},
|
originalNames: []string{"oldName", "oldName"},
|
||||||
expected: "",
|
referrerFinal: "",
|
||||||
filter: Filter{
|
filter: Filter{
|
||||||
FieldSpec: types.FieldSpec{Path: "ref"},
|
NameFieldToUpdate: types.FieldSpec{Path: "ref"},
|
||||||
Target: resid.Gvk{
|
ReferralTarget: resid.Gvk{
|
||||||
Group: "apps",
|
Group: "apps",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
Kind: "Secret",
|
Kind: "Secret",
|
||||||
@@ -321,14 +330,14 @@ metadata:
|
|||||||
|
|
||||||
for tn, tc := range testCases {
|
for tn, tc := range testCases {
|
||||||
t.Run(tn, func(t *testing.T) {
|
t.Run(tn, func(t *testing.T) {
|
||||||
factory := resource.NewFactory(kunstruct.NewKunstructuredFactoryImpl())
|
factory := provider.NewDefaultDepProvider().GetResourceFactory()
|
||||||
referrer, err := factory.FromBytes([]byte(tc.input))
|
referrer, err := factory.FromBytes([]byte(tc.referrerOriginal))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
tc.filter.Referrer = referrer
|
tc.filter.Referrer = referrer
|
||||||
|
|
||||||
resMapFactory := resmap.NewFactory(factory, nil)
|
resMapFactory := resmap.NewFactory(factory)
|
||||||
candidatesRes, err := factory.SliceFromBytesWithNames(
|
candidatesRes, err := factory.SliceFromBytesWithNames(
|
||||||
tc.originalNames, []byte(tc.candidates))
|
tc.originalNames, []byte(tc.candidates))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -338,11 +347,11 @@ metadata:
|
|||||||
candidates := resMapFactory.FromResourceSlice(candidatesRes)
|
candidates := resMapFactory.FromResourceSlice(candidatesRes)
|
||||||
tc.filter.ReferralCandidates = candidates
|
tc.filter.ReferralCandidates = candidates
|
||||||
|
|
||||||
_, err = filtertest_test.RunFilterE(t, tc.input, tc.filter)
|
_, err = filtertest_test.RunFilterE(t, tc.referrerOriginal, tc.filter)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("expect an error")
|
t.Fatalf("expect an error")
|
||||||
}
|
}
|
||||||
if tc.expected != "" && !assert.EqualError(t, err, tc.expected) {
|
if tc.referrerFinal != "" && !assert.EqualError(t, err, tc.referrerFinal) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -351,19 +360,19 @@ metadata:
|
|||||||
|
|
||||||
func TestCandidatesWithDifferentPrefixSuffix(t *testing.T) {
|
func TestCandidatesWithDifferentPrefixSuffix(t *testing.T) {
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
input string
|
referrerOriginal string
|
||||||
candidates string
|
candidates string
|
||||||
expected string
|
referrerFinal string
|
||||||
filter Filter
|
filter Filter
|
||||||
originalNames []string
|
originalNames []string
|
||||||
prefix []string
|
prefix []string
|
||||||
suffix []string
|
suffix []string
|
||||||
inputPrefix string
|
inputPrefix string
|
||||||
inputSuffix string
|
inputSuffix string
|
||||||
err bool
|
err bool
|
||||||
}{
|
}{
|
||||||
"prefix match": {
|
"prefix match": {
|
||||||
input: `
|
referrerOriginal: `
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
@@ -387,7 +396,7 @@ metadata:
|
|||||||
suffix: []string{"", "suffix2"},
|
suffix: []string{"", "suffix2"},
|
||||||
inputPrefix: "prefix1",
|
inputPrefix: "prefix1",
|
||||||
inputSuffix: "",
|
inputSuffix: "",
|
||||||
expected: `
|
referrerFinal: `
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
@@ -396,8 +405,8 @@ ref:
|
|||||||
name: newName
|
name: newName
|
||||||
`,
|
`,
|
||||||
filter: Filter{
|
filter: Filter{
|
||||||
FieldSpec: types.FieldSpec{Path: "ref/name"},
|
NameFieldToUpdate: types.FieldSpec{Path: "ref/name"},
|
||||||
Target: resid.Gvk{
|
ReferralTarget: resid.Gvk{
|
||||||
Group: "apps",
|
Group: "apps",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
Kind: "Secret",
|
Kind: "Secret",
|
||||||
@@ -406,7 +415,7 @@ ref:
|
|||||||
err: false,
|
err: false,
|
||||||
},
|
},
|
||||||
"suffix match": {
|
"suffix match": {
|
||||||
input: `
|
referrerOriginal: `
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
@@ -430,7 +439,7 @@ metadata:
|
|||||||
suffix: []string{"suffix1", "suffix2"},
|
suffix: []string{"suffix1", "suffix2"},
|
||||||
inputPrefix: "",
|
inputPrefix: "",
|
||||||
inputSuffix: "suffix1",
|
inputSuffix: "suffix1",
|
||||||
expected: `
|
referrerFinal: `
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
@@ -439,8 +448,8 @@ ref:
|
|||||||
name: newName
|
name: newName
|
||||||
`,
|
`,
|
||||||
filter: Filter{
|
filter: Filter{
|
||||||
FieldSpec: types.FieldSpec{Path: "ref/name"},
|
NameFieldToUpdate: types.FieldSpec{Path: "ref/name"},
|
||||||
Target: resid.Gvk{
|
ReferralTarget: resid.Gvk{
|
||||||
Group: "apps",
|
Group: "apps",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
Kind: "Secret",
|
Kind: "Secret",
|
||||||
@@ -449,7 +458,7 @@ ref:
|
|||||||
err: false,
|
err: false,
|
||||||
},
|
},
|
||||||
"prefix suffix both match": {
|
"prefix suffix both match": {
|
||||||
input: `
|
referrerOriginal: `
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
@@ -473,7 +482,7 @@ metadata:
|
|||||||
suffix: []string{"suffix1", "suffix2"},
|
suffix: []string{"suffix1", "suffix2"},
|
||||||
inputPrefix: "prefix1",
|
inputPrefix: "prefix1",
|
||||||
inputSuffix: "suffix1",
|
inputSuffix: "suffix1",
|
||||||
expected: `
|
referrerFinal: `
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
@@ -482,8 +491,8 @@ ref:
|
|||||||
name: newName
|
name: newName
|
||||||
`,
|
`,
|
||||||
filter: Filter{
|
filter: Filter{
|
||||||
FieldSpec: types.FieldSpec{Path: "ref/name"},
|
NameFieldToUpdate: types.FieldSpec{Path: "ref/name"},
|
||||||
Target: resid.Gvk{
|
ReferralTarget: resid.Gvk{
|
||||||
Group: "apps",
|
Group: "apps",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
Kind: "Secret",
|
Kind: "Secret",
|
||||||
@@ -492,7 +501,7 @@ ref:
|
|||||||
err: false,
|
err: false,
|
||||||
},
|
},
|
||||||
"multiple match: both": {
|
"multiple match: both": {
|
||||||
input: `
|
referrerOriginal: `
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
@@ -516,10 +525,10 @@ metadata:
|
|||||||
suffix: []string{"suffix", "suffix"},
|
suffix: []string{"suffix", "suffix"},
|
||||||
inputPrefix: "prefix",
|
inputPrefix: "prefix",
|
||||||
inputSuffix: "suffix",
|
inputSuffix: "suffix",
|
||||||
expected: "",
|
referrerFinal: "",
|
||||||
filter: Filter{
|
filter: Filter{
|
||||||
FieldSpec: types.FieldSpec{Path: "ref/name"},
|
NameFieldToUpdate: types.FieldSpec{Path: "ref/name"},
|
||||||
Target: resid.Gvk{
|
ReferralTarget: resid.Gvk{
|
||||||
Group: "apps",
|
Group: "apps",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
Kind: "Secret",
|
Kind: "Secret",
|
||||||
@@ -528,7 +537,7 @@ metadata:
|
|||||||
err: true,
|
err: true,
|
||||||
},
|
},
|
||||||
"multiple match: only prefix": {
|
"multiple match: only prefix": {
|
||||||
input: `
|
referrerOriginal: `
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
@@ -552,10 +561,10 @@ metadata:
|
|||||||
suffix: []string{"", ""},
|
suffix: []string{"", ""},
|
||||||
inputPrefix: "prefix",
|
inputPrefix: "prefix",
|
||||||
inputSuffix: "",
|
inputSuffix: "",
|
||||||
expected: "",
|
referrerFinal: "",
|
||||||
filter: Filter{
|
filter: Filter{
|
||||||
FieldSpec: types.FieldSpec{Path: "ref/name"},
|
NameFieldToUpdate: types.FieldSpec{Path: "ref/name"},
|
||||||
Target: resid.Gvk{
|
ReferralTarget: resid.Gvk{
|
||||||
Group: "apps",
|
Group: "apps",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
Kind: "Secret",
|
Kind: "Secret",
|
||||||
@@ -564,7 +573,7 @@ metadata:
|
|||||||
err: true,
|
err: true,
|
||||||
},
|
},
|
||||||
"multiple match: only suffix": {
|
"multiple match: only suffix": {
|
||||||
input: `
|
referrerOriginal: `
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
@@ -588,10 +597,10 @@ metadata:
|
|||||||
suffix: []string{"suffix", "suffix"},
|
suffix: []string{"suffix", "suffix"},
|
||||||
inputPrefix: "",
|
inputPrefix: "",
|
||||||
inputSuffix: "suffix",
|
inputSuffix: "suffix",
|
||||||
expected: "",
|
referrerFinal: "",
|
||||||
filter: Filter{
|
filter: Filter{
|
||||||
FieldSpec: types.FieldSpec{Path: "ref/name"},
|
NameFieldToUpdate: types.FieldSpec{Path: "ref/name"},
|
||||||
Target: resid.Gvk{
|
ReferralTarget: resid.Gvk{
|
||||||
Group: "apps",
|
Group: "apps",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
Kind: "Secret",
|
Kind: "Secret",
|
||||||
@@ -600,7 +609,7 @@ metadata:
|
|||||||
err: true,
|
err: true,
|
||||||
},
|
},
|
||||||
"no match: neither match": {
|
"no match: neither match": {
|
||||||
input: `
|
referrerOriginal: `
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
@@ -624,7 +633,7 @@ metadata:
|
|||||||
suffix: []string{"suffix1", "suffix2"},
|
suffix: []string{"suffix1", "suffix2"},
|
||||||
inputPrefix: "prefix",
|
inputPrefix: "prefix",
|
||||||
inputSuffix: "suffix",
|
inputSuffix: "suffix",
|
||||||
expected: `
|
referrerFinal: `
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
@@ -633,8 +642,8 @@ ref:
|
|||||||
name: oldName
|
name: oldName
|
||||||
`,
|
`,
|
||||||
filter: Filter{
|
filter: Filter{
|
||||||
FieldSpec: types.FieldSpec{Path: "ref/name"},
|
NameFieldToUpdate: types.FieldSpec{Path: "ref/name"},
|
||||||
Target: resid.Gvk{
|
ReferralTarget: resid.Gvk{
|
||||||
Group: "apps",
|
Group: "apps",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
Kind: "Secret",
|
Kind: "Secret",
|
||||||
@@ -643,7 +652,7 @@ ref:
|
|||||||
err: false,
|
err: false,
|
||||||
},
|
},
|
||||||
"no match: prefix doesn't match": {
|
"no match: prefix doesn't match": {
|
||||||
input: `
|
referrerOriginal: `
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
@@ -667,7 +676,7 @@ metadata:
|
|||||||
suffix: []string{"suffix", "suffix"},
|
suffix: []string{"suffix", "suffix"},
|
||||||
inputPrefix: "prefix",
|
inputPrefix: "prefix",
|
||||||
inputSuffix: "suffix",
|
inputSuffix: "suffix",
|
||||||
expected: `
|
referrerFinal: `
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
@@ -676,8 +685,8 @@ ref:
|
|||||||
name: oldName
|
name: oldName
|
||||||
`,
|
`,
|
||||||
filter: Filter{
|
filter: Filter{
|
||||||
FieldSpec: types.FieldSpec{Path: "ref/name"},
|
NameFieldToUpdate: types.FieldSpec{Path: "ref/name"},
|
||||||
Target: resid.Gvk{
|
ReferralTarget: resid.Gvk{
|
||||||
Group: "apps",
|
Group: "apps",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
Kind: "Secret",
|
Kind: "Secret",
|
||||||
@@ -686,7 +695,7 @@ ref:
|
|||||||
err: false,
|
err: false,
|
||||||
},
|
},
|
||||||
"no match: suffix doesn't match": {
|
"no match: suffix doesn't match": {
|
||||||
input: `
|
referrerOriginal: `
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
@@ -710,7 +719,7 @@ metadata:
|
|||||||
suffix: []string{"suffix1", "suffix2"},
|
suffix: []string{"suffix1", "suffix2"},
|
||||||
inputPrefix: "prefix",
|
inputPrefix: "prefix",
|
||||||
inputSuffix: "suffix",
|
inputSuffix: "suffix",
|
||||||
expected: `
|
referrerFinal: `
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
@@ -719,8 +728,8 @@ ref:
|
|||||||
name: oldName
|
name: oldName
|
||||||
`,
|
`,
|
||||||
filter: Filter{
|
filter: Filter{
|
||||||
FieldSpec: types.FieldSpec{Path: "ref/name"},
|
NameFieldToUpdate: types.FieldSpec{Path: "ref/name"},
|
||||||
Target: resid.Gvk{
|
ReferralTarget: resid.Gvk{
|
||||||
Group: "apps",
|
Group: "apps",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
Kind: "Secret",
|
Kind: "Secret",
|
||||||
@@ -732,8 +741,8 @@ ref:
|
|||||||
|
|
||||||
for tn, tc := range testCases {
|
for tn, tc := range testCases {
|
||||||
t.Run(tn, func(t *testing.T) {
|
t.Run(tn, func(t *testing.T) {
|
||||||
factory := resource.NewFactory(kunstruct.NewKunstructuredFactoryImpl())
|
factory := provider.NewDefaultDepProvider().GetResourceFactory()
|
||||||
referrer, err := factory.FromBytes([]byte(tc.input))
|
referrer, err := factory.FromBytes([]byte(tc.referrerOriginal))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -745,7 +754,7 @@ ref:
|
|||||||
}
|
}
|
||||||
tc.filter.Referrer = referrer
|
tc.filter.Referrer = referrer
|
||||||
|
|
||||||
resMapFactory := resmap.NewFactory(factory, nil)
|
resMapFactory := resmap.NewFactory(factory)
|
||||||
candidatesRes, err := factory.SliceFromBytesWithNames(
|
candidatesRes, err := factory.SliceFromBytesWithNames(
|
||||||
tc.originalNames, []byte(tc.candidates))
|
tc.originalNames, []byte(tc.candidates))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -765,13 +774,15 @@ ref:
|
|||||||
|
|
||||||
if !tc.err {
|
if !tc.err {
|
||||||
if !assert.Equal(t,
|
if !assert.Equal(t,
|
||||||
strings.TrimSpace(tc.expected),
|
strings.TrimSpace(tc.referrerFinal),
|
||||||
strings.TrimSpace(
|
strings.TrimSpace(
|
||||||
filtertest_test.RunFilter(t, tc.input, tc.filter))) {
|
filtertest_test.RunFilter(
|
||||||
|
t, tc.referrerOriginal, tc.filter))) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_, err := filtertest_test.RunFilterE(t, tc.input, tc.filter)
|
_, err := filtertest_test.RunFilterE(
|
||||||
|
t, tc.referrerOriginal, tc.filter)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("an error is expected")
|
t.Fatalf("an error is expected")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// Copyright 2022 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
package nameref
|
package nameref
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// Copyright 2022 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
package nameref
|
package nameref
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -4,11 +4,11 @@
|
|||||||
package namespace
|
package namespace
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sigs.k8s.io/kustomize/api/filters/fieldspec"
|
|
||||||
"sigs.k8s.io/kustomize/api/filters/filtersutil"
|
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -18,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)
|
||||||
@@ -44,7 +52,7 @@ 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: filtersutil.SetScalar(ns.Namespace),
|
SetValue: ns.trackableSetter.SetEntry("", ns.Namespace, yaml.NodeTagString),
|
||||||
CreateKind: yaml.ScalarNode, // Namespace is a ScalarNode
|
CreateKind: yaml.ScalarNode, // Namespace is a ScalarNode
|
||||||
CreateTag: yaml.NodeTagString,
|
CreateTag: yaml.NodeTagString,
|
||||||
})
|
})
|
||||||
@@ -54,16 +62,11 @@ 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
|
||||||
@@ -74,16 +77,15 @@ func (ns Filter) hacks(obj *yaml.RNode) error {
|
|||||||
// 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 := fieldspec.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: filtersutil.SetScalar(ns.Namespace),
|
SetValue: ns.trackableSetter.SetEntry("", ns.Namespace, yaml.NodeTagString),
|
||||||
CreateKind: yaml.ScalarNode, // Namespace is a ScalarNode
|
CreateKind: yaml.ScalarNode, // Namespace is a ScalarNode
|
||||||
}
|
}
|
||||||
_, err := f.Filter(obj)
|
_, err := f.Filter(obj)
|
||||||
@@ -104,8 +106,8 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,7 +120,6 @@ func (ns Filter) roleBindingHack(obj *yaml.RNode, meta yaml.ResourceMeta) error
|
|||||||
|
|
||||||
// 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.
|
||||||
@@ -130,11 +131,14 @@ func (ns Filter) roleBindingHack(obj *yaml.RNode, meta yaml.ResourceMeta) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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.SetEntry("", ns.Namespace, yaml.NodeTagString)(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",
|
||||||
@@ -283,29 +286,134 @@ 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",
|
||||||
|
Tag: "!!str",
|
||||||
|
NodePath: []string{"metadata", "namespace"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: "bar",
|
||||||
|
Tag: "!!str",
|
||||||
|
NodePath: []string{"a", "b", "c"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: "bar",
|
||||||
|
Tag: "!!str",
|
||||||
|
NodePath: []string{"metadata", "namespace"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: "bar",
|
||||||
|
Tag: "!!str",
|
||||||
|
NodePath: []string{"namespace"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: "bar",
|
||||||
|
Tag: "!!str",
|
||||||
|
NodePath: []string{"a", "b", "c"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "numeric",
|
||||||
|
input: `
|
||||||
|
apiVersion: example.com/v1
|
||||||
|
kind: Foo
|
||||||
|
metadata:
|
||||||
|
name: instance
|
||||||
|
---
|
||||||
|
apiVersion: example.com/v1
|
||||||
|
kind: Bar
|
||||||
|
metadata:
|
||||||
|
name: instance
|
||||||
|
`,
|
||||||
|
expected: `
|
||||||
|
apiVersion: example.com/v1
|
||||||
|
kind: Foo
|
||||||
|
metadata:
|
||||||
|
name: instance
|
||||||
|
namespace: "01234"
|
||||||
|
---
|
||||||
|
apiVersion: example.com/v1
|
||||||
|
kind: Bar
|
||||||
|
metadata:
|
||||||
|
name: instance
|
||||||
|
namespace: "01234"
|
||||||
|
`,
|
||||||
|
filter: namespace.Filter{Namespace: "01234"},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
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...) //nolint:gocritic
|
||||||
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())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ 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 {
|
||||||
@@ -27,7 +28,9 @@ func (pf Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
|||||||
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// Copyright 2022 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
package patchstrategicmerge
|
package patchstrategicmerge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -15,6 +18,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
|
||||||
@@ -577,6 +675,63 @@ spec:
|
|||||||
ports:
|
ports:
|
||||||
- containerPort: 80
|
- containerPort: 80
|
||||||
- containerPort: 8080
|
- 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
|
||||||
47
api/filters/prefix/example_test.go
Normal file
47
api/filters/prefix/example_test.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
// Copyright 2020 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package prefix_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"sigs.k8s.io/kustomize/api/filters/prefix"
|
||||||
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleFilter() {
|
||||||
|
err := kio.Pipeline{
|
||||||
|
Inputs: []kio.Reader{&kio.ByteReader{Reader: bytes.NewBufferString(`
|
||||||
|
apiVersion: example.com/v1
|
||||||
|
kind: Foo
|
||||||
|
metadata:
|
||||||
|
name: instance
|
||||||
|
---
|
||||||
|
apiVersion: example.com/v1
|
||||||
|
kind: Bar
|
||||||
|
metadata:
|
||||||
|
name: instance
|
||||||
|
`)}},
|
||||||
|
Filters: []kio.Filter{prefix.Filter{
|
||||||
|
Prefix: "baz-", FieldSpec: types.FieldSpec{Path: "metadata/name"}}},
|
||||||
|
Outputs: []kio.Writer{kio.ByteWriter{Writer: os.Stdout}},
|
||||||
|
}.Execute()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// apiVersion: example.com/v1
|
||||||
|
// kind: Foo
|
||||||
|
// metadata:
|
||||||
|
// name: baz-instance
|
||||||
|
// ---
|
||||||
|
// apiVersion: example.com/v1
|
||||||
|
// kind: Bar
|
||||||
|
// metadata:
|
||||||
|
// name: baz-instance
|
||||||
|
}
|
||||||
50
api/filters/prefix/prefix.go
Normal file
50
api/filters/prefix/prefix.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
// Copyright 2019 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package prefix
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"sigs.k8s.io/kustomize/api/filters/fieldspec"
|
||||||
|
"sigs.k8s.io/kustomize/api/filters/filtersutil"
|
||||||
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Filter applies resource name prefix's using the fieldSpecs
|
||||||
|
type Filter struct {
|
||||||
|
Prefix string `json:"prefix,omitempty" yaml:"prefix,omitempty"`
|
||||||
|
|
||||||
|
FieldSpec types.FieldSpec `json:"fieldSpec,omitempty" yaml:"fieldSpec,omitempty"`
|
||||||
|
|
||||||
|
trackableSetter filtersutil.TrackableSetter
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ kio.Filter = Filter{}
|
||||||
|
var _ kio.TrackableFilter = &Filter{}
|
||||||
|
|
||||||
|
// WithMutationTracker registers a callback which will be invoked each time a field is mutated
|
||||||
|
func (f *Filter) WithMutationTracker(callback func(key, value, tag string, node *yaml.RNode)) {
|
||||||
|
f.trackableSetter.WithMutationTracker(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||||
|
return kio.FilterAll(yaml.FilterFunc(f.run)).Filter(nodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Filter) run(node *yaml.RNode) (*yaml.RNode, error) {
|
||||||
|
err := node.PipeE(fieldspec.Filter{
|
||||||
|
FieldSpec: f.FieldSpec,
|
||||||
|
SetValue: f.evaluateField,
|
||||||
|
CreateKind: yaml.ScalarNode, // Name is a ScalarNode
|
||||||
|
CreateTag: yaml.NodeTagString,
|
||||||
|
})
|
||||||
|
return node, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Filter) evaluateField(node *yaml.RNode) error {
|
||||||
|
return f.trackableSetter.SetScalar(fmt.Sprintf(
|
||||||
|
"%s%s", f.Prefix, node.YNode().Value))(node)
|
||||||
|
}
|
||||||
154
api/filters/prefix/prefix_test.go
Normal file
154
api/filters/prefix/prefix_test.go
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
// Copyright 2019 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package prefix_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"sigs.k8s.io/kustomize/api/filters/prefix"
|
||||||
|
filtertest_test "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
||||||
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
var mutationTrackerStub = filtertest_test.MutationTrackerStub{}
|
||||||
|
|
||||||
|
var tests = map[string]TestCase{
|
||||||
|
"prefix": {
|
||||||
|
input: `
|
||||||
|
apiVersion: example.com/v1
|
||||||
|
kind: Foo
|
||||||
|
metadata:
|
||||||
|
name: instance
|
||||||
|
---
|
||||||
|
apiVersion: example.com/v1
|
||||||
|
kind: Bar
|
||||||
|
metadata:
|
||||||
|
name: instance
|
||||||
|
`,
|
||||||
|
expected: `
|
||||||
|
apiVersion: example.com/v1
|
||||||
|
kind: Foo
|
||||||
|
metadata:
|
||||||
|
name: foo-instance
|
||||||
|
---
|
||||||
|
apiVersion: example.com/v1
|
||||||
|
kind: Bar
|
||||||
|
metadata:
|
||||||
|
name: foo-instance
|
||||||
|
`,
|
||||||
|
filter: prefix.Filter{
|
||||||
|
Prefix: "foo-",
|
||||||
|
FieldSpec: types.FieldSpec{Path: "metadata/name"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"data-fieldspecs": {
|
||||||
|
input: `
|
||||||
|
apiVersion: example.com/v1
|
||||||
|
kind: Foo
|
||||||
|
metadata:
|
||||||
|
name: instance
|
||||||
|
a:
|
||||||
|
b:
|
||||||
|
c: d
|
||||||
|
---
|
||||||
|
apiVersion: example.com/v1
|
||||||
|
kind: Bar
|
||||||
|
metadata:
|
||||||
|
name: instance
|
||||||
|
a:
|
||||||
|
b:
|
||||||
|
c: d
|
||||||
|
`,
|
||||||
|
expected: `
|
||||||
|
apiVersion: example.com/v1
|
||||||
|
kind: Foo
|
||||||
|
metadata:
|
||||||
|
name: instance
|
||||||
|
a:
|
||||||
|
b:
|
||||||
|
c: foo-d
|
||||||
|
---
|
||||||
|
apiVersion: example.com/v1
|
||||||
|
kind: Bar
|
||||||
|
metadata:
|
||||||
|
name: instance
|
||||||
|
a:
|
||||||
|
b:
|
||||||
|
c: foo-d
|
||||||
|
`,
|
||||||
|
filter: prefix.Filter{
|
||||||
|
Prefix: "foo-",
|
||||||
|
FieldSpec: types.FieldSpec{Path: "a/b/c"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"mutation tracker": {
|
||||||
|
input: `
|
||||||
|
apiVersion: example.com/v1
|
||||||
|
kind: Foo
|
||||||
|
metadata:
|
||||||
|
name: instance
|
||||||
|
---
|
||||||
|
apiVersion: example.com/v1
|
||||||
|
kind: Bar
|
||||||
|
metadata:
|
||||||
|
name: instance
|
||||||
|
`,
|
||||||
|
expected: `
|
||||||
|
apiVersion: example.com/v1
|
||||||
|
kind: Foo
|
||||||
|
metadata:
|
||||||
|
name: foo-instance
|
||||||
|
---
|
||||||
|
apiVersion: example.com/v1
|
||||||
|
kind: Bar
|
||||||
|
metadata:
|
||||||
|
name: foo-instance
|
||||||
|
`,
|
||||||
|
filter: prefix.Filter{
|
||||||
|
Prefix: "foo-",
|
||||||
|
FieldSpec: types.FieldSpec{Path: "metadata/name"},
|
||||||
|
},
|
||||||
|
mutationTracker: mutationTrackerStub.MutationTracker,
|
||||||
|
expectedSetValueArgs: []filtertest_test.SetValueArg{
|
||||||
|
{
|
||||||
|
Value: "foo-instance",
|
||||||
|
NodePath: []string{"metadata", "name"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: "foo-instance",
|
||||||
|
NodePath: []string{"metadata", "name"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestCase struct {
|
||||||
|
input string
|
||||||
|
expected string
|
||||||
|
filter prefix.Filter
|
||||||
|
mutationTracker func(key, value, tag string, node *yaml.RNode)
|
||||||
|
expectedSetValueArgs []filtertest_test.SetValueArg
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilter(t *testing.T) {
|
||||||
|
for name := range tests {
|
||||||
|
mutationTrackerStub.Reset()
|
||||||
|
test := tests[name]
|
||||||
|
test.filter.WithMutationTracker(test.mutationTracker)
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
if !assert.Equal(t,
|
||||||
|
strings.TrimSpace(test.expected),
|
||||||
|
strings.TrimSpace(
|
||||||
|
filtertest_test.RunFilter(t, test.input, test.filter))) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
assert.Equal(t, test.expectedSetValueArgs, mutationTrackerStub.SetValueArgs())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,47 +0,0 @@
|
|||||||
// Copyright 2020 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package prefixsuffix_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/filters/prefixsuffix"
|
|
||||||
"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{prefixsuffix.Filter{
|
|
||||||
Prefix: "baz-", FieldSpec: types.FieldSpec{Path: "metadata/name"}}},
|
|
||||||
Outputs: []kio.Writer{kio.ByteWriter{Writer: os.Stdout}},
|
|
||||||
}.Execute()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// apiVersion: example.com/v1
|
|
||||||
// kind: Foo
|
|
||||||
// metadata:
|
|
||||||
// name: baz-instance
|
|
||||||
// ---
|
|
||||||
// apiVersion: example.com/v1
|
|
||||||
// kind: Bar
|
|
||||||
// metadata:
|
|
||||||
// name: baz-instance
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
// Copyright 2019 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package prefixsuffix
|
|
||||||
|
|
||||||
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 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"`
|
|
||||||
|
|
||||||
FieldSpec types.FieldSpec `json:"fieldSpec,omitempty" yaml:"fieldSpec,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ kio.Filter = Filter{}
|
|
||||||
|
|
||||||
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 filtersutil.SetScalar(fmt.Sprintf(
|
|
||||||
"%s%s%s", f.Prefix, node.YNode().Value, f.Suffix))(node)
|
|
||||||
}
|
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
// Copyright 2019 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package prefixsuffix_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"sigs.k8s.io/kustomize/api/filters/prefixsuffix"
|
|
||||||
filtertest_test "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
var tests = map[string]TestCase{
|
|
||||||
"prefix": {
|
|
||||||
input: `
|
|
||||||
apiVersion: example.com/v1
|
|
||||||
kind: Foo
|
|
||||||
metadata:
|
|
||||||
name: instance
|
|
||||||
---
|
|
||||||
apiVersion: example.com/v1
|
|
||||||
kind: Bar
|
|
||||||
metadata:
|
|
||||||
name: instance
|
|
||||||
`,
|
|
||||||
expected: `
|
|
||||||
apiVersion: example.com/v1
|
|
||||||
kind: Foo
|
|
||||||
metadata:
|
|
||||||
name: foo-instance
|
|
||||||
---
|
|
||||||
apiVersion: example.com/v1
|
|
||||||
kind: Bar
|
|
||||||
metadata:
|
|
||||||
name: foo-instance
|
|
||||||
`,
|
|
||||||
filter: prefixsuffix.Filter{Prefix: "foo-"},
|
|
||||||
fs: types.FieldSpec{Path: "metadata/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"},
|
|
||||||
fs: types.FieldSpec{Path: "metadata/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"},
|
|
||||||
fs: types.FieldSpec{Path: "metadata/name"},
|
|
||||||
},
|
|
||||||
|
|
||||||
"data-fieldspecs": {
|
|
||||||
input: `
|
|
||||||
apiVersion: example.com/v1
|
|
||||||
kind: Foo
|
|
||||||
metadata:
|
|
||||||
name: instance
|
|
||||||
a:
|
|
||||||
b:
|
|
||||||
c: d
|
|
||||||
---
|
|
||||||
apiVersion: example.com/v1
|
|
||||||
kind: Bar
|
|
||||||
metadata:
|
|
||||||
name: instance
|
|
||||||
a:
|
|
||||||
b:
|
|
||||||
c: d
|
|
||||||
`,
|
|
||||||
expected: `
|
|
||||||
apiVersion: example.com/v1
|
|
||||||
kind: Foo
|
|
||||||
metadata:
|
|
||||||
name: instance
|
|
||||||
a:
|
|
||||||
b:
|
|
||||||
c: foo-d
|
|
||||||
---
|
|
||||||
apiVersion: example.com/v1
|
|
||||||
kind: Bar
|
|
||||||
metadata:
|
|
||||||
name: instance
|
|
||||||
a:
|
|
||||||
b:
|
|
||||||
c: foo-d
|
|
||||||
`,
|
|
||||||
filter: prefixsuffix.Filter{Prefix: "foo-"},
|
|
||||||
fs: types.FieldSpec{Path: "a/b/c"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
type TestCase struct {
|
|
||||||
input string
|
|
||||||
expected string
|
|
||||||
filter prefixsuffix.Filter
|
|
||||||
fs types.FieldSpec
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFilter(t *testing.T) {
|
|
||||||
for name := range tests {
|
|
||||||
test := tests[name]
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
test.filter.FieldSpec = test.fs
|
|
||||||
if !assert.Equal(t,
|
|
||||||
strings.TrimSpace(test.expected),
|
|
||||||
strings.TrimSpace(
|
|
||||||
filtertest_test.RunFilter(t, test.input, test.filter))) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// Copyright 2022 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
// Package refvar contains a kio.Filter implementation of the kustomize
|
// Package refvar contains a kio.Filter implementation of the kustomize
|
||||||
// refvar transformer.
|
// refvar transformer (find and replace $(FOO) style variables in strings).
|
||||||
package refvar
|
package refvar
|
||||||
|
|||||||
147
api/filters/refvar/expand.go
Normal file
147
api/filters/refvar/expand.go
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
// Copyright 2019 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package refvar
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
operator = '$'
|
||||||
|
referenceOpener = '('
|
||||||
|
referenceCloser = ')'
|
||||||
|
)
|
||||||
|
|
||||||
|
// syntaxWrap returns the input string wrapped by the expansion syntax.
|
||||||
|
func syntaxWrap(input string) string {
|
||||||
|
var sb strings.Builder
|
||||||
|
sb.WriteByte(operator)
|
||||||
|
sb.WriteByte(referenceOpener)
|
||||||
|
sb.WriteString(input)
|
||||||
|
sb.WriteByte(referenceCloser)
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MappingFunc maps a string to anything.
|
||||||
|
type MappingFunc func(string) interface{}
|
||||||
|
|
||||||
|
// MakePrimitiveReplacer returns a MappingFunc that uses a map to do
|
||||||
|
// replacements, and a histogram to count map hits.
|
||||||
|
//
|
||||||
|
// Func behavior:
|
||||||
|
//
|
||||||
|
// If the input key is NOT found in the map, the key is wrapped up as
|
||||||
|
// as a variable declaration string and returned, e.g. key FOO becomes $(FOO).
|
||||||
|
// This string is presumably put back where it was found, and might get replaced
|
||||||
|
// later.
|
||||||
|
//
|
||||||
|
// If the key is found in the map, the value is returned if it is a primitive
|
||||||
|
// type (string, bool, number), and the hit is counted.
|
||||||
|
//
|
||||||
|
// If it's not a primitive type (e.g. a map, struct, func, etc.) then this
|
||||||
|
// function doesn't know what to do with it and it returns the key wrapped up
|
||||||
|
// again as if it had not been replaced. This should probably be an error.
|
||||||
|
func MakePrimitiveReplacer(
|
||||||
|
counts map[string]int, someMap map[string]interface{}) MappingFunc {
|
||||||
|
return func(key string) interface{} {
|
||||||
|
if value, ok := someMap[key]; ok {
|
||||||
|
switch typedV := value.(type) {
|
||||||
|
case string, int, int32, int64, float32, float64, bool:
|
||||||
|
counts[key]++
|
||||||
|
return typedV
|
||||||
|
default:
|
||||||
|
// If the value is some complicated type (e.g. a map or struct),
|
||||||
|
// this function doesn't know how to jam it into a string,
|
||||||
|
// so just pretend it was a cache miss.
|
||||||
|
// Likely this should be an error instead of a silent failure,
|
||||||
|
// since the programmer passed an impossible value.
|
||||||
|
log.Printf(
|
||||||
|
"MakePrimitiveReplacer: bad replacement type=%T val=%v",
|
||||||
|
typedV, typedV)
|
||||||
|
return syntaxWrap(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If unable to return the mapped variable, return it
|
||||||
|
// as it was found, and a later mapping might be able to
|
||||||
|
// replace it.
|
||||||
|
return syntaxWrap(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DoReplacements replaces variable references in the input string
|
||||||
|
// using the mapping function.
|
||||||
|
func DoReplacements(input string, mapping MappingFunc) interface{} {
|
||||||
|
var buf strings.Builder
|
||||||
|
checkpoint := 0
|
||||||
|
for cursor := 0; cursor < len(input); cursor++ {
|
||||||
|
if input[cursor] == operator && cursor+1 < len(input) {
|
||||||
|
// Copy the portion of the input string since the last
|
||||||
|
// checkpoint into the buffer
|
||||||
|
buf.WriteString(input[checkpoint:cursor])
|
||||||
|
|
||||||
|
// Attempt to read the variable name as defined by the
|
||||||
|
// syntax from the input string
|
||||||
|
read, isVar, advance := tryReadVariableName(input[cursor+1:])
|
||||||
|
|
||||||
|
if isVar {
|
||||||
|
// We were able to read a variable name correctly;
|
||||||
|
// apply the mapping to the variable name and copy the
|
||||||
|
// bytes into the buffer
|
||||||
|
mapped := mapping(read)
|
||||||
|
if input == syntaxWrap(read) {
|
||||||
|
// Preserve the type of variable
|
||||||
|
return mapped
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variable is used in a middle of a string
|
||||||
|
buf.WriteString(fmt.Sprintf("%v", mapped))
|
||||||
|
} else {
|
||||||
|
// Not a variable name; copy the read bytes into the buffer
|
||||||
|
buf.WriteString(read)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Advance the cursor in the input string to account for
|
||||||
|
// bytes consumed to read the variable name expression
|
||||||
|
cursor += advance
|
||||||
|
|
||||||
|
// Advance the checkpoint in the input string
|
||||||
|
checkpoint = cursor + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the buffer and any remaining unwritten bytes in the
|
||||||
|
// input string.
|
||||||
|
return buf.String() + input[checkpoint:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// tryReadVariableName attempts to read a variable name from the input
|
||||||
|
// string and returns the content read from the input, whether that content
|
||||||
|
// represents a variable name to perform mapping on, and the number of bytes
|
||||||
|
// consumed in the input string.
|
||||||
|
//
|
||||||
|
// The input string is assumed not to contain the initial operator.
|
||||||
|
func tryReadVariableName(input string) (string, bool, int) {
|
||||||
|
switch input[0] {
|
||||||
|
case operator:
|
||||||
|
// Escaped operator; return it.
|
||||||
|
return input[0:1], false, 1
|
||||||
|
case referenceOpener:
|
||||||
|
// Scan to expression closer
|
||||||
|
for i := 1; i < len(input); i++ {
|
||||||
|
if input[i] == referenceCloser {
|
||||||
|
return input[1:i], true, i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Incomplete reference; return it.
|
||||||
|
return string(operator) + string(referenceOpener), false, 1
|
||||||
|
default:
|
||||||
|
// Not the beginning of an expression, ie, an operator
|
||||||
|
// that doesn't begin an expression. Return the operator
|
||||||
|
// and the first rune in the string.
|
||||||
|
return string(operator) + string(input[0]), false, 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,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",
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// Copyright 2022 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
package refvar
|
package refvar
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -8,15 +11,13 @@ import (
|
|||||||
"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"
|
||||||
|
|
||||||
expansion2 "sigs.k8s.io/kustomize/api/internal/accumulator/expansion"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Filter updates $(VAR) style variables with values.
|
// Filter updates $(VAR) style variables with values.
|
||||||
// The fieldSpecs are the places to look for occurrences of $(VAR).
|
// The fieldSpecs are the places to look for occurrences of $(VAR).
|
||||||
type Filter struct {
|
type Filter struct {
|
||||||
MappingFunc func(string) interface{} `json:"mappingFunc,omitempty" yaml:"mappingFunc,omitempty"`
|
MappingFunc MappingFunc `json:"mappingFunc,omitempty" yaml:"mappingFunc,omitempty"`
|
||||||
FieldSpec types.FieldSpec `json:"fieldSpec,omitempty" yaml:"fieldSpec,omitempty"`
|
FieldSpec types.FieldSpec `json:"fieldSpec,omitempty" yaml:"fieldSpec,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||||
@@ -49,12 +50,21 @@ func (f Filter) set(node *yaml.RNode) error {
|
|||||||
|
|
||||||
func updateNodeValue(node *yaml.Node, newValue interface{}) {
|
func updateNodeValue(node *yaml.Node, newValue interface{}) {
|
||||||
switch newValue := newValue.(type) {
|
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:
|
case int64:
|
||||||
node.Value = strconv.FormatInt(newValue, 10)
|
node.Value = strconv.FormatInt(newValue, 10)
|
||||||
node.Tag = yaml.NodeTagInt
|
node.Tag = yaml.NodeTagInt
|
||||||
case bool:
|
case bool:
|
||||||
node.SetString(strconv.FormatBool(newValue))
|
node.SetString(strconv.FormatBool(newValue))
|
||||||
node.Tag = yaml.NodeTagBool
|
node.Tag = yaml.NodeTagBool
|
||||||
|
case float32:
|
||||||
|
node.SetString(strconv.FormatFloat(float64(newValue), 'f', -1, 32))
|
||||||
|
node.Tag = yaml.NodeTagFloat
|
||||||
case float64:
|
case float64:
|
||||||
node.SetString(strconv.FormatFloat(newValue, 'f', -1, 64))
|
node.SetString(strconv.FormatFloat(newValue, 'f', -1, 64))
|
||||||
node.Tag = yaml.NodeTagFloat
|
node.Tag = yaml.NodeTagFloat
|
||||||
@@ -69,7 +79,7 @@ func (f Filter) setScalar(node *yaml.RNode) error {
|
|||||||
if !yaml.IsYNodeString(node.YNode()) {
|
if !yaml.IsYNodeString(node.YNode()) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
v := expansion2.Expand(node.YNode().Value, f.MappingFunc)
|
v := DoReplacements(node.YNode().Value, f.MappingFunc)
|
||||||
updateNodeValue(node.YNode(), v)
|
updateNodeValue(node.YNode(), v)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -78,12 +88,14 @@ func (f Filter) setMap(node *yaml.RNode) error {
|
|||||||
contents := node.YNode().Content
|
contents := node.YNode().Content
|
||||||
for i := 0; i < len(contents); i += 2 {
|
for i := 0; i < len(contents); i += 2 {
|
||||||
if !yaml.IsYNodeString(contents[i]) {
|
if !yaml.IsYNodeString(contents[i]) {
|
||||||
return fmt.Errorf("invalid map key: %s, type: %s", contents[i].Value, contents[i].Tag)
|
return fmt.Errorf(
|
||||||
|
"invalid map key: value='%s', tag='%s'",
|
||||||
|
contents[i].Value, contents[i].Tag)
|
||||||
}
|
}
|
||||||
if !yaml.IsYNodeString(contents[i+1]) {
|
if !yaml.IsYNodeString(contents[i+1]) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
newValue := expansion2.Expand(contents[i+1].Value, f.MappingFunc)
|
newValue := DoReplacements(contents[i+1].Value, f.MappingFunc)
|
||||||
updateNodeValue(contents[i+1], newValue)
|
updateNodeValue(contents[i+1], newValue)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -94,7 +106,7 @@ func (f Filter) setSeq(node *yaml.RNode) error {
|
|||||||
if !yaml.IsYNodeString(item) {
|
if !yaml.IsYNodeString(item) {
|
||||||
return fmt.Errorf("invalid value type expect a string")
|
return fmt.Errorf("invalid value type expect a string")
|
||||||
}
|
}
|
||||||
newValue := expansion2.Expand(item.Value, f.MappingFunc)
|
newValue := DoReplacements(item.Value, f.MappingFunc)
|
||||||
updateNodeValue(item, newValue)
|
updateNodeValue(item, newValue)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -1,19 +1,25 @@
|
|||||||
package refvar
|
// Copyright 2022 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package refvar_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
expansion2 "sigs.k8s.io/kustomize/api/internal/accumulator/expansion"
|
. "sigs.k8s.io/kustomize/api/filters/refvar"
|
||||||
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"
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFilter(t *testing.T) {
|
var makeMf = func(theMap map[string]interface{}) MappingFunc {
|
||||||
replacementCounts := make(map[string]int)
|
ignored := make(map[string]int)
|
||||||
|
return MakePrimitiveReplacer(ignored, theMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilter(t *testing.T) {
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
input string
|
input string
|
||||||
expected string
|
expected string
|
||||||
@@ -35,7 +41,7 @@ metadata:
|
|||||||
spec:
|
spec:
|
||||||
replicas: 5`,
|
replicas: 5`,
|
||||||
filter: Filter{
|
filter: Filter{
|
||||||
MappingFunc: expansion2.MappingFuncFor(replacementCounts, map[string]interface{}{
|
MappingFunc: makeMf(map[string]interface{}{
|
||||||
"VAR": int64(5),
|
"VAR": int64(5),
|
||||||
}),
|
}),
|
||||||
FieldSpec: types.FieldSpec{Path: "spec/replicas"},
|
FieldSpec: types.FieldSpec{Path: "spec/replicas"},
|
||||||
@@ -57,7 +63,7 @@ metadata:
|
|||||||
spec:
|
spec:
|
||||||
replicas: 1`,
|
replicas: 1`,
|
||||||
filter: Filter{
|
filter: Filter{
|
||||||
MappingFunc: expansion2.MappingFuncFor(replacementCounts, map[string]interface{}{
|
MappingFunc: makeMf(map[string]interface{}{
|
||||||
"VAR": int64(5),
|
"VAR": int64(5),
|
||||||
}),
|
}),
|
||||||
FieldSpec: types.FieldSpec{Path: "spec/replicas"},
|
FieldSpec: types.FieldSpec{Path: "spec/replicas"},
|
||||||
@@ -79,7 +85,7 @@ metadata:
|
|||||||
spec:
|
spec:
|
||||||
replicas: 1`,
|
replicas: 1`,
|
||||||
filter: Filter{
|
filter: Filter{
|
||||||
MappingFunc: expansion2.MappingFuncFor(replacementCounts, map[string]interface{}{
|
MappingFunc: makeMf(map[string]interface{}{
|
||||||
"VAR": int64(5),
|
"VAR": int64(5),
|
||||||
}),
|
}),
|
||||||
FieldSpec: types.FieldSpec{Path: "a/b/c"},
|
FieldSpec: types.FieldSpec{Path: "a/b/c"},
|
||||||
@@ -111,7 +117,7 @@ data:
|
|||||||
- false
|
- false
|
||||||
- 1.23`,
|
- 1.23`,
|
||||||
filter: Filter{
|
filter: Filter{
|
||||||
MappingFunc: expansion2.MappingFuncFor(replacementCounts, map[string]interface{}{
|
MappingFunc: makeMf(map[string]interface{}{
|
||||||
"FOO": "foo",
|
"FOO": "foo",
|
||||||
"BAR": "bar",
|
"BAR": "bar",
|
||||||
"BOOL": false,
|
"BOOL": false,
|
||||||
@@ -142,7 +148,7 @@ data:
|
|||||||
BAZ: $(BAZ)
|
BAZ: $(BAZ)
|
||||||
PLUS: foo+bar`,
|
PLUS: foo+bar`,
|
||||||
filter: Filter{
|
filter: Filter{
|
||||||
MappingFunc: expansion2.MappingFuncFor(replacementCounts, map[string]interface{}{
|
MappingFunc: makeMf(map[string]interface{}{
|
||||||
"FOO": "foo",
|
"FOO": "foo",
|
||||||
"BAR": "bar",
|
"BAR": "bar",
|
||||||
}),
|
}),
|
||||||
@@ -181,7 +187,7 @@ data:
|
|||||||
SLICE:
|
SLICE:
|
||||||
- $(FOO)`,
|
- $(FOO)`,
|
||||||
filter: Filter{
|
filter: Filter{
|
||||||
MappingFunc: expansion2.MappingFuncFor(replacementCounts, map[string]interface{}{
|
MappingFunc: makeMf(map[string]interface{}{
|
||||||
"FOO": "foo",
|
"FOO": "foo",
|
||||||
"BAR": "bar",
|
"BAR": "bar",
|
||||||
}),
|
}),
|
||||||
@@ -204,8 +210,10 @@ metadata:
|
|||||||
data:
|
data:
|
||||||
FOO: null`,
|
FOO: null`,
|
||||||
filter: Filter{
|
filter: Filter{
|
||||||
MappingFunc: expansion2.MappingFuncFor(replacementCounts, map[string]interface{}{}),
|
MappingFunc: makeMf(map[string]interface{}{
|
||||||
FieldSpec: types.FieldSpec{Path: "data/FOO"},
|
// no replacements!
|
||||||
|
}),
|
||||||
|
FieldSpec: types.FieldSpec{Path: "data/FOO"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -223,8 +231,6 @@ data:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFilterUnhappy(t *testing.T) {
|
func TestFilterUnhappy(t *testing.T) {
|
||||||
replacementCounts := make(map[string]int)
|
|
||||||
|
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
input string
|
input string
|
||||||
expectedError string
|
expectedError string
|
||||||
@@ -239,18 +245,9 @@ metadata:
|
|||||||
data:
|
data:
|
||||||
slice:
|
slice:
|
||||||
- false`,
|
- false`,
|
||||||
expectedError: `obj 'apiVersion: apps/v1
|
expectedError: `considering field 'data/slice' of object Deployment.v1.apps/dep.[noNs]: invalid value type expect a string`,
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: dep
|
|
||||||
annotations:
|
|
||||||
config.kubernetes.io/index: '0'
|
|
||||||
data:
|
|
||||||
slice:
|
|
||||||
- false
|
|
||||||
' at path 'data/slice': invalid value type expect a string`,
|
|
||||||
filter: Filter{
|
filter: Filter{
|
||||||
MappingFunc: expansion2.MappingFuncFor(replacementCounts, map[string]interface{}{
|
MappingFunc: makeMf(map[string]interface{}{
|
||||||
"VAR": int64(5),
|
"VAR": int64(5),
|
||||||
}),
|
}),
|
||||||
FieldSpec: types.FieldSpec{Path: "data/slice"},
|
FieldSpec: types.FieldSpec{Path: "data/slice"},
|
||||||
@@ -264,17 +261,9 @@ metadata:
|
|||||||
name: dep
|
name: dep
|
||||||
data:
|
data:
|
||||||
1: str`,
|
1: str`,
|
||||||
expectedError: `obj 'apiVersion: apps/v1
|
expectedError: `considering field 'data' of object Deployment.v1.apps/dep.[noNs]: invalid map key: value='1', tag='` + yaml.NodeTagInt + `'`,
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: dep
|
|
||||||
annotations:
|
|
||||||
config.kubernetes.io/index: '0'
|
|
||||||
data:
|
|
||||||
1: str
|
|
||||||
' at path 'data': invalid map key: 1, type: ` + yaml.NodeTagInt,
|
|
||||||
filter: Filter{
|
filter: Filter{
|
||||||
MappingFunc: expansion2.MappingFuncFor(replacementCounts, map[string]interface{}{
|
MappingFunc: makeMf(map[string]interface{}{
|
||||||
"VAR": int64(5),
|
"VAR": int64(5),
|
||||||
}),
|
}),
|
||||||
FieldSpec: types.FieldSpec{Path: "data"},
|
FieldSpec: types.FieldSpec{Path: "data"},
|
||||||
|
|||||||
7
api/filters/replacement/doc.go
Normal file
7
api/filters/replacement/doc.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
// Copyright 2022 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
256
api/filters/replacement/replacement.go
Normal file
256
api/filters/replacement/replacement.go
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
// Copyright 2021 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package replacement
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"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"
|
||||||
|
kyaml_utils "sigs.k8s.io/kustomize/kyaml/utils"
|
||||||
|
"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 i, 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, &f.Replacements[i])
|
||||||
|
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 i, id := range ids {
|
||||||
|
if id.IsSelectedBy(t.Select.ResId) && !rejectId(t.Reject, &ids[i]) {
|
||||||
|
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 := kyaml_utils.SmarterPathSplitter(fp, ".")
|
||||||
|
var t *yaml.RNode
|
||||||
|
var err error
|
||||||
|
if target.Options != nil && target.Options.Create {
|
||||||
|
// create option is not supported in a wildcard matching.
|
||||||
|
// Because, PathMatcher is not supported create option.
|
||||||
|
// So, if create option is set, we fallback to PathGetter.
|
||||||
|
for _, f := range fieldPath {
|
||||||
|
if f == "*" {
|
||||||
|
return errors.New("cannot support create option in a multi-value target") //nolint:goerr113
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t, err = node.Pipe(yaml.LookupCreate(value.YNode().Kind, fieldPath...))
|
||||||
|
} else {
|
||||||
|
t, err = node.Pipe(&yaml.PathMatcher{Path: fieldPath})
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if t != nil {
|
||||||
|
if err = applyToOneNode(target.Options, t, value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyToOneNode(options *types.FieldOptions, t *yaml.RNode, value *yaml.RNode) error {
|
||||||
|
if len(t.YNode().Content) == 0 {
|
||||||
|
if err := setTargetValue(options, t, value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, scalarNode := range t.YNode().Content {
|
||||||
|
rn := yaml.NewRNode(scalarNode)
|
||||||
|
if err := setTargetValue(options, rn, 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.YNode().Kind == yaml.ScalarNode {
|
||||||
|
// For scalar, only copy the value (leave any type intact to auto-convert int->string or string->int)
|
||||||
|
t.YNode().Value = value.YNode().Value
|
||||||
|
} else {
|
||||||
|
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 := kyaml_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
|
||||||
|
}
|
||||||
2383
api/filters/replacement/replacement_test.go
Normal file
2383
api/filters/replacement/replacement_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,6 @@
|
|||||||
|
// Copyright 2022 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
package replicacount
|
package replicacount
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// Copyright 2022 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
package replicacount
|
package replicacount
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -14,9 +17,17 @@ import (
|
|||||||
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"`
|
FieldSpec types.FieldSpec `json:"fieldSpec,omitempty" yaml:"fieldSpec,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 (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)
|
||||||
@@ -33,5 +44,5 @@ func (rc Filter) run(node *yaml.RNode) (*yaml.RNode, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (rc Filter) set(node *yaml.RNode) error {
|
func (rc Filter) set(node *yaml.RNode) error {
|
||||||
return filtersutil.SetScalar(strconv.FormatInt(rc.Replica.Count, 10))(node)
|
return rc.trackableSetter.SetEntry("", strconv.FormatInt(rc.Replica.Count, 10), yaml.NodeTagInt)(node)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// Copyright 2022 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
package replicacount
|
package replicacount
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -7,14 +10,17 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
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) {
|
||||||
|
mutationTrackerStub := filtertest_test.MutationTrackerStub{}
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
input string
|
input string
|
||||||
expected string
|
expected string
|
||||||
filter Filter
|
filter Filter
|
||||||
|
mutationTracker func(key, value, tag string, node *yaml.RNode)
|
||||||
|
expectedSetValueArgs []filtertest_test.SetValueArg
|
||||||
}{
|
}{
|
||||||
"update field": {
|
"update field": {
|
||||||
input: `
|
input: `
|
||||||
@@ -161,9 +167,44 @@ spec:
|
|||||||
FieldSpec: types.FieldSpec{Path: "spec/template/replicas"},
|
FieldSpec: types.FieldSpec{Path: "spec/template/replicas"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"mutation tracker": {
|
||||||
|
input: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: dep
|
||||||
|
spec:
|
||||||
|
replicas: 5
|
||||||
|
`,
|
||||||
|
expected: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: dep
|
||||||
|
spec:
|
||||||
|
replicas: 42
|
||||||
|
`,
|
||||||
|
filter: Filter{
|
||||||
|
Replica: types.Replica{
|
||||||
|
Name: "dep",
|
||||||
|
Count: 42,
|
||||||
|
},
|
||||||
|
FieldSpec: types.FieldSpec{Path: "spec/replicas"},
|
||||||
|
},
|
||||||
|
mutationTracker: mutationTrackerStub.MutationTracker,
|
||||||
|
expectedSetValueArgs: []filtertest_test.SetValueArg{
|
||||||
|
{
|
||||||
|
Value: "42",
|
||||||
|
Tag: "!!int",
|
||||||
|
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) {
|
||||||
if !assert.Equal(t,
|
if !assert.Equal(t,
|
||||||
strings.TrimSpace(tc.expected),
|
strings.TrimSpace(tc.expected),
|
||||||
@@ -171,6 +212,7 @@ spec:
|
|||||||
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 {
|
||||||
|
|||||||
26
api/go.mod
26
api/go.mod
@@ -1,24 +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/go-cmp v0.3.0
|
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||||
github.com/hashicorp/go-multierror v1.1.0
|
github.com/imdario/mergo v0.3.5
|
||||||
github.com/pkg/errors v0.8.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/stretchr/testify v1.4.0
|
github.com/stretchr/testify v1.7.0
|
||||||
github.com/yujunz/go-getter v1.4.1-lite
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e
|
k8s.io/kube-openapi v0.0.0-20220401212409-b28bf2818661
|
||||||
gopkg.in/yaml.v2 v2.3.0
|
sigs.k8s.io/kustomize/kyaml v0.13.7
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71
|
|
||||||
k8s.io/api v0.17.0
|
|
||||||
k8s.io/apimachinery v0.17.0
|
|
||||||
k8s.io/client-go v0.17.0
|
|
||||||
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a
|
|
||||||
sigs.k8s.io/kustomize/kyaml v0.9.4
|
|
||||||
sigs.k8s.io/yaml v1.2.0
|
sigs.k8s.io/yaml v1.2.0
|
||||||
)
|
)
|
||||||
|
|||||||
938
api/go.sum
938
api/go.sum
File diff suppressed because it is too large
Load Diff
@@ -20,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")
|
||||||
@@ -48,23 +48,18 @@ 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)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// HashRNode returns the hash value of input RNode
|
// Hasher computes the hash of an RNode.
|
||||||
func HashRNode(node *yaml.RNode) (string, error) {
|
type Hasher struct{}
|
||||||
// get node kind
|
|
||||||
kindNode, err := node.Pipe(yaml.FieldMatcher{Name: "kind"})
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
kind := kindNode.YNode().Value
|
|
||||||
|
|
||||||
// calculate hash for different kinds
|
// Hash returns a hash of the argument.
|
||||||
encoded := ""
|
func (h *Hasher) Hash(node *yaml.RNode) (r string, err error) {
|
||||||
switch kind {
|
var encoded string
|
||||||
|
switch node.GetKind() {
|
||||||
case "ConfigMap":
|
case "ConfigMap":
|
||||||
encoded, err = encodeConfigMap(node)
|
encoded, err = encodeConfigMap(node)
|
||||||
case "Secret":
|
case "Secret":
|
||||||
@@ -77,10 +72,11 @@ func HashRNode(node *yaml.RNode) (string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return Encode(Hash(encoded))
|
return encode(hex256(encoded))
|
||||||
}
|
}
|
||||||
|
|
||||||
func getNodeValues(node *yaml.RNode, paths []string) (map[string]interface{}, error) {
|
func getNodeValues(
|
||||||
|
node *yaml.RNode, paths []string) (map[string]interface{}, error) {
|
||||||
values := make(map[string]interface{})
|
values := make(map[string]interface{})
|
||||||
for _, p := range paths {
|
for _, p := range paths {
|
||||||
vn, err := node.Pipe(yaml.Lookup(p))
|
vn, err := node.Pipe(yaml.Lookup(p))
|
||||||
@@ -117,8 +113,11 @@ func encodeConfigMap(node *yaml.RNode) (string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
m := map[string]interface{}{"kind": "ConfigMap", "name": values["metadata/name"],
|
m := map[string]interface{}{
|
||||||
"data": values["data"]}
|
"kind": "ConfigMap",
|
||||||
|
"name": values["metadata/name"],
|
||||||
|
"data": values["data"],
|
||||||
|
}
|
||||||
if _, ok := values["binaryData"].(map[string]interface{}); ok {
|
if _, ok := values["binaryData"].(map[string]interface{}); ok {
|
||||||
m["binaryData"] = values["binaryData"]
|
m["binaryData"] = values["binaryData"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,10 +32,10 @@ func TestSortArrayAndComputeHash(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHash(t *testing.T) {
|
func Test_hex256(t *testing.T) {
|
||||||
// hash the empty string to be sure that sha256 is being used
|
// 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)
|
||||||
}
|
}
|
||||||
@@ -56,7 +56,7 @@ kind: ConfigMap`, "6ct58987ht", ""},
|
|||||||
{"one key", `
|
{"one key", `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: ConfigMap
|
kind: ConfigMap
|
||||||
data:
|
data:
|
||||||
one: ""`, "9g67k2htb6", ""},
|
one: ""`, "9g67k2htb6", ""},
|
||||||
// three keys (tests sorting order)
|
// three keys (tests sorting order)
|
||||||
{"three keys", `
|
{"three keys", `
|
||||||
@@ -93,17 +93,17 @@ data:
|
|||||||
binaryData:
|
binaryData:
|
||||||
two: ""`, "698h7c7t9m", ""},
|
two: ""`, "698h7c7t9m", ""},
|
||||||
}
|
}
|
||||||
|
h := &Hasher{}
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
node, err := yaml.Parse(c.cmYaml)
|
node, err := yaml.Parse(c.cmYaml)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
h, err := HashRNode(node)
|
hashed, err := h.Hash(node)
|
||||||
if SkipRest(t, c.desc, err, c.err) {
|
if SkipRest(t, c.desc, err, c.err) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if c.hash != h {
|
if c.hash != hashed {
|
||||||
t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h)
|
t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -154,35 +154,34 @@ type: my-type
|
|||||||
data:
|
data:
|
||||||
one: ""`, "74bd68bm66", ""},
|
one: ""`, "74bd68bm66", ""},
|
||||||
}
|
}
|
||||||
|
h := &Hasher{}
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
node, err := yaml.Parse(c.secretYaml)
|
node, err := yaml.Parse(c.secretYaml)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
h, err := HashRNode(node)
|
hashed, err := h.Hash(node)
|
||||||
if SkipRest(t, c.desc, err, c.err) {
|
if SkipRest(t, c.desc, err, c.err) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if c.hash != h {
|
if c.hash != hashed {
|
||||||
t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h)
|
t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUnstructuredHash(t *testing.T) {
|
func TestBasicHash(t *testing.T) {
|
||||||
cases := []struct {
|
cases := map[string]struct {
|
||||||
desc string
|
res string
|
||||||
unstructured string
|
hash string
|
||||||
hash string
|
err string
|
||||||
err string
|
|
||||||
}{
|
}{
|
||||||
{"minimal", `
|
"minimal": {`
|
||||||
apiVersion: test/v1
|
apiVersion: test/v1
|
||||||
kind: TestResource
|
kind: TestResource
|
||||||
metadata:
|
metadata:
|
||||||
name: my-resource`, "244782mkb7", ""},
|
name: my-resource`, "244782mkb7", ""},
|
||||||
{"with spec", `
|
"with spec": {`
|
||||||
apiVersion: test/v1
|
apiVersion: test/v1
|
||||||
kind: TestResource
|
kind: TestResource
|
||||||
metadata:
|
metadata:
|
||||||
@@ -191,19 +190,22 @@ spec:
|
|||||||
foo: 1
|
foo: 1
|
||||||
bar: abc`, "59m2mdccg4", ""},
|
bar: abc`, "59m2mdccg4", ""},
|
||||||
}
|
}
|
||||||
|
h := &Hasher{}
|
||||||
for _, c := range cases {
|
for n := range cases {
|
||||||
node, err := yaml.Parse(c.unstructured)
|
c := cases[n]
|
||||||
if err != nil {
|
t.Run(n, func(t *testing.T) {
|
||||||
t.Fatal(err)
|
node, err := yaml.Parse(c.res)
|
||||||
}
|
if err != nil {
|
||||||
h, err := HashRNode(node)
|
t.Fatal(err)
|
||||||
if SkipRest(t, c.desc, err, c.err) {
|
}
|
||||||
continue
|
hashed, err := h.Hash(node)
|
||||||
}
|
if SkipRest(t, n, err, c.err) {
|
||||||
if c.hash != h {
|
return
|
||||||
t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h)
|
}
|
||||||
}
|
if c.hash != hashed {
|
||||||
|
t.Errorf("case %q, expect hash %q but got %q", n, c.hash, h)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,7 +224,7 @@ kind: ConfigMap`, `{"data":"","kind":"ConfigMap","name":""}`, ""},
|
|||||||
{"one key", `
|
{"one key", `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: ConfigMap
|
kind: ConfigMap
|
||||||
data:
|
data:
|
||||||
one: ""`, `{"data":{"one":""},"kind":"ConfigMap","name":""}`, ""},
|
one: ""`, `{"data":{"one":""},"kind":"ConfigMap","name":""}`, ""},
|
||||||
// three keys (tests sorting order)
|
// three keys (tests sorting order)
|
||||||
{"three keys", `
|
{"three keys", `
|
||||||
@@ -334,10 +336,12 @@ data:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SkipRest returns true if there was a non-nil error or if we expected an error that didn't happen,
|
// SkipRest returns true if there was a non-nil error or if we expected an
|
||||||
// and logs the appropriate error on the test object.
|
// 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.
|
// 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 {
|
func SkipRest(t *testing.T, desc string, err error, contains string) bool {
|
||||||
|
t.Helper()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if len(contains) == 0 {
|
if len(contains) == 0 {
|
||||||
t.Errorf("case %q, expect nil error but got %q", desc, err.Error())
|
t.Errorf("case %q, expect nil error but got %q", desc, err.Error())
|
||||||
|
|||||||
@@ -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,84 +38,10 @@ type Loader interface {
|
|||||||
Cleanup() error
|
Cleanup() error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Kunstructured represents a Kubernetes Resource Model object.
|
// KustHasher returns a hash of the argument
|
||||||
type Kunstructured interface {
|
|
||||||
// Several uses.
|
|
||||||
Copy() Kunstructured
|
|
||||||
|
|
||||||
// Used by Resource.Replace, which in turn is used in many places, e.g.
|
|
||||||
// - resource.Resource.Merge
|
|
||||||
// - resWrangler.appendReplaceOrMerge (AbsorbAll)
|
|
||||||
// - api.internal.k8sdeps.transformer.patch.conflictdetector
|
|
||||||
GetAnnotations() map[string]string
|
|
||||||
|
|
||||||
// Used by ResAccumulator and ReplacementTransformer.
|
|
||||||
GetFieldValue(string) (interface{}, error)
|
|
||||||
|
|
||||||
// Used by Resource.OrgId
|
|
||||||
GetGvk() resid.Gvk
|
|
||||||
|
|
||||||
// Used by resource.Factory.SliceFromBytes
|
|
||||||
GetKind() string
|
|
||||||
|
|
||||||
// Used by Resource.Replace
|
|
||||||
GetLabels() map[string]string
|
|
||||||
|
|
||||||
// Used by Resource.CurId and resource factory.
|
|
||||||
GetName() string
|
|
||||||
|
|
||||||
// Used by special case code in
|
|
||||||
// ResMap.SubsetThatCouldBeReferencedByResource
|
|
||||||
GetSlice(path string) ([]interface{}, error)
|
|
||||||
|
|
||||||
// GetString returns the value of a string field.
|
|
||||||
// Used by Resource.GetNamespace
|
|
||||||
GetString(string) (string, error)
|
|
||||||
|
|
||||||
// Several uses.
|
|
||||||
Map() map[string]interface{}
|
|
||||||
|
|
||||||
// Used by Resource.AsYAML and Resource.String
|
|
||||||
MarshalJSON() ([]byte, error)
|
|
||||||
|
|
||||||
// Used by resWrangler.Select
|
|
||||||
MatchesAnnotationSelector(selector string) (bool, error)
|
|
||||||
|
|
||||||
// Used by resWrangler.Select
|
|
||||||
MatchesLabelSelector(selector string) (bool, error)
|
|
||||||
|
|
||||||
// Used by Resource.Replace.
|
|
||||||
SetAnnotations(map[string]string)
|
|
||||||
|
|
||||||
// Used by PatchStrategicMergeTransformer.
|
|
||||||
SetGvk(resid.Gvk)
|
|
||||||
|
|
||||||
// Used by Resource.Replace and used to remove "validated by" labels.
|
|
||||||
SetLabels(map[string]string)
|
|
||||||
|
|
||||||
// Used by Resource.Replace.
|
|
||||||
SetName(string)
|
|
||||||
|
|
||||||
// Used by Resource.Replace.
|
|
||||||
SetNamespace(string)
|
|
||||||
|
|
||||||
// Needed, for now, by kyaml/filtersutil.ApplyToJSON.
|
|
||||||
UnmarshalJSON([]byte) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// KunstructuredFactory makes instances of Kunstructured.
|
|
||||||
type KunstructuredFactory interface {
|
|
||||||
SliceFromBytes([]byte) ([]Kunstructured, error)
|
|
||||||
FromMap(m map[string]interface{}) Kunstructured
|
|
||||||
Hasher() KunstructuredHasher
|
|
||||||
MakeConfigMap(kvLdr KvLoader, args *types.ConfigMapArgs) (Kunstructured, error)
|
|
||||||
MakeSecret(kvLdr KvLoader, args *types.SecretArgs) (Kunstructured, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// KunstructuredHasher returns a hash of the argument
|
|
||||||
// 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
|
||||||
|
|||||||
@@ -14,37 +14,53 @@ func IsImageMatched(s, t string) bool {
|
|||||||
// Tag values are limited to [a-zA-Z0-9_.{}-].
|
// Tag values are limited to [a-zA-Z0-9_.{}-].
|
||||||
// Some tools like Bazel rules_k8s allow tag patterns with {} characters.
|
// Some tools like Bazel rules_k8s allow tag patterns with {} characters.
|
||||||
// More info: https://github.com/bazelbuild/rules_k8s/pull/423
|
// More info: https://github.com/bazelbuild/rules_k8s/pull/423
|
||||||
pattern, _ := regexp.Compile("^" + t + "(@sha256)?(:[a-zA-Z0-9_.{}-]*)?$")
|
pattern, _ := regexp.Compile("^" + t + "(:[a-zA-Z0-9_.{}-]*)?(@sha256:[a-zA-Z0-9_.{}-]*)?$")
|
||||||
return pattern.MatchString(s)
|
return pattern.MatchString(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Split separates and returns the name and tag parts
|
// Split separates and returns the name and tag parts
|
||||||
// from the image string using either colon `:` or at `@` separators.
|
// from the image string using either colon `:` or at `@` separators.
|
||||||
// Note that the returned tag keeps its separator.
|
// image reference pattern: [[host[:port]/]component/]component[:tag][@digest]
|
||||||
func Split(imageName string) (name string, tag string) {
|
func Split(imageName string) (name string, tag string, digest string) {
|
||||||
// check if image name contains a domain
|
// check if image name contains a domain
|
||||||
// if domain is present, ignore domain and check for `:`
|
// if domain is present, ignore domain and check for `:`
|
||||||
ic := -1
|
searchName := imageName
|
||||||
if slashIndex := strings.Index(imageName, "/"); slashIndex < 0 {
|
slashIndex := strings.Index(imageName, "/")
|
||||||
ic = strings.LastIndex(imageName, ":")
|
if slashIndex > 0 {
|
||||||
|
searchName = imageName[slashIndex:]
|
||||||
} else {
|
} else {
|
||||||
lastIc := strings.LastIndex(imageName[slashIndex:], ":")
|
slashIndex = 0
|
||||||
// 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
|
id := strings.Index(searchName, "@")
|
||||||
if ia > 0 {
|
ic := strings.Index(searchName, ":")
|
||||||
i = ia
|
|
||||||
|
// no tag or digest
|
||||||
|
if ic < 0 && id < 0 {
|
||||||
|
return imageName, "", ""
|
||||||
}
|
}
|
||||||
|
|
||||||
name = imageName[:i]
|
// digest only
|
||||||
tag = imageName[i:]
|
if id >= 0 && (id < ic || ic < 0) {
|
||||||
return
|
id += slashIndex
|
||||||
|
name = imageName[:id]
|
||||||
|
digest = strings.TrimPrefix(imageName[id:], "@")
|
||||||
|
return name, "", digest
|
||||||
|
}
|
||||||
|
|
||||||
|
// tag and digest
|
||||||
|
if id >= 0 && ic >= 0 {
|
||||||
|
id += slashIndex
|
||||||
|
ic += slashIndex
|
||||||
|
name = imageName[:ic]
|
||||||
|
tag = strings.TrimPrefix(imageName[ic:id], ":")
|
||||||
|
digest = strings.TrimPrefix(imageName[id:], "@")
|
||||||
|
return name, tag, digest
|
||||||
|
}
|
||||||
|
|
||||||
|
// tag only
|
||||||
|
ic += slashIndex
|
||||||
|
name = imageName[:ic]
|
||||||
|
tag = strings.TrimPrefix(imageName[ic:], ":")
|
||||||
|
return name, tag, ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,11 +23,23 @@ func TestIsImageMatched(t *testing.T) {
|
|||||||
isMatched: true,
|
isMatched: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
testName: "name is match",
|
testName: "name is match with tag",
|
||||||
value: "nginx:12345",
|
value: "nginx:12345",
|
||||||
name: "nginx",
|
name: "nginx",
|
||||||
isMatched: true,
|
isMatched: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
testName: "name is match with digest",
|
||||||
|
value: "nginx@sha256:xyz",
|
||||||
|
name: "nginx",
|
||||||
|
isMatched: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "name is match with tag and digest",
|
||||||
|
value: "nginx:12345@sha256:xyz",
|
||||||
|
name: "nginx",
|
||||||
|
isMatched: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
testName: "name is not a match",
|
testName: "name is not a match",
|
||||||
value: "apache:12345",
|
value: "apache:12345",
|
||||||
@@ -49,32 +61,65 @@ func TestSplit(t *testing.T) {
|
|||||||
value string
|
value string
|
||||||
name string
|
name string
|
||||||
tag string
|
tag string
|
||||||
|
digest string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
testName: "no tag",
|
testName: "no tag",
|
||||||
value: "nginx",
|
value: "nginx",
|
||||||
name: "nginx",
|
name: "nginx",
|
||||||
tag: "",
|
tag: "",
|
||||||
|
digest: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
testName: "with tag",
|
testName: "with tag",
|
||||||
value: "nginx:1.2.3",
|
value: "nginx:1.2.3",
|
||||||
name: "nginx",
|
name: "nginx",
|
||||||
tag: ":1.2.3",
|
tag: "1.2.3",
|
||||||
|
digest: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
testName: "with digest",
|
testName: "with digest",
|
||||||
value: "nginx@12345",
|
value: "nginx@sha256:12345",
|
||||||
name: "nginx",
|
name: "nginx",
|
||||||
tag: "@12345",
|
tag: "",
|
||||||
|
digest: "sha256:12345",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "with tag and digest",
|
||||||
|
value: "nginx:1.2.3@sha256:12345",
|
||||||
|
name: "nginx",
|
||||||
|
tag: "1.2.3",
|
||||||
|
digest: "sha256:12345",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "with domain",
|
||||||
|
value: "docker.io/nginx:1.2.3",
|
||||||
|
name: "docker.io/nginx",
|
||||||
|
tag: "1.2.3",
|
||||||
|
digest: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "with domain and port",
|
||||||
|
value: "foo.com:443/nginx:1.2.3",
|
||||||
|
name: "foo.com:443/nginx",
|
||||||
|
tag: "1.2.3",
|
||||||
|
digest: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "with domain, port, tag and digest",
|
||||||
|
value: "foo.com:443/nginx:1.2.3@sha256:12345",
|
||||||
|
name: "foo.com:443/nginx",
|
||||||
|
tag: "1.2.3",
|
||||||
|
digest: "sha256:12345",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.testName, func(t *testing.T) {
|
t.Run(tc.testName, func(t *testing.T) {
|
||||||
name, tag := Split(tc.value)
|
name, tag, digest := Split(tc.value)
|
||||||
assert.Equal(t, tc.name, name)
|
assert.Equal(t, tc.name, name)
|
||||||
assert.Equal(t, tc.tag, tag)
|
assert.Equal(t, tc.tag, tag)
|
||||||
|
assert.Equal(t, tc.digest, digest)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,121 +0,0 @@
|
|||||||
// Copyright 2019 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
// Package expansion provides functions find and replace $(FOO) style variables in strings.
|
|
||||||
package expansion
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
operator = '$'
|
|
||||||
referenceOpener = '('
|
|
||||||
referenceCloser = ')'
|
|
||||||
)
|
|
||||||
|
|
||||||
// syntaxWrap returns the input string wrapped by the expansion syntax.
|
|
||||||
func syntaxWrap(input string) string {
|
|
||||||
return string(operator) + string(referenceOpener) + input + string(referenceCloser)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MappingFuncFor returns a mapping function for use with Expand that
|
|
||||||
// implements the expansion semantics defined in the expansion spec; it
|
|
||||||
// returns the input string wrapped in the expansion syntax if no mapping
|
|
||||||
// for the input is found.
|
|
||||||
func MappingFuncFor(
|
|
||||||
counts map[string]int,
|
|
||||||
context ...map[string]interface{}) func(string) interface{} {
|
|
||||||
return func(input string) interface{} {
|
|
||||||
for _, vars := range context {
|
|
||||||
val, ok := vars[input]
|
|
||||||
if ok {
|
|
||||||
counts[input]++
|
|
||||||
switch typedV := val.(type) {
|
|
||||||
case string, int64, float64, bool:
|
|
||||||
return typedV
|
|
||||||
default:
|
|
||||||
return syntaxWrap(input)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return syntaxWrap(input)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expand replaces variable references in the input string according to
|
|
||||||
// the expansion spec using the given mapping function to resolve the
|
|
||||||
// values of variables.
|
|
||||||
func Expand(input string, mapping func(string) interface{}) interface{} {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
checkpoint := 0
|
|
||||||
for cursor := 0; cursor < len(input); cursor++ {
|
|
||||||
if input[cursor] == operator && cursor+1 < len(input) {
|
|
||||||
// Copy the portion of the input string since the last
|
|
||||||
// checkpoint into the buffer
|
|
||||||
buf.WriteString(input[checkpoint:cursor])
|
|
||||||
|
|
||||||
// Attempt to read the variable name as defined by the
|
|
||||||
// syntax from the input string
|
|
||||||
read, isVar, advance := tryReadVariableName(input[cursor+1:])
|
|
||||||
|
|
||||||
if isVar {
|
|
||||||
// We were able to read a variable name correctly;
|
|
||||||
// apply the mapping to the variable name and copy the
|
|
||||||
// bytes into the buffer
|
|
||||||
mapped := mapping(read)
|
|
||||||
if input == syntaxWrap(read) {
|
|
||||||
// Preserve the type of variable
|
|
||||||
return mapped
|
|
||||||
}
|
|
||||||
|
|
||||||
// Variable is used in a middle of a string
|
|
||||||
buf.WriteString(fmt.Sprintf("%v", mapped))
|
|
||||||
} else {
|
|
||||||
// Not a variable name; copy the read bytes into the buffer
|
|
||||||
buf.WriteString(read)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Advance the cursor in the input string to account for
|
|
||||||
// bytes consumed to read the variable name expression
|
|
||||||
cursor += advance
|
|
||||||
|
|
||||||
// Advance the checkpoint in the input string
|
|
||||||
checkpoint = cursor + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the buffer and any remaining unwritten bytes in the
|
|
||||||
// input string.
|
|
||||||
return buf.String() + input[checkpoint:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// tryReadVariableName attempts to read a variable name from the input
|
|
||||||
// string and returns the content read from the input, whether that content
|
|
||||||
// represents a variable name to perform mapping on, and the number of bytes
|
|
||||||
// consumed in the input string.
|
|
||||||
//
|
|
||||||
// The input string is assumed not to contain the initial operator.
|
|
||||||
func tryReadVariableName(input string) (string, bool, int) {
|
|
||||||
switch input[0] {
|
|
||||||
case operator:
|
|
||||||
// Escaped operator; return it.
|
|
||||||
return input[0:1], false, 1
|
|
||||||
case referenceOpener:
|
|
||||||
// Scan to expression closer
|
|
||||||
for i := 1; i < len(input); i++ {
|
|
||||||
if input[i] == referenceCloser {
|
|
||||||
return input[1:i], true, i + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Incomplete reference; return it.
|
|
||||||
return string(operator) + string(referenceOpener), false, 1
|
|
||||||
default:
|
|
||||||
// Not the beginning of an expression, ie, an operator
|
|
||||||
// that doesn't begin an expression. Return the operator
|
|
||||||
// and the first rune in the string.
|
|
||||||
return string(operator) + string(input[0]), false, 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,23 +4,30 @@
|
|||||||
package accumulator
|
package accumulator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/filters/nameref"
|
"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/resmap"
|
"sigs.k8s.io/kustomize/api/resmap"
|
||||||
"sigs.k8s.io/kustomize/kyaml/filtersutil"
|
"sigs.k8s.io/kustomize/api/resource"
|
||||||
|
"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")
|
||||||
}
|
}
|
||||||
@@ -33,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:
|
||||||
@@ -48,54 +106,59 @@ 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 cache the resource OrgId values because they don't change and otherwise are very visible in a memory pprof
|
||||||
// We look in this subset for all Deployment objects
|
resourceOrgIds := make([]resid.ResId, len(resources))
|
||||||
// with a resId that has a Name matching the field value
|
for i, resource := range resources {
|
||||||
// present in the HPA. If no match do nothing; if more
|
resourceOrgIds[i] = resource.OrgId()
|
||||||
// than one match, it's an error.
|
}
|
||||||
//
|
|
||||||
// We overwrite the HPA name field with the value found
|
fMap = make(filterMap)
|
||||||
// in the Deployment's name field (the name in the raw
|
for _, backReference := range t.backRefs {
|
||||||
// object - the modified name - not the unmodified name
|
for _, referrerSpec := range backReference.Referrers {
|
||||||
// in the Deployment's resId).
|
for i, res := range resources {
|
||||||
//
|
if resourceOrgIds[i].IsSelected(&referrerSpec.Gvk) {
|
||||||
// This process assumes that the name stored in a ResId
|
// If this is true, the res might be a referrer, and if
|
||||||
// (the ResMap key) isn't modified by name transformers.
|
// so, the name reference it holds might need an update.
|
||||||
// Name transformers should only modify the name in the
|
if resHasField(res, referrerSpec.Path) {
|
||||||
// body of the resource object (the value in the ResMap).
|
// Optimization - the referrer has the field
|
||||||
//
|
// that might need updating.
|
||||||
func (o *nameReferenceTransformer) Transform(m resmap.ResMap) error {
|
fMap[res] = append(fMap[res], nameref.Filter{
|
||||||
// TODO: Too much looping, here and in transitive calls.
|
// Name field to write in the Referrer.
|
||||||
for _, referrer := range m.Resources() {
|
// If the path specified here isn't found in
|
||||||
var candidates resmap.ResMap
|
// the Referrer, nothing happens (no error,
|
||||||
for _, target := range o.backRefs {
|
// no field creation).
|
||||||
for _, fSpec := range target.FieldSpecs {
|
NameFieldToUpdate: referrerSpec,
|
||||||
if referrer.OrgId().IsSelected(&fSpec.Gvk) {
|
// Specification of object class to read from.
|
||||||
if candidates == nil {
|
// Always read from metadata/name field.
|
||||||
candidates = m.SubsetThatCouldBeReferencedByResource(referrer)
|
ReferralTarget: backReference.Gvk,
|
||||||
}
|
})
|
||||||
err := filtersutil.ApplyToJSON(nameref.Filter{
|
|
||||||
FieldSpec: fSpec,
|
|
||||||
Referrer: referrer,
|
|
||||||
Target: target.Gvk,
|
|
||||||
ReferralCandidates: candidates,
|
|
||||||
}, referrer)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return fMap
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: check res for field existence here to avoid extra work.
|
||||||
|
// res.GetFieldValue, which uses yaml.Lookup under the hood, doesn't know
|
||||||
|
// how to parse fieldspec-style paths that make no distinction
|
||||||
|
// between maps and sequences. This means it cannot lookup commonly
|
||||||
|
// used "indeterminate" paths like
|
||||||
|
// spec/containers/env/valueFrom/configMapKeyRef/name
|
||||||
|
// ('containers' is a list, not a map).
|
||||||
|
// However, the fieldspec filter does know how to handle this;
|
||||||
|
// extract that code and call it here?
|
||||||
|
func resHasField(res *resource.Resource, path string) bool {
|
||||||
|
return true
|
||||||
|
// fld := strings.Join(utils.PathSplitter(path), ".")
|
||||||
|
// _, e := res.GetFieldValue(fld)
|
||||||
|
// return e == nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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: "cannot find field 'name' in node"},
|
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",
|
||||||
@@ -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",
|
||||||
@@ -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",
|
||||||
@@ -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",
|
||||||
@@ -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,19 +4,15 @@
|
|||||||
package accumulator
|
package accumulator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
expansion2 "sigs.k8s.io/kustomize/api/internal/accumulator/expansion"
|
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/filters/refvar"
|
"sigs.k8s.io/kustomize/api/filters/refvar"
|
||||||
"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"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type refVarTransformer struct {
|
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
|
||||||
@@ -35,8 +31,7 @@ func newRefVarTransformer(
|
|||||||
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -46,14 +41,13 @@ 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 {
|
||||||
err := filtersutil.ApplyToJSON(refvar.Filter{
|
err := res.ApplyFilter(refvar.Filter{
|
||||||
MappingFunc: rv.mappingFunc,
|
MappingFunc: mf,
|
||||||
FieldSpec: fieldSpec,
|
FieldSpec: fieldSpec,
|
||||||
}, res)
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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",
|
||||||
@@ -46,8 +42,7 @@ func TestRefVarTransformer(t *testing.T) {
|
|||||||
{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/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",
|
||||||
@@ -77,8 +72,7 @@ func TestRefVarTransformer(t *testing.T) {
|
|||||||
}}).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",
|
||||||
@@ -109,15 +103,13 @@ func TestRefVarTransformer(t *testing.T) {
|
|||||||
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",
|
||||||
@@ -128,18 +120,15 @@ func TestRefVarTransformer(t *testing.T) {
|
|||||||
"slice": []interface{}{5}, // noticeably *not* a []string
|
"slice": []interface{}{5}, // noticeably *not* a []string
|
||||||
}}).ResMap(),
|
}}).ResMap(),
|
||||||
},
|
},
|
||||||
errMessage: `obj '{"apiVersion": "v1", "data": {"slice": [5]}, "kind": "ConfigMap", "metadata": {"name": "cm1"}}
|
errMessage: `considering field 'data/slice' of object ConfigMap.v1.[noGrp]/cm1.[noNs]: invalid value type expect a string`,
|
||||||
' at path 'data/slice': invalid value type expect a string`,
|
|
||||||
},
|
},
|
||||||
{
|
"var replacement in nil": {
|
||||||
description: "var replacement in nil",
|
|
||||||
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/nil"},
|
{Gvk: resid.Gvk{Version: "v1", Kind: "ConfigMap"}, Path: "data/nil"},
|
||||||
},
|
},
|
||||||
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",
|
||||||
@@ -151,8 +140,7 @@ func TestRefVarTransformer(t *testing.T) {
|
|||||||
}}).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",
|
||||||
@@ -166,20 +154,18 @@ func TestRefVarTransformer(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
@@ -189,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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user