mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-28 17:28:18 +00:00
Compare commits
733 Commits
v1.0.8
...
change-lan
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
865348695f | ||
|
|
2ec8189c1c | ||
|
|
a5dfc65440 | ||
|
|
45302f0790 | ||
|
|
1afc6c775b | ||
|
|
ca4d5ed42b | ||
|
|
cd9572e0bb | ||
|
|
ac3ea4d6f3 | ||
|
|
030824b196 | ||
|
|
cfb0c5efad | ||
|
|
b67d713bc0 | ||
|
|
0ac48f60a5 | ||
|
|
445f739234 | ||
|
|
fc8063f752 | ||
|
|
520acc7d97 | ||
|
|
ae0510f648 | ||
|
|
0f50be877c | ||
|
|
5b18c4de0c | ||
|
|
72fd31fd20 | ||
|
|
256ffdb932 | ||
|
|
8991bcb399 | ||
|
|
185ae510e8 | ||
|
|
40303cb329 | ||
|
|
38ec207609 | ||
|
|
1545e07dd6 | ||
|
|
f123380917 | ||
|
|
b4fc1e4357 | ||
|
|
76a3179868 | ||
|
|
c9bf70fd4b | ||
|
|
9a85071085 | ||
|
|
a6f41bb96d | ||
|
|
3f2acc90aa | ||
|
|
aba9f7d1e5 | ||
|
|
3b8c5ee96d | ||
|
|
a5bb5479fb | ||
|
|
3c58c9d132 | ||
|
|
1b1f91580e | ||
|
|
644dc4b9a7 | ||
|
|
96707645e2 | ||
|
|
b878e5f10d | ||
|
|
a914570240 | ||
|
|
b3d2ab29e9 | ||
|
|
3ff5c793e3 | ||
|
|
c752660aa6 | ||
|
|
efded10e26 | ||
|
|
8767495b5a | ||
|
|
403ede788c | ||
|
|
c444f93eb5 | ||
|
|
ed146f656e | ||
|
|
bcb697eb0b | ||
|
|
3ac66049c7 | ||
|
|
7a1a231041 | ||
|
|
748c88c276 | ||
|
|
6f4b104c9e | ||
|
|
867201a075 | ||
|
|
2545ea1019 | ||
|
|
5be42092af | ||
|
|
50c076eb3f | ||
|
|
fb9e00bf33 | ||
|
|
b9007fcc29 | ||
|
|
f6e01cfda7 | ||
|
|
9203478a8a | ||
|
|
28cb6daec7 | ||
|
|
e191ff53dd | ||
|
|
177297c0ef | ||
|
|
e5d730e1fe | ||
|
|
ba43ecbcb7 | ||
|
|
ee68a9c450 | ||
|
|
175c754f61 | ||
|
|
e8eed838b5 | ||
|
|
e9a3f9f5f6 | ||
|
|
826affb8dd | ||
|
|
4937b1c75e | ||
|
|
ffc16d51e0 | ||
|
|
1623f1e4c0 | ||
|
|
b32e041bfe | ||
|
|
38029d1836 | ||
|
|
b2dd74ab97 | ||
|
|
16fe7ced6a | ||
|
|
cb4af7a9d4 | ||
|
|
7493732176 | ||
|
|
2cf8371add | ||
|
|
a575c24a24 | ||
|
|
9e8d06e7ce | ||
|
|
4f1a2350ce | ||
|
|
cefb64b6a9 | ||
|
|
440d036176 | ||
|
|
53f0deec8f | ||
|
|
3c495e3b23 | ||
|
|
deaf0779a1 | ||
|
|
fd7a353df6 | ||
|
|
927b497feb | ||
|
|
237c54f47e | ||
|
|
8c23db47a7 | ||
|
|
7971ac1cb8 | ||
|
|
2490e605c3 | ||
|
|
21a0cba43e | ||
|
|
42d9287985 | ||
|
|
4848987a1f | ||
|
|
53a22cbe9b | ||
|
|
c3700e0c88 | ||
|
|
58d9a51040 | ||
|
|
8f395ad86f | ||
|
|
99391157ec | ||
|
|
99406d4412 | ||
|
|
c1dea6676f | ||
|
|
afa4664511 | ||
|
|
267eec5509 | ||
|
|
9764eb2f83 | ||
|
|
9a12b55139 | ||
|
|
f8cffef977 | ||
|
|
822420e4ab | ||
|
|
b60fca05bd | ||
|
|
1a35071672 | ||
|
|
bfc3655bad | ||
|
|
2c0c0c9497 | ||
|
|
46bd38e89d | ||
|
|
9fc4d388ce | ||
|
|
2965134f89 | ||
|
|
3a7c8a03f4 | ||
|
|
449b1b68e0 | ||
|
|
dd59eb38d0 | ||
|
|
a8465c95e1 | ||
|
|
df2f67b191 | ||
|
|
7764dee59d | ||
|
|
6465a36176 | ||
|
|
103c1b3a4f | ||
|
|
29cbec37b8 | ||
|
|
2c9e4507a7 | ||
|
|
d5d5c076a7 | ||
|
|
f09bbff35c | ||
|
|
2627e2507b | ||
|
|
1bd7afe6e7 | ||
|
|
e6c1b14108 | ||
|
|
7130e3ff1d | ||
|
|
abf538d80d | ||
|
|
f311ba8d4f | ||
|
|
3e85c4589b | ||
|
|
d0cf047381 | ||
|
|
31091a8df2 | ||
|
|
e207ae4c01 | ||
|
|
3011f18047 | ||
|
|
388d5c2d7c | ||
|
|
5c4719651e | ||
|
|
f850ca63f4 | ||
|
|
65886f1258 | ||
|
|
942e36e19f | ||
|
|
7b82154c4c | ||
|
|
284efc709c | ||
|
|
56965a0046 | ||
|
|
18f6328284 | ||
|
|
e7be999bc9 | ||
|
|
c06b95077d | ||
|
|
c4da063934 | ||
|
|
62d3200e4f | ||
|
|
9a419824ae | ||
|
|
a94eab0398 | ||
|
|
01b8ab8524 | ||
|
|
fa552d7773 | ||
|
|
7acbd4d3e0 | ||
|
|
9811123e2e | ||
|
|
f7cd44be42 | ||
|
|
9a4692e6ee | ||
|
|
3d0e29075d | ||
|
|
0f571b9120 | ||
|
|
3a44508d6f | ||
|
|
5294355c98 | ||
|
|
559efd6477 | ||
|
|
a59577c08d | ||
|
|
4f429d6b86 | ||
|
|
a6f6514412 | ||
|
|
31ee38b1a1 | ||
|
|
46c7d6d39a | ||
|
|
78cbff16ef | ||
|
|
28cefb3bd1 | ||
|
|
e666630d36 | ||
|
|
ed2ad860c6 | ||
|
|
a341c24b2a | ||
|
|
0101d6e393 | ||
|
|
e429d8ca10 | ||
|
|
45ba785641 | ||
|
|
ea3d5e68db | ||
|
|
eb75203926 | ||
|
|
1303ea3969 | ||
|
|
bb69e9e70b | ||
|
|
16d1b20ed6 | ||
|
|
b0c3cd75e1 | ||
|
|
f4eef1dc0b | ||
|
|
76c6655520 | ||
|
|
d5c8734555 | ||
|
|
62ee138173 | ||
|
|
ff6cd3ca55 | ||
|
|
852e7ed5aa | ||
|
|
b7e8042a02 | ||
|
|
6bfd7cff72 | ||
|
|
9d77cbea8c | ||
|
|
8bbe147c14 | ||
|
|
b67179e951 | ||
|
|
47237aa7a2 | ||
|
|
5e6c06fb61 | ||
|
|
901455eb0b | ||
|
|
f8c80b7335 | ||
|
|
8db82d27e9 | ||
|
|
1eab47b63f | ||
|
|
c4656b71e5 | ||
|
|
711d3d3515 | ||
|
|
0488f570cb | ||
|
|
0e459ebac8 | ||
|
|
1d65f24b04 | ||
|
|
70719a8f65 | ||
|
|
773c1f2199 | ||
|
|
bf1c801a5e | ||
|
|
e1420b408c | ||
|
|
88a7471039 | ||
|
|
3c58cf0bf0 | ||
|
|
77eebb89fd | ||
|
|
d4d993a53c | ||
|
|
ef3b0672c5 | ||
|
|
0f30c09cbf | ||
|
|
6f670a8f38 | ||
|
|
8c93f7ba74 | ||
|
|
7d3735b19e | ||
|
|
f5f8e49fa3 | ||
|
|
1382d87d7f | ||
|
|
e65b45f969 | ||
|
|
d72b16235a | ||
|
|
3118ccfd05 | ||
|
|
fdba7df3c1 | ||
|
|
02d753027a | ||
|
|
1a43759ac3 | ||
|
|
7574f07be3 | ||
|
|
48717f3f30 | ||
|
|
74d3e92b55 | ||
|
|
f66024b1c1 | ||
|
|
bf4e09a400 | ||
|
|
d968c0b4b1 | ||
|
|
9837b5b429 | ||
|
|
1a03dcabde | ||
|
|
6fb11493ad | ||
|
|
1f063d6712 | ||
|
|
cebcd8a44d | ||
|
|
ce7e5ee2c3 | ||
|
|
242b9209d8 | ||
|
|
92bd809bc8 | ||
|
|
ccc4461827 | ||
|
|
9de524da7d | ||
|
|
7c8db24656 | ||
|
|
d720e9ef49 | ||
|
|
9e69b9dcc4 | ||
|
|
4f7b0c1a21 | ||
|
|
fc5c7264cf | ||
|
|
ede407e6a2 | ||
|
|
e41ca934ac | ||
|
|
0184d5b697 | ||
|
|
e14ebc0adf | ||
|
|
e905704b0c | ||
|
|
f8060f3575 | ||
|
|
120e7b5744 | ||
|
|
b15b20467c | ||
|
|
1d005d47b5 | ||
|
|
d8585334cc | ||
|
|
6444981796 | ||
|
|
713c06354f | ||
|
|
5e2c947cf8 | ||
|
|
92ede0d3c9 | ||
|
|
84057436d6 | ||
|
|
bf18cf2d9e | ||
|
|
7913e5f5bc | ||
|
|
f550540318 | ||
|
|
1d263d24dd | ||
|
|
199763dec8 | ||
|
|
093801479c | ||
|
|
cdcc0052a6 | ||
|
|
b6d4101808 | ||
|
|
83f4fa2190 | ||
|
|
35daae1715 | ||
|
|
1997606372 | ||
|
|
209b115b7c | ||
|
|
93515517b8 | ||
|
|
8c2bff2c91 | ||
|
|
00e9657025 | ||
|
|
31691f0330 | ||
|
|
bb74a42e04 | ||
|
|
731a2a683e | ||
|
|
95fd0c5530 | ||
|
|
79d357b460 | ||
|
|
3ddc20f72c | ||
|
|
4179b8e6c8 | ||
|
|
da23b6fce1 | ||
|
|
feb0502cb4 | ||
|
|
af8a169619 | ||
|
|
68ca28879d | ||
|
|
ad400cd13d | ||
|
|
90b863d124 | ||
|
|
9b7ddd6684 | ||
|
|
bcb939c19d | ||
|
|
e2102dec3c | ||
|
|
2ef16dce90 | ||
|
|
1d9a20b391 | ||
|
|
d953eca630 | ||
|
|
6651e488d6 | ||
|
|
fd3cd47562 | ||
|
|
dcb5682594 | ||
|
|
0bd2a1e232 | ||
|
|
4d77c9f940 | ||
|
|
c461f1f766 | ||
|
|
fbcae2b770 | ||
|
|
c21dfefbdf | ||
|
|
a0c22b8216 | ||
|
|
f7a59178a8 | ||
|
|
028724df08 | ||
|
|
51bbf57e95 | ||
|
|
3e4ec3a12c | ||
|
|
a9dff35a24 | ||
|
|
1cf8156c0c | ||
|
|
f1b8fdec7f | ||
|
|
62d096e57d | ||
|
|
e49bd3ab1d | ||
|
|
1edfdea5e8 | ||
|
|
d141b2421c | ||
|
|
244b3a2c59 | ||
|
|
2e6bdd4041 | ||
|
|
077d554b76 | ||
|
|
4e058f8ece | ||
|
|
d4b90c8f4e | ||
|
|
fed8195eb2 | ||
|
|
b22e43a4a7 | ||
|
|
2c1be17fe7 | ||
|
|
56ce6b8ba4 | ||
|
|
78bac973f7 | ||
|
|
bfd61a7605 | ||
|
|
c40e3d12e8 | ||
|
|
7568531118 | ||
|
|
87411590c5 | ||
|
|
d4170797ae | ||
|
|
6616b25d66 | ||
|
|
6d56c1750f | ||
|
|
4e2c4b94e3 | ||
|
|
0be9815d27 | ||
|
|
f7c34ccb52 | ||
|
|
549290c447 | ||
|
|
2fa4a34589 | ||
|
|
14af70d148 | ||
|
|
6dd599a983 | ||
|
|
176ad74a1c | ||
|
|
38f0ca9f03 | ||
|
|
4d60f9229b | ||
|
|
ea1dd08a8c | ||
|
|
73624da253 | ||
|
|
78a2884b79 | ||
|
|
e24968c679 | ||
|
|
60dc3aa09d | ||
|
|
94be867a54 | ||
|
|
b9ab948ef2 | ||
|
|
a5c6938c65 | ||
|
|
5d0c7aa6a9 | ||
|
|
032fffe111 | ||
|
|
1b726b26cd | ||
|
|
50a8b27854 | ||
|
|
aeb2adbcfb | ||
|
|
746c7b0b5b | ||
|
|
93ad371400 | ||
|
|
d98afdc229 | ||
|
|
80f3afc1ff | ||
|
|
b16a7364fd | ||
|
|
4b543169c8 | ||
|
|
b7e1f8da72 | ||
|
|
97507a92a3 | ||
|
|
a838b85426 | ||
|
|
92fc368ede | ||
|
|
8c994725cb | ||
|
|
20b13a03e0 | ||
|
|
9dcbee1d48 | ||
|
|
77ac84c468 | ||
|
|
8b76799dd9 | ||
|
|
bab0421c6c | ||
|
|
95203c58c4 | ||
|
|
ad7c90b904 | ||
|
|
4583c4a9de | ||
|
|
428cef54c1 | ||
|
|
037f898f81 | ||
|
|
541754df8d | ||
|
|
1cd99ab68e | ||
|
|
6f566d7a38 | ||
|
|
4b25963c93 | ||
|
|
900152f724 | ||
|
|
048c1dde97 | ||
|
|
949fd51463 | ||
|
|
dd17174b35 | ||
|
|
0d14e89549 | ||
|
|
64372a786b | ||
|
|
819b2e99d0 | ||
|
|
d8e703d0f5 | ||
|
|
47a04f2648 | ||
|
|
4af125fa2d | ||
|
|
0665371590 | ||
|
|
ecb83c6ae1 | ||
|
|
c4264daf6a | ||
|
|
243cbae411 | ||
|
|
186dd20ad6 | ||
|
|
d4ba22191a | ||
|
|
29694e5b6a | ||
|
|
a99f415f36 | ||
|
|
54d6cf7087 | ||
|
|
e487e494f9 | ||
|
|
11a19906b9 | ||
|
|
65100e13b3 | ||
|
|
3b52fd5019 | ||
|
|
20e37eaf65 | ||
|
|
b6b2fb9c62 | ||
|
|
b971e6a1da | ||
|
|
087c4976b6 | ||
|
|
d0e4db74b7 | ||
|
|
f7414fec08 | ||
|
|
8cecccbc88 | ||
|
|
441f45e1cc | ||
|
|
48e8a3aec3 | ||
|
|
3fe07888ce | ||
|
|
77b44f570a | ||
|
|
352ec69556 | ||
|
|
bd83773a1e | ||
|
|
bf8b435457 | ||
|
|
e9b19281b2 | ||
|
|
986c85e728 | ||
|
|
8e72931a8b | ||
|
|
aeda4172e4 | ||
|
|
d40f52e953 | ||
|
|
3e47a2c0a7 | ||
|
|
3b9cd6bedd | ||
|
|
0759136d3f | ||
|
|
f0f8aad2bb | ||
|
|
259cecd4b8 | ||
|
|
38873aa0fa | ||
|
|
506c4a330d | ||
|
|
63e4e5ccaa | ||
|
|
cbcc976828 | ||
|
|
1ce1b82f6f | ||
|
|
b92e9ab075 | ||
|
|
cfdae37ef5 | ||
|
|
108b3e497b | ||
|
|
bff228815f | ||
|
|
252cf3723c | ||
|
|
5b88179406 | ||
|
|
6ad5d9f55b | ||
|
|
8a8331bf57 | ||
|
|
931f43f8d7 | ||
|
|
3c1e52bf94 | ||
|
|
a62d15e746 | ||
|
|
8f701a0041 | ||
|
|
593f9231ae | ||
|
|
59df8a0dda | ||
|
|
6b93973bad | ||
|
|
df3ec571fb | ||
|
|
f03fad7a96 | ||
|
|
f714e9faf3 | ||
|
|
3e1a3d83da | ||
|
|
8ba2ea9ca7 | ||
|
|
7dc8ef1028 | ||
|
|
ef51cceff5 | ||
|
|
a40c2502de | ||
|
|
0201f9cba8 | ||
|
|
7c1277f24c | ||
|
|
29f03dfb55 | ||
|
|
02d2d38c21 | ||
|
|
6757efe290 | ||
|
|
5990af8ced | ||
|
|
6cddc25f0e | ||
|
|
8bd773b536 | ||
|
|
d9ba209543 | ||
|
|
c51646e3db | ||
|
|
4f9d00c021 | ||
|
|
0042c4be54 | ||
|
|
910eb322e0 | ||
|
|
064b768176 | ||
|
|
4daa655516 | ||
|
|
d6910e9788 | ||
|
|
eed16afb00 | ||
|
|
6ec77b27da | ||
|
|
621ed52bab | ||
|
|
b8c2ed20d1 | ||
|
|
19ad9c2d46 | ||
|
|
41cc210fa0 | ||
|
|
3488b542ac | ||
|
|
04a030bcf0 | ||
|
|
25415c5501 | ||
|
|
a094be45d9 | ||
|
|
fdb8a7d74a | ||
|
|
d481dbad62 | ||
|
|
c1e7f1b957 | ||
|
|
93094c78eb | ||
|
|
a14609f730 | ||
|
|
a8984578e4 | ||
|
|
51e9fec65d | ||
|
|
38b7f42f9e | ||
|
|
e574948577 | ||
|
|
ebf1efe07e | ||
|
|
83bc67c8ad | ||
|
|
1648eceb47 | ||
|
|
538aaaf217 | ||
|
|
5b35443533 | ||
|
|
e089a56e05 | ||
|
|
5c4a778e6a | ||
|
|
e0ec8028eb | ||
|
|
578ff2e45c | ||
|
|
d04877a9e7 | ||
|
|
727b5ebd7f | ||
|
|
af1e1e6942 | ||
|
|
d05bb6b199 | ||
|
|
ba953484bf | ||
|
|
fdf78b1d7d | ||
|
|
95fed47c1c | ||
|
|
4cf916e6f4 | ||
|
|
23bf326d93 | ||
|
|
bcd4d185a7 | ||
|
|
57a5fa593c | ||
|
|
421ca3fb3c | ||
|
|
29945c2c7a | ||
|
|
9d82d54c5b | ||
|
|
4827d9984f | ||
|
|
d718fe3ee1 | ||
|
|
a8fbe35ecf | ||
|
|
5947f696ff | ||
|
|
40e0bbeec2 | ||
|
|
ecbf3c5f51 | ||
|
|
dfa952f0d5 | ||
|
|
793577d044 | ||
|
|
1224dc0c87 | ||
|
|
885c1952a4 | ||
|
|
383b3e798b | ||
|
|
1020167e22 | ||
|
|
3c242f58da | ||
|
|
f8a18ce662 | ||
|
|
6a917c5f36 | ||
|
|
7af1f206aa | ||
|
|
0714abfe79 | ||
|
|
6037734641 | ||
|
|
76ba38cec5 | ||
|
|
5c918dc56a | ||
|
|
292ed0e605 | ||
|
|
e97960c2f0 | ||
|
|
9f73341271 | ||
|
|
163515c5a0 | ||
|
|
41845522f6 | ||
|
|
ec86b30d2b | ||
|
|
a90c957463 | ||
|
|
18a2321ddd | ||
|
|
a5f0d457ec | ||
|
|
368b7f3939 | ||
|
|
e4dfbe79e1 | ||
|
|
fdf5fa58d3 | ||
|
|
9ef96e9bb2 | ||
|
|
a8e393496f | ||
|
|
6b302443e6 | ||
|
|
3fdf7a0b88 | ||
|
|
0cb02f1448 | ||
|
|
8b09afdf3e | ||
|
|
23963e854a | ||
|
|
04167cf77e | ||
|
|
6f0a01fcf4 | ||
|
|
0824433260 | ||
|
|
3e0f5ea327 | ||
|
|
a4be48eb32 | ||
|
|
90d03b0afe | ||
|
|
37802e1026 | ||
|
|
16add04ccf | ||
|
|
90c88d7f96 | ||
|
|
2b0e2725f9 | ||
|
|
66bbae586f | ||
|
|
d2ac2df372 | ||
|
|
d5aed20f0a | ||
|
|
42fcdef9f0 | ||
|
|
482811460d | ||
|
|
8fd93030b4 | ||
|
|
5db1ef23fe | ||
|
|
14fc54e323 | ||
|
|
3d1d1f0bb8 | ||
|
|
a5f56027b5 | ||
|
|
bad3ccddc3 | ||
|
|
da35a219d1 | ||
|
|
8209aeea6d | ||
|
|
2d2315c4c4 | ||
|
|
c6a78cee92 | ||
|
|
e0958159f3 | ||
|
|
9d804ba3a8 | ||
|
|
808df20cdb | ||
|
|
c46d2ce791 | ||
|
|
f20528be35 | ||
|
|
5253747c00 | ||
|
|
aaee97c0fa | ||
|
|
d82c40c9fe | ||
|
|
dcc9c4d31a | ||
|
|
3b119fb707 | ||
|
|
b198b65d52 | ||
|
|
f94974cc2c | ||
|
|
d9f9a51e55 | ||
|
|
111f41785f | ||
|
|
e65995cd32 | ||
|
|
ea9d5e3670 | ||
|
|
40b2bf76ae | ||
|
|
6fa110d4fd | ||
|
|
d33d154e14 | ||
|
|
483d329556 | ||
|
|
f3e4615a33 | ||
|
|
9106cee216 | ||
|
|
2d26d95a98 | ||
|
|
5745d030fb | ||
|
|
9f7eccc68f | ||
|
|
3fe047f79c | ||
|
|
9897ce8bf2 | ||
|
|
c33a97fcf2 | ||
|
|
ca4a5d33f0 | ||
|
|
2953dad221 | ||
|
|
f35a11ff37 | ||
|
|
8534107fc8 | ||
|
|
681e2bf213 | ||
|
|
2283c06971 | ||
|
|
bb9435a604 | ||
|
|
96091dfcf5 | ||
|
|
cf4a1ba083 | ||
|
|
4c7b63a215 | ||
|
|
1e04a0e943 | ||
|
|
f7353b1295 | ||
|
|
dbf04985c4 | ||
|
|
f783486057 | ||
|
|
0faef46773 | ||
|
|
cbd7a1bb58 | ||
|
|
19ac0e9327 | ||
|
|
b5cf3a2146 | ||
|
|
5cf0cbe041 | ||
|
|
df5c3ab91e | ||
|
|
5b95db2208 | ||
|
|
22d955b3a9 | ||
|
|
b7fa38e2e7 | ||
|
|
b16c85888e | ||
|
|
261d64ec1d | ||
|
|
62f7cdbb43 | ||
|
|
30814302af | ||
|
|
fd3cd64e11 | ||
|
|
536cd8d45e | ||
|
|
78de5374ed | ||
|
|
aff76e0d0e | ||
|
|
0f4ab07324 | ||
|
|
08da2455dd | ||
|
|
8e0c55f9fa | ||
|
|
00b4beda91 | ||
|
|
1af119db80 | ||
|
|
4eb2757847 | ||
|
|
3cdfbd843b | ||
|
|
0f5a39f328 | ||
|
|
9c8302b2d2 | ||
|
|
e5ea1b0a19 | ||
|
|
e6558fb9fc | ||
|
|
b67f8d2b7b | ||
|
|
5c92f09dd0 | ||
|
|
f2f1125e44 | ||
|
|
6d4ad82262 | ||
|
|
7cf5f8caae | ||
|
|
3d4bf3abbf | ||
|
|
5036a12a65 | ||
|
|
489f6e2e67 | ||
|
|
c9887e8c15 | ||
|
|
239db504ff | ||
|
|
9b7ce3b6ba | ||
|
|
3b86e64faf | ||
|
|
c3ae23d3a5 | ||
|
|
73e28ca556 | ||
|
|
a634da4d19 | ||
|
|
a3dce9409b | ||
|
|
a9cf1975ca | ||
|
|
51ece9412e | ||
|
|
b37258edf0 | ||
|
|
bb9fafa6cc | ||
|
|
f5fee4decf | ||
|
|
279826f6d6 | ||
|
|
56e11b57e3 | ||
|
|
6d65049221 | ||
|
|
65ee4e4f2a | ||
|
|
a1538c5610 | ||
|
|
e32e5c21d7 | ||
|
|
132cdad7c4 | ||
|
|
fa89a0ab4d | ||
|
|
ad093555a6 | ||
|
|
2fbccdd05b | ||
|
|
eb985a8af0 | ||
|
|
8f150d84ae | ||
|
|
74d8575097 | ||
|
|
71c3cf163e | ||
|
|
b95423285f | ||
|
|
24733315d7 | ||
|
|
fbc38d0c60 | ||
|
|
8b5c4aa591 | ||
|
|
c9aff4c47a | ||
|
|
8a8f35863c | ||
|
|
9a5d759230 | ||
|
|
94c3b1212e | ||
|
|
c129a3d3b8 | ||
|
|
6d935b6a4a | ||
|
|
8f903b6e3f | ||
|
|
78139957d2 | ||
|
|
ffffbedf41 | ||
|
|
fb6130e1e0 | ||
|
|
4c94f3ec38 | ||
|
|
d67425daf1 | ||
|
|
48065cc694 | ||
|
|
f35e16bd8d | ||
|
|
986c7cc31b | ||
|
|
078c90cabe | ||
|
|
30597252c7 | ||
|
|
d8b27ef8fe | ||
|
|
19197a490e | ||
|
|
317833aeff | ||
|
|
acf989f1be | ||
|
|
7ab710889c | ||
|
|
8ef87309a2 | ||
|
|
20fa90a137 | ||
|
|
fb355eb320 | ||
|
|
8aa0cc145c | ||
|
|
e276fc71c7 | ||
|
|
119c3e3b24 | ||
|
|
67adc56c73 | ||
|
|
97a771d1e2 | ||
|
|
c27279ce7a | ||
|
|
13c368a2db | ||
|
|
2f47e298d2 | ||
|
|
188ede2cd4 | ||
|
|
f23419fde6 | ||
|
|
c614f4b5de | ||
|
|
3266716584 | ||
|
|
8cc6df51f3 | ||
|
|
6b1ffe13a0 | ||
|
|
ce3daf254f | ||
|
|
6c5a75bf73 | ||
|
|
cdb78cbdd3 |
7
.gitignore
vendored
7
.gitignore
vendored
@@ -4,9 +4,16 @@
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
kustomize
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# We use sed -i.bak when doing in-line replace, because it works better cross-platform
|
||||
.bak
|
||||
|
||||
# macOS
|
||||
*.DS_store
|
||||
|
||||
30
.golangci.yml
Normal file
30
.golangci.yml
Normal file
@@ -0,0 +1,30 @@
|
||||
run:
|
||||
deadline: 5m
|
||||
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- dupl
|
||||
- goconst
|
||||
- gocyclo
|
||||
- gofmt
|
||||
- golint
|
||||
- govet
|
||||
- ineffassign
|
||||
- interfacer
|
||||
- lll
|
||||
- misspell
|
||||
- nakedret
|
||||
- structcheck
|
||||
- unparam
|
||||
- varcheck
|
||||
|
||||
linters-settings:
|
||||
dupl:
|
||||
threshold: 400
|
||||
lll:
|
||||
line-length: 170
|
||||
gocyclo:
|
||||
min-complexity: 15
|
||||
golint:
|
||||
min-confidence: 0.85
|
||||
41
.travis.yml
41
.travis.yml
@@ -1,25 +1,42 @@
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
# TODO: Uncomment when tests running on Windows.
|
||||
# - windows
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- tree
|
||||
homebrew:
|
||||
packages:
|
||||
- tree
|
||||
|
||||
# Only clone the most recent commit.
|
||||
git:
|
||||
depth: 1
|
||||
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.10.x
|
||||
- 1.12
|
||||
|
||||
go_import_path: sigs.k8s.io/kustomize
|
||||
|
||||
env:
|
||||
- GOLANGCI_RELEASE="v1.12"
|
||||
|
||||
before_install:
|
||||
- source ./bin/consider-early-travis-exit.sh
|
||||
- sudo apt-get install tree
|
||||
- go get -u github.com/golang/lint/golint
|
||||
- go get -u golang.org/x/tools/cmd/goimports
|
||||
- go get -u github.com/onsi/ginkgo/ginkgo
|
||||
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $GOPATH/bin ${GOLANGCI_RELEASE}
|
||||
- go get -u github.com/monopole/mdrip
|
||||
- go get -u github.com/fzipp/gocyclo
|
||||
- go get -u gopkg.in/alecthomas/gometalinter.v2 && gometalinter.v2 --install
|
||||
# The following would install Helm if needed for some reason.
|
||||
# - wget https://storage.googleapis.com/kubernetes-helm/helm-v2.13.1-linux-amd64.tar.gz
|
||||
# - tar -xvzf helm-v2.13.1-linux-amd64.tar.gz
|
||||
# - sudo mv linux-amd64/helm /usr/local/bin/helm
|
||||
|
||||
# Install must be set to prevent default `go get` to run.
|
||||
# The dependencies have already been vendored by `dep` so
|
||||
# we don't need to fetch them.
|
||||
install:
|
||||
-
|
||||
# Skip the install process; let pre-commit.sh do it.
|
||||
install: true
|
||||
|
||||
script:
|
||||
- ./bin/pre-commit.sh
|
||||
|
||||
22
CONTRIBUTING.md
Normal file
22
CONTRIBUTING.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# 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:
|
||||
|
||||
_As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities._
|
||||
|
||||
## Getting Started
|
||||
|
||||
We have full documentation on how to get started contributing here:
|
||||
|
||||
- [Contributor License Agreement](https://git.k8s.io/community/CLA.md) Kubernetes projects require that you sign a Contributor License Agreement (CLA) before we can accept your pull requests
|
||||
- [Kubernetes Contributor Guide](http://git.k8s.io/community/contributors/guide) - Main contributor documentation, or you can just jump directly to the [contributing section](http://git.k8s.io/community/contributors/guide#contributing)
|
||||
- [Contributor Cheat Sheet](https://git.k8s.io/community/contributors/guide/contributor-cheatsheet.md) - Common resources for existing developers
|
||||
|
||||
## Mentorship
|
||||
|
||||
- [Mentoring Initiatives](https://git.k8s.io/community/mentoring) - We have a diverse set of mentorship programs available that are always looking for volunteers!
|
||||
|
||||
## Contact Information
|
||||
|
||||
- [Slack channel](https://kubernetes.slack.com/messages/sig-cli)
|
||||
- [Mailing list](https://groups.google.com/forum/#!forum/kubernetes-sig-cli)
|
||||
527
Gopkg.lock
generated
527
Gopkg.lock
generated
@@ -1,527 +0,0 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
digest = "1:8e47871087b94913898333f37af26732faaab30cdb41571136cf7aec9921dae7"
|
||||
name = "github.com/PuerkitoBio/purell"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "0bcb03f4b4d0a9428594752bd2a3b9aa0a9d4bd4"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:331a419049c2be691e5ba1d24342fc77c7e767a80c666a18fd8a9f7b82419c1c"
|
||||
name = "github.com/PuerkitoBio/urlesc"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "de5bf2ad457846296e2031421a34e2568e304e35"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:9299ad32dcec0f92ad06773f73426bd46a21efa96f6a8138c287bb185933e77e"
|
||||
name = "github.com/aws/aws-sdk-go"
|
||||
packages = [
|
||||
"aws",
|
||||
"aws/awserr",
|
||||
"aws/awsutil",
|
||||
"aws/client",
|
||||
"aws/client/metadata",
|
||||
"aws/corehandlers",
|
||||
"aws/credentials",
|
||||
"aws/credentials/ec2rolecreds",
|
||||
"aws/credentials/endpointcreds",
|
||||
"aws/credentials/stscreds",
|
||||
"aws/csm",
|
||||
"aws/defaults",
|
||||
"aws/ec2metadata",
|
||||
"aws/endpoints",
|
||||
"aws/request",
|
||||
"aws/session",
|
||||
"aws/signer/v4",
|
||||
"internal/sdkio",
|
||||
"internal/sdkrand",
|
||||
"internal/sdkuri",
|
||||
"internal/shareddefaults",
|
||||
"private/protocol",
|
||||
"private/protocol/eventstream",
|
||||
"private/protocol/eventstream/eventstreamapi",
|
||||
"private/protocol/query",
|
||||
"private/protocol/query/queryutil",
|
||||
"private/protocol/rest",
|
||||
"private/protocol/restxml",
|
||||
"private/protocol/xml/xmlutil",
|
||||
"service/s3",
|
||||
"service/sts",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "daed0c76021ea9c4e659e3ec80bcd2d657297100"
|
||||
version = "v1.15.12"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:98e84060475ed245c3b355042afd43a74aa7d32efe50658f4f995977916f9fc3"
|
||||
name = "github.com/bgentry/go-netrc"
|
||||
packages = ["netrc"]
|
||||
pruneopts = ""
|
||||
revision = "9fd32a8b3d3d3f9d43c341bfe098430e07609480"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:56c130d885a4aacae1dd9c7b71cfe39912c7ebc1ff7d2b46083c8812996dc43b"
|
||||
name = "github.com/davecgh/go-spew"
|
||||
packages = ["spew"]
|
||||
pruneopts = ""
|
||||
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:971e9ba63a417c5f1f83ab358677bc59e96ff04285f26c6646ff089fb60b15e8"
|
||||
name = "github.com/emicklei/go-restful"
|
||||
packages = [
|
||||
".",
|
||||
"log",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "3658237ded108b4134956c1b3050349d93e7b895"
|
||||
version = "v2.7.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:dcefbadf4534c5ecac8573698fba6e6e601157bfa8f96aafe29df31ae582ef2a"
|
||||
name = "github.com/evanphx/json-patch"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "afac545df32f2287a079e2dfb7ba2745a643747e"
|
||||
version = "v3.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:b13707423743d41665fd23f0c36b2f37bb49c30e94adb813319c44188a51ba22"
|
||||
name = "github.com/ghodss/yaml"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "0ca9ea5df5451ffdf184b4428c902747c2c11cd7"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:858b7fe7b0f4bc7ef9953926828f2816ea52d01a88d72d1c45bc8c108f23c356"
|
||||
name = "github.com/go-ini/ini"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "358ee7663966325963d4e8b2e1fbd570c5195153"
|
||||
version = "v1.38.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:e116a4866bffeec941056a1fcfd37e520fad1ee60e4e3579719f19a43c392e10"
|
||||
name = "github.com/go-openapi/jsonpointer"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "3a0015ad55fa9873f41605d3e8f28cd279c32ab2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:3830527ef0f4f9b268d9286661c0f52f9115f8aefd9f45ee7352516f93489ac9"
|
||||
name = "github.com/go-openapi/jsonreference"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "3fb327e6747da3043567ee86abd02bb6376b6be2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:238a056875c4b053b4b29984765ee335bf8c539fdf17e527fd9b7aa72521c8dd"
|
||||
name = "github.com/go-openapi/spec"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "bcff419492eeeb01f76e77d2ebc714dc97b607f5"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:7b067ca8b94982960860d18c42e29f15bbd0e8d9ae8145a83a218296e75393cf"
|
||||
name = "github.com/go-openapi/swag"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "811b1089cde9dad18d4d0c2d09fbdbf28dbd27a5"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:0a3f6a0c68ab8f3d455f8892295503b179e571b7fefe47cc6c556405d1f83411"
|
||||
name = "github.com/gogo/protobuf"
|
||||
packages = [
|
||||
"proto",
|
||||
"sortkeys",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "1adfc126b41513cc696b209667c8656ea7aac67c"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:107b233e45174dbab5b1324201d092ea9448e58243ab9f039e4c0f332e121e3a"
|
||||
name = "github.com/golang/glog"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "23def4e6c14b4da8ac2ed8007337bc5eb5007998"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:f958a1c137db276e52f0b50efee41a1a389dcdded59a69711f3e872757dab34b"
|
||||
name = "github.com/golang/protobuf"
|
||||
packages = [
|
||||
"proto",
|
||||
"ptypes",
|
||||
"ptypes/any",
|
||||
"ptypes/duration",
|
||||
"ptypes/timestamp",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "b4deda0973fb4c70b50d226b1af49f3da59f5265"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:754f77e9c839b24778a4b64422236d38515301d2baeb63113aa3edc42e6af692"
|
||||
name = "github.com/google/gofuzz"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "24818f796faf91cd76ec7bddd72458fbced7a6c1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:2a131706ff80636629ab6373f2944569b8252ecc018cda8040931b05d32e3c16"
|
||||
name = "github.com/googleapis/gnostic"
|
||||
packages = [
|
||||
"OpenAPIv2",
|
||||
"compiler",
|
||||
"extensions",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "ee43cbb60db7bd22502942cccbc39059117352ab"
|
||||
version = "v0.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:f5d25fd7bdda08e39e01193ef94a1ebf7547b1b931bcdec785d08050598f306c"
|
||||
name = "github.com/hashicorp/go-cleanhttp"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "d5fe4b57a186c716b0e00b8c301cbd9b4182694d"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:fd15b3f6aac9d0fe68c6e38922282e0d2e88cd77b927ac3dd842e363645522c0"
|
||||
name = "github.com/hashicorp/go-getter"
|
||||
packages = [
|
||||
".",
|
||||
"helper/url",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "4bda8fa99001c61db3cad96b421d4c12a81f256d"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:2cf6c60c74eacadd31652674364af55c8d54a86b8ea193548f1c37f8c9af8f9c"
|
||||
name = "github.com/hashicorp/go-safetemp"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "b1a1dbde6fdc11e3ae79efd9039009e22d4ae240"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:139bdc2c89779b8ff8b1150be28f889b0ed964e6da96f32cbc9035bd4642881c"
|
||||
name = "github.com/hashicorp/go-version"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "270f2f71b1ee587f3b609f00f422b76a6b28f348"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be"
|
||||
name = "github.com/inconshreveable/mousetrap"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
|
||||
version = "v1.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:6f49eae0c1e5dab1dafafee34b207aeb7a42303105960944828c2079b92fc88e"
|
||||
name = "github.com/jmespath/go-jmespath"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "0b12d6b5"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:9eab2325abbed0ebcee9d44bb3660a69d5d10e42d5ac4a0e77f7a6ea22bfce88"
|
||||
name = "github.com/json-iterator/go"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "ca39e5af3ece67bbcda3d0f4f56a8e24d9f2dad4"
|
||||
version = "1.1.3"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:10e1dfc240bcd0fce14366215fd2b070e79d9b9460b27382db4423ad74204f2b"
|
||||
name = "github.com/krishicks/yaml-patch"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "83cc9ac50becbbfafb86a89167f3bc5372e8e530"
|
||||
version = "v0.0.10"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:d9e483f4b9e306facf126bd90b02d512bd22ea4471e1568867e32221a8abbb16"
|
||||
name = "github.com/mailru/easyjson"
|
||||
packages = [
|
||||
"buffer",
|
||||
"jlexer",
|
||||
"jwriter",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "3fdea8d05856a0c8df22ed4bc71b3219245e4485"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:83854f6b1d2ce047b69657e3a87ba7602f4c5505e8bdfd02ab857db8e983bde1"
|
||||
name = "github.com/mitchellh/go-homedir"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "58046073cbffe2f25d425fe1331102f55cf719de"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:51c98e2c9a8d0a724a69f46421876af14e12132cb02f1d0e144785d752247162"
|
||||
name = "github.com/mitchellh/go-testing-interface"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "a61a99592b77c9ba629d254a693acffaeb4b7e28"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:0c0ff2a89c1bb0d01887e1dac043ad7efbf3ec77482ef058ac423d13497e16fd"
|
||||
name = "github.com/modern-go/concurrent"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94"
|
||||
version = "1.0.3"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:420f9231f816eeca3ff5aab070caac3ed7f27e4d37ded96ce9de3d7a7a2e31ad"
|
||||
name = "github.com/modern-go/reflect2"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "1df9eeb2bb81f327b96228865c5687bc2194af3f"
|
||||
version = "1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:7365acd48986e205ccb8652cc746f09c8b7876030d53710ea6ef7d0bd0dcd7ca"
|
||||
name = "github.com/pkg/errors"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
|
||||
version = "v0.8.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:74c32990510c9f188556aa17600313e867d1d06f5a9db244056a95d144ec34ce"
|
||||
name = "github.com/spf13/cobra"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "a1f051bc3eba734da4772d60e2d677f47cf93ef4"
|
||||
version = "v0.0.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:8e243c568f36b09031ec18dff5f7d2769dcf5ca4d624ea511c8e3197dc3d352d"
|
||||
name = "github.com/spf13/pflag"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "583c0c0531f06d5278b7d917446061adc344b5cd"
|
||||
version = "v1.0.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:ee723e6a1962a196eeba1b24f82af61a4f60f8821d7aa96d48e787f8337bcffc"
|
||||
name = "github.com/ulikunitz/xz"
|
||||
packages = [
|
||||
".",
|
||||
"internal/hash",
|
||||
"internal/xlog",
|
||||
"lzma",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "0c6b41e72360850ca4f98dc341fd999726ea007f"
|
||||
version = "v0.5.4"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:9e548233d0dc00e74be262e54a9d1bbe7e4c19e5951083520261740e37daeb02"
|
||||
name = "golang.org/x/net"
|
||||
packages = [
|
||||
"http/httpguts",
|
||||
"http2",
|
||||
"http2/hpack",
|
||||
"idna",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "2491c5de3490fced2f6cff376127c667efeed857"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:5acd3512b047305d49e8763eef7ba423901e85d5dd2fd1e71778a0ea8de10bd4"
|
||||
name = "golang.org/x/text"
|
||||
packages = [
|
||||
"collate",
|
||||
"collate/build",
|
||||
"internal/colltab",
|
||||
"internal/gen",
|
||||
"internal/tag",
|
||||
"internal/triegen",
|
||||
"internal/ucd",
|
||||
"language",
|
||||
"secure/bidirule",
|
||||
"transform",
|
||||
"unicode/bidi",
|
||||
"unicode/cldr",
|
||||
"unicode/norm",
|
||||
"unicode/rangetable",
|
||||
"width",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
|
||||
version = "v0.3.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:75fb3fcfc73a8c723efde7777b40e8e8ff9babf30d8c56160d01beffea8a95a6"
|
||||
name = "gopkg.in/inf.v0"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "d2d2541c53f18d2a059457998ce2876cc8e67cbf"
|
||||
version = "v0.9.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:f0620375dd1f6251d9973b5f2596228cc8042e887cd7f827e4220bc1ce8c30e2"
|
||||
name = "gopkg.in/yaml.v2"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183"
|
||||
version = "v2.2.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:663df6da5560210fc39194a0a2c4fceba09ead717c330f1174bb15597cf18ce8"
|
||||
name = "k8s.io/api"
|
||||
packages = [
|
||||
"admissionregistration/v1alpha1",
|
||||
"admissionregistration/v1beta1",
|
||||
"apps/v1",
|
||||
"apps/v1beta1",
|
||||
"apps/v1beta2",
|
||||
"authentication/v1",
|
||||
"authentication/v1beta1",
|
||||
"authorization/v1",
|
||||
"authorization/v1beta1",
|
||||
"autoscaling/v1",
|
||||
"autoscaling/v2beta1",
|
||||
"batch/v1",
|
||||
"batch/v1beta1",
|
||||
"batch/v2alpha1",
|
||||
"certificates/v1beta1",
|
||||
"core/v1",
|
||||
"events/v1beta1",
|
||||
"extensions/v1beta1",
|
||||
"networking/v1",
|
||||
"policy/v1beta1",
|
||||
"rbac/v1",
|
||||
"rbac/v1alpha1",
|
||||
"rbac/v1beta1",
|
||||
"scheduling/v1alpha1",
|
||||
"settings/v1alpha1",
|
||||
"storage/v1",
|
||||
"storage/v1alpha1",
|
||||
"storage/v1beta1",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "53d615ae3f440f957cb9989d989d597f047262d9"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:bcb2285bb525712de7903a5d254c2789df65c8b58d2cfac5a26d950ad94c2079"
|
||||
name = "k8s.io/apimachinery"
|
||||
packages = [
|
||||
"pkg/api/equality",
|
||||
"pkg/api/meta",
|
||||
"pkg/api/resource",
|
||||
"pkg/api/validation",
|
||||
"pkg/apis/meta/v1",
|
||||
"pkg/apis/meta/v1/unstructured",
|
||||
"pkg/apis/meta/v1/validation",
|
||||
"pkg/apis/meta/v1beta1",
|
||||
"pkg/conversion",
|
||||
"pkg/conversion/queryparams",
|
||||
"pkg/fields",
|
||||
"pkg/labels",
|
||||
"pkg/runtime",
|
||||
"pkg/runtime/schema",
|
||||
"pkg/runtime/serializer",
|
||||
"pkg/runtime/serializer/json",
|
||||
"pkg/runtime/serializer/protobuf",
|
||||
"pkg/runtime/serializer/recognizer",
|
||||
"pkg/runtime/serializer/versioning",
|
||||
"pkg/selection",
|
||||
"pkg/types",
|
||||
"pkg/util/errors",
|
||||
"pkg/util/framer",
|
||||
"pkg/util/intstr",
|
||||
"pkg/util/json",
|
||||
"pkg/util/mergepatch",
|
||||
"pkg/util/net",
|
||||
"pkg/util/runtime",
|
||||
"pkg/util/sets",
|
||||
"pkg/util/strategicpatch",
|
||||
"pkg/util/validation",
|
||||
"pkg/util/validation/field",
|
||||
"pkg/util/wait",
|
||||
"pkg/util/yaml",
|
||||
"pkg/watch",
|
||||
"third_party/forked/golang/json",
|
||||
"third_party/forked/golang/reflect",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "13b73596e4b63e03203e86f6d9c7bcc1b937c62f"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:071cc2f032b701b9dba26568e040940f26931a49e3a3985f3375f17f7f6d9c5f"
|
||||
name = "k8s.io/client-go"
|
||||
packages = ["kubernetes/scheme"]
|
||||
pruneopts = ""
|
||||
revision = "23781f4d6632d88e869066eaebb743857aa1ef9b"
|
||||
version = "v7.0.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:386c5d69077ce740614e8309ddf107dde91a5db25d3d779143f452fb4fbdfd1e"
|
||||
name = "k8s.io/kube-openapi"
|
||||
packages = [
|
||||
"pkg/common",
|
||||
"pkg/util/proto",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "b3f03f55328800731ce03a164b80973014ecd455"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
input-imports = [
|
||||
"github.com/evanphx/json-patch",
|
||||
"github.com/ghodss/yaml",
|
||||
"github.com/golang/glog",
|
||||
"github.com/hashicorp/go-getter",
|
||||
"github.com/krishicks/yaml-patch",
|
||||
"github.com/pkg/errors",
|
||||
"github.com/spf13/cobra",
|
||||
"gopkg.in/yaml.v2",
|
||||
"k8s.io/api/core/v1",
|
||||
"k8s.io/apimachinery/pkg/api/validation",
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured",
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/validation",
|
||||
"k8s.io/apimachinery/pkg/runtime",
|
||||
"k8s.io/apimachinery/pkg/runtime/schema",
|
||||
"k8s.io/apimachinery/pkg/util/mergepatch",
|
||||
"k8s.io/apimachinery/pkg/util/sets",
|
||||
"k8s.io/apimachinery/pkg/util/strategicpatch",
|
||||
"k8s.io/apimachinery/pkg/util/validation",
|
||||
"k8s.io/apimachinery/pkg/util/validation/field",
|
||||
"k8s.io/apimachinery/pkg/util/yaml",
|
||||
"k8s.io/client-go/kubernetes/scheme",
|
||||
"k8s.io/kube-openapi/pkg/common",
|
||||
]
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
66
Gopkg.toml
66
Gopkg.toml
@@ -1,66 +0,0 @@
|
||||
|
||||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project"
|
||||
# version = "1.0.0"
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project2"
|
||||
# branch = "dev"
|
||||
# source = "github.com/myfork/project2"
|
||||
#
|
||||
# [[override]]
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/evanphx/json-patch"
|
||||
version = "3.0.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/ghodss/yaml"
|
||||
version = "1.0.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/golang/glog"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/spf13/cobra"
|
||||
version = "0.0.2"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "k8s.io/api"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "k8s.io/apimachinery"
|
||||
|
||||
[[constraint]]
|
||||
name = "k8s.io/client-go"
|
||||
version = "7.0.0"
|
||||
|
||||
[[override]]
|
||||
branch = "master"
|
||||
name = "k8s.io/utils"
|
||||
|
||||
[[override]]
|
||||
branch = "master"
|
||||
name = "github.com/go-openapi/spec"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/hashicorp/go-getter"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/krishicks/yaml-patch"
|
||||
version = "0.0.10"
|
||||
@@ -1,6 +1,5 @@
|
||||
aliases:
|
||||
kustomize-admins:
|
||||
- grodrigues3
|
||||
- monopole
|
||||
- pwittrock
|
||||
kustomize-maintainers:
|
||||
|
||||
82
README.md
82
README.md
@@ -9,6 +9,10 @@ patch [kubernetes style] API objects. It's like
|
||||
[`make`], in that what it does is declared in a file,
|
||||
and it's like [`sed`], in that it emits editted text.
|
||||
|
||||
This tool is sponsored by [sig-cli] ([KEP]), and
|
||||
inspired by [DAM].
|
||||
|
||||
|
||||
[](https://travis-ci.org/kubernetes-sigs/kustomize)
|
||||
[](https://goreportcard.com/report/github.com/kubernetes-sigs/kustomize)
|
||||
|
||||
@@ -16,6 +20,11 @@ and it's like [`sed`], in that it emits editted text.
|
||||
page], or see these [install] notes. Then try one of
|
||||
the tested [examples].
|
||||
|
||||
**Note** Kustomize is now available on kubectl v1.14 and can be used by specifying a directory containing a `kustomization.yaml`:
|
||||
```shell
|
||||
kubectl apply -k dir/
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
|
||||
@@ -113,21 +122,87 @@ The YAML can be directly [applied] to a cluster:
|
||||
> kustomize build ~/someApp/overlays/production | kubectl apply -f -
|
||||
> ```
|
||||
|
||||
## About
|
||||
## Community
|
||||
|
||||
This tool is sponsored by [sig-cli] ([KEP]).
|
||||
### Filing bug reports
|
||||
|
||||
|
||||
[KEP]: https://github.com/kubernetes/community/blob/master/keps/sig-cli/0008-kustomize.md
|
||||
##### A good report specifies
|
||||
|
||||
* the output of `kustomize version`,
|
||||
* the input (the content of `kustomization.yaml`
|
||||
and any files it refers to),
|
||||
* the expected YAML output.
|
||||
|
||||
##### A _great_ report is a bug reproduction test
|
||||
|
||||
Kustomize has a simple test harness in the
|
||||
[target package] for specifying a kustomization's
|
||||
input and the expected output.
|
||||
See this [example of a target test].
|
||||
|
||||
The pattern is
|
||||
* call `NewKustTestHarness`
|
||||
* specify kustomization input data (resources,
|
||||
patches, etc.) as inline strings,
|
||||
* call `makeKustTarget().MakeCustomizedResMap()`
|
||||
* compare the actual output to expected output
|
||||
|
||||
In a bug reproduction test, the expected output string
|
||||
initially contains the _wrong_ (unexpected) output,
|
||||
thus unambiguously reproducing the bug.
|
||||
|
||||
Nearby comments should explain what the output
|
||||
_should_ be, and have a TODO pointing to the related
|
||||
issue.
|
||||
|
||||
The person who fixes the bug then has a clear
|
||||
bug reproduction and a test to modify when
|
||||
the bug is fixed.
|
||||
|
||||
The bug reporter can then see the bug was fixed,
|
||||
and has permanent regression coverage to prevent
|
||||
its reintroduction.
|
||||
|
||||
### Feature requests
|
||||
|
||||
Feature requests are welcome.
|
||||
|
||||
Before working on an implementation, please
|
||||
* Read the [eschewed feature list].
|
||||
* File an issue describing
|
||||
how the new feature would behave
|
||||
and label it [kind/feature].
|
||||
|
||||
### Other communication channels
|
||||
|
||||
- [Slack]
|
||||
- [Mailing List]
|
||||
- General kubernetes [community page]
|
||||
|
||||
### Code of conduct
|
||||
|
||||
Participation in the Kubernetes community
|
||||
is governed by the [Kubernetes Code of Conduct].
|
||||
|
||||
[`make`]: https://www.gnu.org/software/make
|
||||
[`sed`]: https://www.gnu.org/software/sed
|
||||
[DAM]: docs/glossary.md#declarative-application-management
|
||||
[KEP]: https://github.com/kubernetes/enhancements/blob/master/keps/sig-cli/0008-kustomize.md
|
||||
[Kubernetes Code of Conduct]: code-of-conduct.md
|
||||
[Mailing List]: https://groups.google.com/forum/#!forum/kubernetes-sig-cli
|
||||
[Slack]: https://kubernetes.slack.com/messages/sig-cli
|
||||
[applied]: docs/glossary.md#apply
|
||||
[base]: docs/glossary.md#base
|
||||
[community page]: http://kubernetes.io/community/
|
||||
[declarative configuration]: docs/glossary.md#declarative-application-management
|
||||
[eschewed feature list]: docs/eschewedFeatures.md
|
||||
[example of a target test]: https://github.com/kubernetes-sigs/kustomize/blob/master/pkg/target/baseandoverlaysmall_test.go
|
||||
[examples]: examples/README.md
|
||||
[imageBase]: docs/base.jpg
|
||||
[imageOverlay]: docs/overlay.jpg
|
||||
[install]: docs/INSTALL.md
|
||||
[kind/feature]: https://github.com/kubernetes-sigs/kustomize/labels/kind%2Ffeature
|
||||
[kubernetes style]: docs/glossary.md#kubernetes-style-object
|
||||
[kustomization]: docs/glossary.md#kustomization
|
||||
[overlay]: docs/glossary.md#overlay
|
||||
@@ -136,6 +211,7 @@ This tool is sponsored by [sig-cli] ([KEP]).
|
||||
[resource]: docs/glossary.md#resource
|
||||
[resources]: docs/glossary.md#resource
|
||||
[sig-cli]: https://github.com/kubernetes/community/blob/master/sig-cli/README.md
|
||||
[target package]: https://github.com/kubernetes-sigs/kustomize/tree/master/pkg/target
|
||||
[variant]: docs/glossary.md#variant
|
||||
[variants]: docs/glossary.md#variant
|
||||
[workflows]: docs/workflows.md
|
||||
|
||||
@@ -11,10 +11,6 @@ cd "$base_dir" || {
|
||||
|
||||
rc=0
|
||||
|
||||
function go_dirs {
|
||||
go list -f '{{.Dir}}' ./... | tail -n +2 | tr '\n' '\0'
|
||||
}
|
||||
|
||||
function runTest {
|
||||
local name=$1
|
||||
local result="SUCCESS"
|
||||
@@ -28,56 +24,76 @@ function runTest {
|
||||
printf "============== end %s : %s code=%d\n\n\n" "$name" "$result" $code
|
||||
}
|
||||
|
||||
function testGoFmt {
|
||||
diff <(echo -n) <(go_dirs | xargs -0 gofmt -s -d -l)
|
||||
}
|
||||
|
||||
|
||||
function testGoCyclo {
|
||||
diff <(echo -n) <(go_dirs | xargs -0 gocyclo -over 15)
|
||||
}
|
||||
|
||||
function testGoLint {
|
||||
diff -u <(echo -n) <(go_dirs | xargs -0 golint --min_confidence 0.85 )
|
||||
}
|
||||
|
||||
# Not using the 'goimports' check because it reports hyphens in imported
|
||||
# package names as errors, and we vendor in packages that have
|
||||
# hyphens in their names.
|
||||
function testGoMetalinter {
|
||||
diff -u <(echo -n) <(go_dirs | xargs -0 gometalinter.v2 --disable-all --deadline 5m \
|
||||
--enable=misspell \
|
||||
--enable=structcheck \
|
||||
--enable=deadcode \
|
||||
--enable=varcheck \
|
||||
--enable=goconst \
|
||||
--enable=unparam \
|
||||
--enable=ineffassign \
|
||||
--enable=nakedret \
|
||||
--enable=interfacer \
|
||||
--enable=misspell \
|
||||
--line-length=170 --enable=lll \
|
||||
--dupl-threshold=400 --enable=dupl)
|
||||
}
|
||||
|
||||
function testGoVet {
|
||||
go vet -all ./...
|
||||
function testGoLangCILint {
|
||||
golangci-lint run ./...
|
||||
}
|
||||
|
||||
function testGoTest {
|
||||
go test -v ./...
|
||||
}
|
||||
|
||||
function testNoTravisGoTest {
|
||||
go test -v sigs.k8s.io/kustomize/pkg/target \
|
||||
-run TestChartInflatorExecPlugin -tags=notravis
|
||||
}
|
||||
|
||||
function testExamples {
|
||||
mdrip --mode test --label test README.md ./examples
|
||||
}
|
||||
|
||||
runTest testGoFmt
|
||||
runTest testGoMetalinter
|
||||
runTest testGoLint
|
||||
runTest testGoVet
|
||||
runTest testGoCyclo
|
||||
# Use of GOPATH is optional if go modules are
|
||||
# used. This script tries to work for people who
|
||||
# don't have GOPATH set, and work for travis.
|
||||
#
|
||||
# Upon entry, travis has GOPATH set, and used it
|
||||
# to install mdrip and the like.
|
||||
#
|
||||
# Use GOPATH to define XDG_CONFIG_HOME, then unset
|
||||
# GOPATH so that go.mod is unambiguously honored.
|
||||
echo "GOPATH=$GOPATH"
|
||||
|
||||
if [ -z ${GOPATH+x} ]; then
|
||||
echo GOPATH is unset
|
||||
tmp=$HOME/gopath
|
||||
if [ -d "$tmp" ]; then
|
||||
oldGoPath=$tmp
|
||||
else
|
||||
tmp=$HOME/go
|
||||
if [ -d "$tmp" ]; then
|
||||
oldGoPath=$tmp
|
||||
fi
|
||||
fi
|
||||
else
|
||||
oldGoPath=$GOPATH
|
||||
unset GOPATH
|
||||
fi
|
||||
echo "oldGoPath=$oldGoPath"
|
||||
export XDG_CONFIG_HOME=$oldGoPath/src/sigs.k8s.io
|
||||
echo "XDG_CONFIG_HOME=$XDG_CONFIG_HOME"
|
||||
if [ ! -d "$XDG_CONFIG_HOME" ]; then
|
||||
echo "$XDG_CONFIG_HOME is not a directory."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Until go v1.13, set this explicitly.
|
||||
export GO111MODULE=on
|
||||
|
||||
echo "HOME=$HOME"
|
||||
echo "GOPATH=$GOPATH"
|
||||
echo "GO111MODULE=$GO111MODULE"
|
||||
echo pwd=`pwd`
|
||||
echo " "
|
||||
echo "Beginning tests..."
|
||||
|
||||
runTest testGoLangCILint
|
||||
runTest testGoTest
|
||||
|
||||
if [ -z ${TRAVIS+x} ]; then
|
||||
echo Not on travis, so running the notravis tests
|
||||
runTest testNoTravisGoTest
|
||||
fi
|
||||
|
||||
PATH=$HOME/go/bin:$PATH
|
||||
runTest testExamples
|
||||
|
||||
if [ $rc -eq 0 ]; then
|
||||
|
||||
@@ -4,7 +4,7 @@ project_name: kustomize
|
||||
builds:
|
||||
- main: ./kustomize.go
|
||||
binary: kustomize
|
||||
ldflags: -s -X sigs.k8s.io/kustomize/pkg/commands.kustomizeVersion={{.Version}} -X sigs.k8s.io/kustomize/pkg/commands.gitCommit={{.Commit}} -X sigs.k8s.io/kustomize/pkg/commands.buildDate={{.Date}}
|
||||
ldflags: -s -X sigs.k8s.io/kustomize/pkg/commands/misc.kustomizeVersion={{.Version}} -X sigs.k8s.io/kustomize/pkg/commands/misc.gitCommit={{.Commit}} -X sigs.k8s.io/kustomize/pkg/commands/misc.buildDate={{.Date}}
|
||||
goos:
|
||||
- darwin
|
||||
- linux
|
||||
|
||||
281
build/vendor_kustomize.diff
Executable file
281
build/vendor_kustomize.diff
Executable file
@@ -0,0 +1,281 @@
|
||||
commit 1b893558aa83ac6491e5ba416b493170a9045fec
|
||||
Author: Jingfang Liu <jingfangliu@google.com>
|
||||
Date: Mon Nov 12 10:26:12 2018 -0800
|
||||
|
||||
last change
|
||||
|
||||
diff --git a/staging/src/k8s.io/cli-runtime/artifacts/kustomization/configMap.yaml b/staging/src/k8s.io/cli-runtime/artifacts/kustomization/configMap.yaml
|
||||
new file mode 100644
|
||||
index 0000000000..0008853094
|
||||
--- /dev/null
|
||||
+++ b/staging/src/k8s.io/cli-runtime/artifacts/kustomization/configMap.yaml
|
||||
@@ -0,0 +1,8 @@
|
||||
+
|
||||
+apiVersion: v1
|
||||
+kind: ConfigMap
|
||||
+metadata:
|
||||
+ name: the-map
|
||||
+data:
|
||||
+ altGreeting: "Good Morning!"
|
||||
+ enableRisky: "false"
|
||||
diff --git a/staging/src/k8s.io/cli-runtime/artifacts/kustomization/deployment.yaml b/staging/src/k8s.io/cli-runtime/artifacts/kustomization/deployment.yaml
|
||||
new file mode 100644
|
||||
index 0000000000..6e79409080
|
||||
--- /dev/null
|
||||
+++ b/staging/src/k8s.io/cli-runtime/artifacts/kustomization/deployment.yaml
|
||||
@@ -0,0 +1,30 @@
|
||||
+apiVersion: apps/v1
|
||||
+kind: Deployment
|
||||
+metadata:
|
||||
+ name: the-deployment
|
||||
+spec:
|
||||
+ replicas: 3
|
||||
+ template:
|
||||
+ metadata:
|
||||
+ labels:
|
||||
+ deployment: hello
|
||||
+ spec:
|
||||
+ containers:
|
||||
+ - name: the-container
|
||||
+ image: monopole/hello:1
|
||||
+ command: ["/hello",
|
||||
+ "--port=8080",
|
||||
+ "--enableRiskyFeature=$(ENABLE_RISKY)"]
|
||||
+ ports:
|
||||
+ - containerPort: 8080
|
||||
+ env:
|
||||
+ - name: ALT_GREETING
|
||||
+ valueFrom:
|
||||
+ configMapKeyRef:
|
||||
+ name: the-map
|
||||
+ key: altGreeting
|
||||
+ - name: ENABLE_RISKY
|
||||
+ valueFrom:
|
||||
+ configMapKeyRef:
|
||||
+ name: the-map
|
||||
+ key: enableRisky
|
||||
diff --git a/staging/src/k8s.io/cli-runtime/artifacts/kustomization/kustomization.yaml b/staging/src/k8s.io/cli-runtime/artifacts/kustomization/kustomization.yaml
|
||||
new file mode 100644
|
||||
index 0000000000..6e1e3202d5
|
||||
--- /dev/null
|
||||
+++ b/staging/src/k8s.io/cli-runtime/artifacts/kustomization/kustomization.yaml
|
||||
@@ -0,0 +1,5 @@
|
||||
+nameprefix: test-
|
||||
+ resources:
|
||||
+- deployment.yaml
|
||||
+- service.yaml
|
||||
+- configMap.yaml
|
||||
diff --git a/staging/src/k8s.io/cli-runtime/artifacts/kustomization/service.yaml b/staging/src/k8s.io/cli-runtime/artifacts/kustomization/service.yaml
|
||||
new file mode 100644
|
||||
index 0000000000..2942cdb7df
|
||||
--- /dev/null
|
||||
+++ b/staging/src/k8s.io/cli-runtime/artifacts/kustomization/service.yaml
|
||||
@@ -0,0 +1,13 @@
|
||||
+kind: Service
|
||||
+apiVersion: v1
|
||||
+metadata:
|
||||
+ name: the-service
|
||||
+spec:
|
||||
+ selector:
|
||||
+ deployment: hello
|
||||
+ type: LoadBalancer
|
||||
+ ports:
|
||||
+ - protocol: TCP
|
||||
+ port: 8666
|
||||
+ targetPort: 8080
|
||||
+
|
||||
\ No newline at end of file
|
||||
diff --git a/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/BUILD b/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/BUILD
|
||||
index 22b34de008..b91d1c0130 100644
|
||||
--- a/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/BUILD
|
||||
+++ b/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/BUILD
|
||||
@@ -35,12 +35,15 @@ go_library(
|
||||
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/util/yaml:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/watch:go_default_library",
|
||||
+ "//staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/discovery:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/kubernetes/scheme:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/rest:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/restmapper:go_default_library",
|
||||
"//vendor/golang.org/x/text/encoding/unicode:go_default_library",
|
||||
"//vendor/golang.org/x/text/transform:go_default_library",
|
||||
+ "//vendor/sigs.k8s.io/kustomize/pkg/commands/build:go_default_library",
|
||||
+ "//vendor/sigs.k8s.io/kustomize/pkg/fs:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
diff --git a/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/builder_test.go b/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/builder_test.go
|
||||
index 7fd526b33c..801f13f772 100644
|
||||
--- a/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/builder_test.go
|
||||
+++ b/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/builder_test.go
|
||||
@@ -465,27 +465,48 @@ func TestPathBuilderWithMultipleInvalid(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDirectoryBuilder(t *testing.T) {
|
||||
- b := newDefaultBuilder().
|
||||
- FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{"../../../artifacts/guestbook"}}).
|
||||
- NamespaceParam("test").DefaultNamespace()
|
||||
+ tests := []struct {
|
||||
+ directories []string
|
||||
+ singleItem bool
|
||||
+ number int
|
||||
+ expectedNames []string
|
||||
+ }{
|
||||
+ {[]string{"../../../artifacts/guestbook"}, false, 3, []string{"redis-master"}},
|
||||
+ {[]string{"../../../artifacts/kustomization"}, true, 3, []string{"test-the-deployment"}},
|
||||
+ {[]string{"../../../artifacts/guestbook", "../../../artifacts/kustomization"}, false, 6, []string{"redis-master", "test-the-deployment"}},
|
||||
+ }
|
||||
|
||||
- test := &testVisitor{}
|
||||
- singleItemImplied := false
|
||||
+ for _, tt := range tests {
|
||||
+ b := newDefaultBuilder().
|
||||
+ FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: tt.directories}).
|
||||
+ NamespaceParam("test").DefaultNamespace()
|
||||
|
||||
- err := b.Do().IntoSingleItemImplied(&singleItemImplied).Visit(test.Handle)
|
||||
- if err != nil || singleItemImplied || len(test.Infos) < 3 {
|
||||
- t.Fatalf("unexpected response: %v %t %#v", err, singleItemImplied, test.Infos)
|
||||
- }
|
||||
+ test := &testVisitor{}
|
||||
+ singleItemImplied := false
|
||||
|
||||
- found := false
|
||||
- for _, info := range test.Infos {
|
||||
- if info.Name == "redis-master" && info.Namespace == "test" && info.Object != nil {
|
||||
- found = true
|
||||
- break
|
||||
+ err := b.Do().IntoSingleItemImplied(&singleItemImplied).Visit(test.Handle)
|
||||
+ if err != nil || singleItemImplied != tt.singleItem || len(test.Infos) < tt.number {
|
||||
+ t.Fatalf("unexpected response: %v %t %#v", err, singleItemImplied, test.Infos)
|
||||
+ }
|
||||
+
|
||||
+ contained := func(name string) bool {
|
||||
+ for _, info := range test.Infos {
|
||||
+ if info.Name == name && info.Namespace == "test" && info.Object != nil {
|
||||
+ return true
|
||||
+ }
|
||||
+ }
|
||||
+ return false
|
||||
+ }
|
||||
+
|
||||
+ allFound := true
|
||||
+ for _, name := range tt.expectedNames {
|
||||
+ if !contained(name) {
|
||||
+ allFound = false
|
||||
+ }
|
||||
+ }
|
||||
+ if !allFound {
|
||||
+ t.Errorf("unexpected responses: %#v", test.Infos)
|
||||
}
|
||||
- }
|
||||
- if !found {
|
||||
- t.Errorf("unexpected responses: %#v", test.Infos)
|
||||
}
|
||||
}
|
||||
|
||||
diff --git a/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/visitor.go b/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/visitor.go
|
||||
index 32c1a691a5..d7a37e1cde 100644
|
||||
--- a/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/visitor.go
|
||||
+++ b/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/visitor.go
|
||||
@@ -20,10 +20,12 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
+ "io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
+ "strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/text/encoding/unicode"
|
||||
@@ -38,6 +40,9 @@ import (
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
+ "k8s.io/cli-runtime/pkg/kustomize/k8sdeps"
|
||||
+ "sigs.k8s.io/kustomize/pkg/commands/build"
|
||||
+ "sigs.k8s.io/kustomize/pkg/fs"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -452,7 +457,10 @@ func ExpandPathsToFileVisitors(mapper *mapper, paths string, recursive bool, ext
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
-
|
||||
+ if isKustomizationDir(path) {
|
||||
+ visitors = append(visitors, NewKustomizationVisitor(mapper, path, schema))
|
||||
+ return filepath.SkipDir
|
||||
+ }
|
||||
if fi.IsDir() {
|
||||
if path != paths && !recursive {
|
||||
return filepath.SkipDir
|
||||
@@ -463,7 +471,10 @@ func ExpandPathsToFileVisitors(mapper *mapper, paths string, recursive bool, ext
|
||||
if path != paths && ignoreFile(path, extensions) {
|
||||
return nil
|
||||
}
|
||||
-
|
||||
+ if strings.HasSuffix(path, "kustomization.yaml") {
|
||||
+ visitors = append(visitors, NewKustomizationVisitor(mapper, filepath.Dir(path), schema))
|
||||
+ return nil
|
||||
+ }
|
||||
visitor := &FileVisitor{
|
||||
Path: path,
|
||||
StreamVisitor: NewStreamVisitor(nil, mapper, path, schema),
|
||||
@@ -479,6 +490,14 @@ func ExpandPathsToFileVisitors(mapper *mapper, paths string, recursive bool, ext
|
||||
return visitors, nil
|
||||
}
|
||||
|
||||
+func isKustomizationDir(path string) bool {
|
||||
+ if _, err := os.Stat(filepath.Join(path, "kustomization.yaml")); err == nil {
|
||||
+ return true
|
||||
+ }
|
||||
+ return false
|
||||
+}
|
||||
+
|
||||
+
|
||||
// FileVisitor is wrapping around a StreamVisitor, to handle open/close files
|
||||
type FileVisitor struct {
|
||||
Path string
|
||||
@@ -507,6 +526,37 @@ func (v *FileVisitor) Visit(fn VisitorFunc) error {
|
||||
return v.StreamVisitor.Visit(fn)
|
||||
}
|
||||
|
||||
+// KustomizationVisitor prorvides the output of kustomization build
|
||||
+type KustomizationVisitor struct {
|
||||
+ Path string
|
||||
+ *StreamVisitor
|
||||
+}
|
||||
+
|
||||
+// Visit in a KustomizationVisitor build the kustomization output
|
||||
+func (v *KustomizationVisitor) Visit(fn VisitorFunc) error {
|
||||
+ fSys := fs.MakeRealFS()
|
||||
+ f := k8sdeps.NewFactory()
|
||||
+ var out bytes.Buffer
|
||||
+ cmd := build.NewCmdBuild(&out, fSys, f.ResmapF, f.TransformerF)
|
||||
+ cmd.SetArgs([]string{v.Path})
|
||||
+ // we want to silence usage, error output, and any future output from cobra
|
||||
+ // we will get error output as a golang error from execute
|
||||
+ cmd.SetOutput(ioutil.Discard)
|
||||
+ _, err := cmd.ExecuteC()
|
||||
+ if err != nil {
|
||||
+ return err
|
||||
+ }
|
||||
+ v.StreamVisitor.Reader = bytes.NewReader(out.Bytes())
|
||||
+ return v.StreamVisitor.Visit(fn)
|
||||
+}
|
||||
+
|
||||
+func NewKustomizationVisitor(mapper *mapper, path string, schema ContentValidator) *KustomizationVisitor {
|
||||
+ return &KustomizationVisitor{
|
||||
+ Path: path,
|
||||
+ StreamVisitor: NewStreamVisitor(nil, mapper, path, schema),
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
// StreamVisitor reads objects from an io.Reader and walks them. A stream visitor can only be
|
||||
// visited once.
|
||||
// TODO: depends on objects being in JSON format before being passed to decode - need to implement
|
||||
118
build/vendor_kustomize.sh
Executable file
118
build/vendor_kustomize.sh
Executable file
@@ -0,0 +1,118 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Copyright 2018 The Kubernetes Authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
set -e
|
||||
set -x
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
|
||||
|
||||
# vendor_kustomize.sh creates the change in kubernetes repo for vendoring kustomize
|
||||
|
||||
function setUpWorkspace {
|
||||
KPATH=~/kustomize_vendor
|
||||
mkdir $KPATH
|
||||
GOPATH=$KPATH
|
||||
}
|
||||
|
||||
function cloneK8s {
|
||||
mkdir -p $KPATH/src/k8s.io
|
||||
cd $KPATH/src/k8s.io
|
||||
|
||||
git clone git@github.com:kubernetes/kubernetes.git
|
||||
}
|
||||
|
||||
function godepRestore {
|
||||
cd $KPATH/src/k8s.io/kubernetes
|
||||
|
||||
# restore dependencies
|
||||
hack/run-in-gopath.sh hack/godep-restore.sh
|
||||
}
|
||||
|
||||
function getKustomizeDeps {
|
||||
# get Kustomize and Kustomize dependencies
|
||||
hack/run-in-gopath.sh godep get sigs.k8s.io/kustomize/pkg/commands
|
||||
hack/run-in-gopath.sh godep get github.com/bgentry/go-netrc/netrc
|
||||
hack/run-in-gopath.sh godep get github.com/hashicorp/go-cleanhttp
|
||||
hack/run-in-gopath.sh godep get github.com/hashicorp/go-getter
|
||||
hack/run-in-gopath.sh godep get github.com/hashicorp/go-safetemp
|
||||
hack/run-in-gopath.sh godep get github.com/hashicorp/go-version
|
||||
|
||||
# The hashes below passed bin/pre-commit.sh with kustomize HEAD at time of merger.
|
||||
DEPS=(
|
||||
"hashicorp/go-getter 4bda8fa99001c61db3cad96b421d4c12a81f256d"
|
||||
"hashicorp/go-cleanhttp d5fe4b57a186c716b0e00b8c301cbd9b4182694d"
|
||||
"hashicorp/go-safetemp b1a1dbde6fdc11e3ae79efd9039009e22d4ae240"
|
||||
"hashicorp/go-version 270f2f71b1ee587f3b609f00f422b76a6b28f348"
|
||||
"bgentry/go-netrc 9fd32a8b3d3d3f9d43c341bfe098430e07609480"
|
||||
"mitchellh/go-homedir 58046073cbffe2f25d425fe1331102f55cf719de"
|
||||
"mitchellh/go-testing-interface a61a99592b77c9ba629d254a693acffaeb4b7e28"
|
||||
"ulikunitz/xz v0.5.4"
|
||||
)
|
||||
|
||||
function foo {
|
||||
cd $KPATH/src/k8s.io/kubernetes/_output/local/go/src/github.com/$1
|
||||
git checkout $2
|
||||
}
|
||||
for i in "${DEPS[@]}"; do
|
||||
foo $i
|
||||
done
|
||||
}
|
||||
|
||||
function updateK8s {
|
||||
# Copy k8sdeps from Kustomize to cli-runtime in staging
|
||||
mkdir -p $KPATH/src/k8s.io/kubernetes/staging/src/k8s.io/cli-runtime/pkg/kustomize
|
||||
cp -r $KPATH/src/k8s.io/kubernetes/_output/local/go/src/sigs.k8s.io/kustomize/k8sdeps \
|
||||
$KPATH/src/k8s.io/kubernetes/staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps
|
||||
|
||||
# Change import path of k8sdeps
|
||||
find $KPATH/src/k8s.io/kubernetes/staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps \
|
||||
-type f -name "*.go" | \
|
||||
xargs sed -i \
|
||||
's!sigs.k8s.io/kustomize/k8sdeps!k8s.io/cli-runtime/pkg/kustomize/k8sdeps!'
|
||||
|
||||
|
||||
# Add kustomize command to kubectl
|
||||
cp $DIR/vendor_kustomize.diff $KPATH/vendor_kustomize.diff
|
||||
|
||||
cd $GOPATH/src/k8s.io/kubernetes
|
||||
git apply --ignore-space-change --ignore-whitespace $KPATH/vendor_kustomize.diff
|
||||
}
|
||||
|
||||
function godepSave {
|
||||
# Save all dependencies into k8s.io/kubernetes/vendor by running
|
||||
# hack/godep-save.sh
|
||||
hack/run-in-gopath.sh hack/godep-save.sh
|
||||
}
|
||||
|
||||
function verify {
|
||||
# make sure in k8s.io/kubernetes/vendor/sigs.k8s.io/kustomize
|
||||
# there is no internal package
|
||||
test 0 == $(ls $KPATH/src/k8s.io/kubernetes/vendor/sigs.k8s.io/kustomize | grep “internal” | wc -l)
|
||||
|
||||
# Make sure it compiles.
|
||||
test 0 == $(bazel build cmd/kubectl:kubectl)
|
||||
|
||||
# next step, open a PR
|
||||
echo "The change for vendoring kustomize is ready in $GOPATH/src/k8s.io/kubernetes.\n Next step, open a PR for it.\n"
|
||||
}
|
||||
|
||||
setUpWorkspace
|
||||
cloneK8s
|
||||
godepRestore
|
||||
getKustomizeDeps
|
||||
updateK8s
|
||||
godepSave
|
||||
verify
|
||||
@@ -1,4 +0,0 @@
|
||||
# Kustomize Community Code of Conduct
|
||||
|
||||
Kustomize contributers expected to adhere to
|
||||
the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
|
||||
@@ -1,36 +0,0 @@
|
||||
# Contributing guidelines
|
||||
|
||||
[Contributor License Agreement]: https://git.k8s.io/community/CLA.md
|
||||
[github workflow guide]: https://github.com/kubernetes/community/blob/master/contributors/guide/github-workflow.md
|
||||
[CNCF code of conduct]: https://github.com/cncf/foundation/blob/master/code-of-conduct.md
|
||||
|
||||
## Contributing a Patch
|
||||
|
||||
1. Kubernetes projects require contributors to sign the
|
||||
[Contributor License Agreement] before pull requests
|
||||
can be considered.
|
||||
1. Submit an issue describing your proposed change to
|
||||
the repo in question.
|
||||
1. The [repo owners](../OWNERS) will respond to your issue
|
||||
promptly.
|
||||
1. Fork the repo, develop and test your code.
|
||||
See the [github workflow guide].
|
||||
1. For _new features_, provide a markdown-based demo following
|
||||
the pattern established in the [examples](../examples) directory.
|
||||
Run `bin/pre-commit.sh` to test your demo.
|
||||
1. Submit a pull request.
|
||||
|
||||
## Community, discussion, contribution, and support
|
||||
|
||||
Learn how to engage with the Kubernetes community on
|
||||
the [community page](http://kubernetes.io/community/).
|
||||
|
||||
You can reach the maintainers of this project at:
|
||||
|
||||
- [Slack](http://slack.k8s.io/)
|
||||
- [Mailing List](https://groups.google.com/forum/#!forum/kubernetes-kustomize)
|
||||
|
||||
## Code of conduct
|
||||
|
||||
Participation in the Kubernetes community is governed
|
||||
by the [CNCF code of conduct].
|
||||
39
docs/FAQ.md
Normal file
39
docs/FAQ.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# FAQ
|
||||
|
||||
## security: file 'foo' is not in or below 'bar'
|
||||
|
||||
v2.0 added a security check that prevents
|
||||
kustomizations from reading files outside their own
|
||||
directory root.
|
||||
|
||||
This was meant to help protect the person inclined to
|
||||
download kustomization directories from the web and use
|
||||
them without inspection to control their production
|
||||
cluster
|
||||
(see [#693](https://github.com/kubernetes-sigs/kustomize/issues/693),
|
||||
[#700](https://github.com/kubernetes-sigs/kustomize/pull/700),
|
||||
[#995](https://github.com/kubernetes-sigs/kustomize/pull/995) and
|
||||
[#998](https://github.com/kubernetes-sigs/kustomize/pull/998))
|
||||
|
||||
Resources (including configmap and secret generators)
|
||||
can _still be shared_ via the recommended best practice
|
||||
of placing them in a directory with their own
|
||||
kustomization file, and refering to this directory as a
|
||||
[`base`](glossary.md#base) from any kustomization that
|
||||
wants to use it. This encourages modularity and
|
||||
relocatability.
|
||||
|
||||
At the moment (in v2.0.3), however, there's no
|
||||
(released) analogous way to share patch files and other
|
||||
transformer configuration data between kustomizations.
|
||||
|
||||
As a stop-gap until we add base-like behavior for
|
||||
transformers, we've added a flag to disable the check:
|
||||
|
||||
|
||||
```
|
||||
kustomize build --load_restrictor none $target
|
||||
```
|
||||
|
||||
This flag is not in v2.0.3, but is available from head
|
||||
(`go install sigs.k8s.io/kustomize`).
|
||||
@@ -8,9 +8,20 @@ manager:
|
||||
|
||||
brew install kustomize
|
||||
|
||||
On windows, you can install kustomize with Chocolatey package
|
||||
manager.
|
||||
|
||||
choco install kustomize
|
||||
|
||||
For support on the chocolatey package and prior releases, please reference the following links:
|
||||
- [Choco Package](https://chocolatey.org/packages/kustomize)
|
||||
- [Package Source](https://github.com/kenmaglio/choco-kustomize)
|
||||
|
||||
|
||||
For all operating systems, download a binary from the
|
||||
[release page].
|
||||
|
||||
|
||||
Or try this to grab the latest official release
|
||||
using the command line:
|
||||
|
||||
@@ -29,5 +40,5 @@ To install from head with [Go] v1.10.1 or higher:
|
||||
|
||||
<!-- @installkustomize @test -->
|
||||
```
|
||||
go get github.com/kubernetes-sigs/kustomize
|
||||
go get sigs.k8s.io/kustomize
|
||||
```
|
||||
|
||||
@@ -1,23 +1,26 @@
|
||||
# Kustomize docs
|
||||
|
||||
|
||||
* [installation instructions](INSTALL.md)
|
||||
|
||||
|
||||
* [FAQ](FAQ.md)
|
||||
|
||||
* [kustomization.yaml](kustomization.yaml) - Example of a
|
||||
[kustomization](glossary.md#kustomization)
|
||||
with explanations of each field.
|
||||
|
||||
* [versioning policy](versioningPolicy.md) - How the code and the kustomization
|
||||
file evolve in time.
|
||||
|
||||
* [version 2.0.0](version2.0.0.md) - Release notes for Kustomize 2.0.0.
|
||||
|
||||
* [workflow](workflows.md) - Some steps one might take in using
|
||||
bespoke and off-the-shelf configurations.
|
||||
|
||||
|
||||
* [glossary](glossary.md) - An attempt to disambiguiate terminology.
|
||||
|
||||
|
||||
* [eschewed features](eschewedFeatures.md) - Why certain features are (currently)
|
||||
not supported in Kustomize.
|
||||
|
||||
* [contributing guidelines](CONTRIBUTING.md) - Please read before sending a PR.
|
||||
|
||||
* [code of conduct](CODE_OF_CONDUCT.md)
|
||||
|
||||
|
||||
|
||||
|
||||
* [contributing guidelines](../CONTRIBUTING.md) - Please read before sending a PR.
|
||||
|
||||
* [code of conduct](../code-of-conduct.md)
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
# Eschewed Features
|
||||
|
||||
The maintainers established this list to
|
||||
place bounds on the kustomize feature
|
||||
set. The bounds can be changed with
|
||||
a consensus on the risks.
|
||||
|
||||
For a bigger picture about why kustomize
|
||||
does some things and not others, see the
|
||||
glossary entry for [DAM].
|
||||
|
||||
## Removal directives
|
||||
|
||||
`kustomize` supports configurations that can be reasoned about as
|
||||
_compositions_ or _mixins_ - concepts that are widely accepted as
|
||||
a best practice in various programming languages.
|
||||
|
||||
To this end, `kustomize` offers various _addition_ directives. One
|
||||
can add labels, annotations, patches, resources and bases.
|
||||
To this end, `kustomize` offers various _addition_ directives.
|
||||
One may add labels, annotations, patches, resources, bases, etc.
|
||||
Corresponding _removal_ directives are not offered.
|
||||
|
||||
Removal semantics would introduce many possibilities for
|
||||
@@ -27,6 +36,48 @@ what you don't want and commit it to your private fork, then use
|
||||
kustomize on your fork. As often as desired, use _git rebase_ to
|
||||
capture improvements from the upstream base.
|
||||
|
||||
## Unstructured edits
|
||||
|
||||
_Structured edits_ are changes controlled by
|
||||
knowledge of the k8s API, and YAML or JSON syntax.
|
||||
|
||||
Most edits performed by kustomize can be expressed as
|
||||
[JSON patches] or [SMP patches]. Common edits, like
|
||||
adding labels or adding a name prefix, get dedicated
|
||||
shorthand commands. Another class of edits take
|
||||
data from one specific object's field and use it in
|
||||
another (e.g. a service object's name found and
|
||||
copied into a container's command line).
|
||||
|
||||
These edits are designed to create valid output
|
||||
given valid input, and can provide syntactically
|
||||
and semantically informed error messages if inputs
|
||||
are invalid.
|
||||
|
||||
_Unstructured edits_, e.g. a templating approach,
|
||||
or a command to replace any target string in the
|
||||
character stream with some other string, aren't
|
||||
limited by any syntax or object structure.
|
||||
|
||||
Such powerful techniques are eschewed because
|
||||
- There would be no way to say that a kustomization
|
||||
was correct without running it and checking
|
||||
the output.
|
||||
- Errors in the output would be
|
||||
disconnected from the edit that caused it.
|
||||
- They are toil to maintain by a rotating
|
||||
staff of operators.
|
||||
|
||||
Kustomizations are meant to be sharable and stackable.
|
||||
Imagine tracing down a problem rooted in a
|
||||
clever set of stacked regexp replacements
|
||||
performed by various overlays on some remote base.
|
||||
|
||||
Other tools (sed, jinja, erb, envsubst, helm, ksonnet,
|
||||
etc.) provide varying degrees of unstructured editting
|
||||
and/or embedded languages, and can be used instead
|
||||
of, or in a pipe with, kustomize.
|
||||
|
||||
## Build-time side effects from CLI args or env variables
|
||||
|
||||
`kustomize` supports the best practice of storing one's
|
||||
@@ -35,7 +86,7 @@ entire configuration in a version control system.
|
||||
Changing `kustomize build` configuration output as a result
|
||||
of additional arguments or flags to `build`, or by
|
||||
consulting shell environment variable values in `build`
|
||||
code, would violate that goal.
|
||||
code, would frustrate that goal.
|
||||
|
||||
`kustomize` insteads offers [kustomization] file `edit`
|
||||
commands. Like any shell command, they can accept
|
||||
@@ -45,7 +96,7 @@ For example, to set the tag used on an image to match an
|
||||
environment variable, run
|
||||
|
||||
```
|
||||
kustomize edit set imagetag nginx:$MY_NGINX_VERSION
|
||||
kustomize edit set image nginx:$MY_NGINX_VERSION
|
||||
```
|
||||
|
||||
as part of some encapsulating work flow executed before
|
||||
@@ -70,11 +121,10 @@ commands that accept globbed arguments, expand them at _edit
|
||||
time_ relative to the local file system, and store the resulting
|
||||
explicit names into the kustomization file.
|
||||
|
||||
In this way the resources, patches and bases used at _build time_
|
||||
remain explicitly declared in version control.
|
||||
|
||||
|
||||
[base]: glossary.md#base
|
||||
[DAM]: glossary.md#declarative-application-management
|
||||
[java import]: https://www.codebyamir.com/blog/pitfalls-java-import-wildcards
|
||||
[JSON patches]: glossary.md#patchjson6902
|
||||
[kustomization]: glossary.md#kustomization
|
||||
[OTS workflow]: workflows.md#off-the-shelf-configuration
|
||||
[java import]: https://www.codebyamir.com/blog/pitfalls-java-import-wildcards
|
||||
[SMP patches]: glossary.md#patchstrategicmerge
|
||||
|
||||
150
docs/glossary.md
150
docs/glossary.md
@@ -1,8 +1,11 @@
|
||||
# Glossary
|
||||
|
||||
[CRD spec]: https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/
|
||||
[CRD]: #custom-resource-definition
|
||||
[DAM]: #declarative-application-management
|
||||
[Declarative Application Management]: https://github.com/kubernetes/community/blob/master/contributors/design-proposals/architecture/declarative-application-management.md
|
||||
[JSON]: https://www.json.org/
|
||||
[JSONPatch]: https://tools.ietf.org/html/rfc6902
|
||||
[JSONMergePatch]: https://tools.ietf.org/html/rfc7386
|
||||
[Resource]: #resource
|
||||
[YAML]: http://www.yaml.org/start.html
|
||||
[application]: #application
|
||||
@@ -21,14 +24,15 @@
|
||||
[overlays]: #overlay
|
||||
[patch]: #patch
|
||||
[patches]: #patch
|
||||
[patchJson6902]: #patchJson6902
|
||||
[patchJson6902]: #patchjson6902
|
||||
[patchExampleJson6902]: https://github.com/kubernetes-sigs/kustomize/blob/master/examples/jsonpatch.md
|
||||
[patchesJson6902]: #patchjson6902
|
||||
[proposal]: https://github.com/kubernetes/community/pull/1629
|
||||
[rebase]: https://git-scm.com/docs/git-rebase
|
||||
[resource]: #resource
|
||||
[resources]: #resource
|
||||
[rpm]: https://en.wikipedia.org/wiki/Rpm_(software)
|
||||
[strategic merge]: https://github.com/kubernetes/community/blob/master/contributors/devel/strategic-merge-patch.md
|
||||
[strategic-merge]: https://git.k8s.io/community/contributors/devel/sig-api-machinery/strategic-merge-patch.md
|
||||
[target]: #target
|
||||
[variant]: #variant
|
||||
[variants]: #variant
|
||||
@@ -96,22 +100,37 @@ simpler than the workflow associated with an
|
||||
periodically capturing someone else's upgrades to the
|
||||
[off-the-shelf] config.
|
||||
|
||||
## custom resource definition
|
||||
|
||||
One can extend the k8s API by making a
|
||||
Custom Resource Definition ([CRD spec]).
|
||||
|
||||
This defines a custom [resource] (CD), an entirely
|
||||
new resource that can be used alongside _native_
|
||||
resources like ConfigMaps, Deployments, etc.
|
||||
|
||||
Kustomize can customize a CD, but to do so
|
||||
kustomize must also be given the corresponding CRD
|
||||
so that it can interpret the structure correctly.
|
||||
|
||||
## declarative application management
|
||||
|
||||
_Declarative Application Management_ (DAM) is a [set of
|
||||
ideas](https://goo.gl/T66ZcD) aiming to ease management
|
||||
of k8s clusters.
|
||||
Kustomize aspires to support [Declarative Application Management],
|
||||
a set of best practices around managing k8s clusters.
|
||||
|
||||
* Works with any configuration, be it bespoke,
|
||||
In brief, kustomize should
|
||||
|
||||
* Work with any configuration, be it bespoke,
|
||||
off-the-shelf, stateless, stateful, etc.
|
||||
* Supports common customizations, and creation of
|
||||
[variants] (dev vs. staging vs. production).
|
||||
* Exposes and teaches native k8s APIs, rather than
|
||||
hiding them.
|
||||
* No friction integration with version control to
|
||||
* Support common customizations, and creation of
|
||||
[variants] (e.g. _development_ vs.
|
||||
_staging_ vs. _production_).
|
||||
* Expose and teach native k8s APIs, rather than
|
||||
hide them.
|
||||
* Add no friction to version control integration to
|
||||
support reviews and audit trails.
|
||||
* Composable with other tools in a unix sense.
|
||||
* Eschews crossing the line into templating, domain
|
||||
* Compose with other tools in a unix sense.
|
||||
* Eschew crossing the line into templating, domain
|
||||
specific languages, etc., frustrating the other
|
||||
goals.
|
||||
|
||||
@@ -131,18 +150,23 @@ Here's an [example](kustomization.yaml).
|
||||
|
||||
A kustomization contains fields falling into these categories:
|
||||
|
||||
* Immediate customization declarations, e.g.
|
||||
_namePrefix_, _commonLabels_, etc.
|
||||
* Resource _generators_ for configmaps and secrets.
|
||||
* References to _external files_ in these categories:
|
||||
* _Customization operators_ for modifying operands, e.g.
|
||||
_namePrefix_, _nameSuffix_, _commonLabels_, _patches_, etc.
|
||||
|
||||
* _Customization operands_:
|
||||
* [resources] - completely specified k8s API objects,
|
||||
e.g. `deployment.yaml`, `configmap.yaml`, etc.
|
||||
* [patches] - _partial_ resources that modify full
|
||||
resources defined in a [base]
|
||||
(only meaningful in an [overlay]).
|
||||
* [bases] - path to a directory containing
|
||||
a [kustomization] (only meaningful in an [overlay]).
|
||||
* (_TBD_) Standard k8s API kind-version fields.
|
||||
* [bases] - paths or github URLs specifying directories
|
||||
containing a [kustomization]. These bases may
|
||||
be subjected to more customization, or merely
|
||||
included in the output.
|
||||
* [CRD]s - custom resource definition files, to allow use
|
||||
of _custom_ resources in the _resources_ list.
|
||||
Not an actual operand - but allows the use of new operands.
|
||||
|
||||
* Generators, for creating more resources
|
||||
(configmaps and secrets) which can then be
|
||||
customized.
|
||||
|
||||
## kubernetes
|
||||
|
||||
@@ -246,45 +270,71 @@ management tool in the tradition of, say, [apt] or
|
||||
|
||||
## patch
|
||||
|
||||
A _patch_ is a partially defined k8s resource with a
|
||||
name that must match a resource already known per
|
||||
traversal rules built into [kustomize].
|
||||
General instructions to modify a resource.
|
||||
|
||||
_Patch_ is a field in the kustomization, distinct from
|
||||
resources, because a patch file looks like a resource
|
||||
file, but has different semantics. A patch depends on
|
||||
(modifies) a resource, whereas a resource has no
|
||||
dependencies. Since any resource file can be used as a
|
||||
patch, one cannot reliably distinguish a resource from
|
||||
a patch just by looking at the file's [YAML].
|
||||
There are two alternative techniques with similar
|
||||
power but different notation - the
|
||||
[strategic merge patch](#patchstrategicmerge)
|
||||
and the [JSON patch](#patchjson6902).
|
||||
|
||||
## patchStrategicMerge
|
||||
|
||||
A _patchStrategicMerge_ is [strategic-merge]-style patch (SMP).
|
||||
|
||||
An SMP looks like an incomplete YAML specification of
|
||||
a k8s resource. The SMP includes `TypeMeta`
|
||||
fields to establish the group/version/kind/name of the
|
||||
[resource] to patch, then just enough remaining fields
|
||||
to step into a nested structure to specify a new field
|
||||
value, e.g. an image tag.
|
||||
|
||||
By default, an SMP _replaces_ values. This
|
||||
usually desired when the target value is a simple
|
||||
string, but may not be desired when the target
|
||||
value is a list.
|
||||
|
||||
To change this
|
||||
default behavior, add a _directive_. Recognized
|
||||
directives include _replace_ (the default), _merge_
|
||||
(avoid replacing a list), _delete_ and a few more
|
||||
(see [these notes][strategic-merge]).
|
||||
|
||||
Note that for custom resources, SMPs are treated as
|
||||
[json merge patches][JSONMergePatch].
|
||||
|
||||
Fun fact - any resource file can be used as
|
||||
an SMP, overwriting matching fields in another
|
||||
resource with the same group/version/kind/name,
|
||||
but leaving all other fields as they were.
|
||||
|
||||
TODO(monopole): add ptr to example.
|
||||
|
||||
## patchJson6902
|
||||
A _patchJson6902_ refers to a kubernetes object and a [JSONPatch]
|
||||
that can patch the object. A [JSONPatch] contains a list of operations to change the object's field directly.
|
||||
This is different from [patch], which is
|
||||
applied to a target kubernetes object by [strategic merge].
|
||||
|
||||
_patchesJson6902_ is a field in kustomization. It contains a list of
|
||||
_patchJson6902_.
|
||||
A _patchJson6902_ refers to a kubernetes [resource] and
|
||||
a [JSONPatch] specifying how to change the resource.
|
||||
|
||||
A _patchJson6902_ can do almost everything a
|
||||
_patchStrategicMerge_ can do, but with a briefer
|
||||
syntax. See this [example][patchExampleJson6902].
|
||||
|
||||
## resource
|
||||
|
||||
A _resource_, in the context of kustomize, is a path to
|
||||
a [YAML] or [JSON] file that completely defines a
|
||||
functional k8s API object, like a deployment or a
|
||||
configmap.
|
||||
A _resource_ in the context of a REST-ful API is the
|
||||
target object of an HTTP operation like _GET_, _PUT_ or
|
||||
_POST_. k8s offers a REST-ful API surface to interact
|
||||
with clients.
|
||||
|
||||
A _resource_, in the context of kustomization file,
|
||||
is a path to a [YAML] or [JSON] file describing
|
||||
a k8s API object, like a Deployment or a
|
||||
ConfigmMap.
|
||||
|
||||
More generally, a resource can be any correct YAML file
|
||||
that [defines an object](https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/#required-fields)
|
||||
with a _kind_ and a _metadata/name_ field.
|
||||
|
||||
|
||||
A _resource_ in the content of a REST-ful API is the
|
||||
target of an HTTP operation like _GET_, _PUT_ or
|
||||
_POST_. k8s offers a RESTful API surface to interact
|
||||
with clients.
|
||||
|
||||
|
||||
## sub-target / sub-application / sub-package
|
||||
|
||||
A _sub-whatever_ is not a thing. There are only
|
||||
|
||||
@@ -28,9 +28,12 @@
|
||||
# don't exist.
|
||||
#
|
||||
# In practice, fields with no value should simply be
|
||||
# omitted from kustomize.yaml to reduce the content
|
||||
# omitted from kustomization.yaml to reduce the content
|
||||
# visible in configuration reviews.
|
||||
# ----------------------------------------------------
|
||||
# apiVersion and kind of Kustomization
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
# Adds namespace to all resources.
|
||||
namespace: my-namespace
|
||||
@@ -40,6 +43,13 @@ namespace: my-namespace
|
||||
# "wordpress" becomes "alices-wordpress".
|
||||
namePrefix: alices-
|
||||
|
||||
# Value of this field is appended to the
|
||||
# names of all resources, e.g. a deployment named
|
||||
# "wordpress" becomes "wordpress-v2".
|
||||
# The suffix is appended before content hash
|
||||
# if resource type is ConfigMap or Secret.
|
||||
nameSuffix: -v2
|
||||
|
||||
# Labels to add to all resources and selectors.
|
||||
commonLabels:
|
||||
someName: someValue
|
||||
@@ -59,44 +69,60 @@ commonAnnotations:
|
||||
# markers ("---").
|
||||
resources:
|
||||
- some-service.yaml
|
||||
- ../some-dir/some-deployment.yaml
|
||||
- sub-dir/some-deployment.yaml
|
||||
|
||||
# Each entry in this list results in the creation of
|
||||
# one ConfigMap resource (it's a generator of n maps).
|
||||
# The example below creates a ConfigMap with the
|
||||
# names and contents of the given files.
|
||||
# The example below creates two ConfigMaps. One with the
|
||||
# names and contents of the given files, the other with
|
||||
# key/value as data.
|
||||
# Each configMapGenerator item accepts a parameter of
|
||||
# behavior: [create|replace|merge]. This allows an overlay to modify or
|
||||
# replace an existing configMap from the parent.
|
||||
configMapGenerator:
|
||||
- name: myJavaServerProps
|
||||
files:
|
||||
- application.properties
|
||||
- more.properties
|
||||
- name: myJavaServerEnvVars
|
||||
literals:
|
||||
- JAVA_HOME=/opt/java/jdk
|
||||
- JAVA_TOOL_OPTIONS=-agentlib:hprof
|
||||
|
||||
# Each entry in this list results in the creation of
|
||||
# one Secret resource (it's a generator of n secrets).
|
||||
# A command can do anything to get a secret,
|
||||
# e.g. prompt the user directly, start a webserver to
|
||||
# initate an oauth dance, etc.
|
||||
secretGenerator:
|
||||
- name: app-tls
|
||||
commands:
|
||||
tls.crt: "cat secret/tls.cert"
|
||||
tls.key: "cat secret/tls.key"
|
||||
files:
|
||||
- secret/tls.cert
|
||||
- secret/tls.key
|
||||
type: "kubernetes.io/tls"
|
||||
- name: app-tls-namespaced
|
||||
# you can define a namespace to generate secret in, defaults to: "default"
|
||||
namespace: apps
|
||||
files:
|
||||
- tls.crt=catsecret/tls.cert
|
||||
- tls.key=secret/tls.key
|
||||
type: "kubernetes.io/tls"
|
||||
- name: downloaded_secret
|
||||
# timeoutSeconds specifies the number of seconds to
|
||||
# wait for the commands below. It defaults to 5 seconds.
|
||||
timeoutSeconds: 30
|
||||
commands:
|
||||
username: "curl -s https://path/to/secrets/username.yaml"
|
||||
password: "curl -s https://path/to/secrets/password.yaml"
|
||||
type: Opaque
|
||||
- name: env_file_secret
|
||||
# envCommand is similar to command but outputs lines of key=val pairs
|
||||
# i.e. a Docker .env file or a .ini file.
|
||||
# you can only specify one envCommand per secret.
|
||||
envCommand: printf \"DB_USERNAME=admin\nDB_PASSWORD=somepw\"
|
||||
# env is a path to a file to read lines of key=val
|
||||
# you can only specify one env file per secret.
|
||||
env: env.txt
|
||||
type: Opaque
|
||||
|
||||
# generatorOptions modify behavior of all ConfigMap and Secret generators
|
||||
generatorOptions:
|
||||
# labels to add to all generated resources
|
||||
labels:
|
||||
kustomize.generated.resources: somevalue
|
||||
# annotations to add to all generated resources
|
||||
annotations:
|
||||
kustomize.generated.resource: somevalue
|
||||
# disableNameSuffixHash is true disables the default behavior of adding a
|
||||
# suffix to the names of generated resources that is a hash of
|
||||
# the resource contents.
|
||||
disableNameSuffixHash: true
|
||||
|
||||
# Each entry in this list should resolve to a directory
|
||||
# containing a kustomization file, else the
|
||||
# customization fails.
|
||||
@@ -116,9 +142,9 @@ secretGenerator:
|
||||
# etc. that differ from the common base).
|
||||
bases:
|
||||
- ../../base
|
||||
- github.com/kubernetes-sigs/kustomize//examples/multibases?ref=v1.0.6
|
||||
- github.com/kubernetes-sigs/kustomize/examples/multibases?ref=v1.0.6
|
||||
- github.com/Liujingfang1/mysql
|
||||
- github.com/Liujingfang1/kustomize//examples/helloWorld?ref=test-branch
|
||||
- github.com/Liujingfang1/kustomize/examples/helloWorld?ref=test-branch
|
||||
|
||||
# Each entry in this list should resolve to
|
||||
# a partial or complete resource definition file.
|
||||
@@ -133,7 +159,7 @@ bases:
|
||||
# a memory request/limit, change an env var in a
|
||||
# ConfigMap, etc. Small patches are easy to review and
|
||||
# easy to mix together in overlays.
|
||||
patches:
|
||||
patchesStrategicMerge:
|
||||
- service_port_8888.yaml
|
||||
- deployment_increase_replicas.yaml
|
||||
- deployment_increase_memory.yaml
|
||||
@@ -162,7 +188,7 @@ patches:
|
||||
# path: /some/existing/path
|
||||
# value: new value
|
||||
#
|
||||
patchesJson6902
|
||||
patchesJson6902:
|
||||
- target:
|
||||
version: v1
|
||||
kind: Deployment
|
||||
@@ -175,25 +201,35 @@ patchesJson6902
|
||||
path: add_service_annotation.yaml
|
||||
|
||||
# Each entry in this list should be a relative path to
|
||||
# a file for custom resource definition(CRD).
|
||||
# a file for custom resource definition(CRD) in openAPI definition.
|
||||
#
|
||||
# The presence of this field is to allow kustomize be
|
||||
# aware of CRDs and apply proper
|
||||
# transformation for any objects in those types.
|
||||
#
|
||||
# Typical use case: A CRD object refers to a ConfigMap object.
|
||||
# In kustomization, the ConfigMap object name may change by adding namePrefix or hashing
|
||||
# In kustomization, the ConfigMap object name may change by adding namePrefix, nameSuffix, or hashing
|
||||
# The name reference for this ConfigMap object in CRD object need to be
|
||||
# updated with namePrefix or hashing in the same way.
|
||||
# updated with namePrefix, nameSuffix, or hashing in the same way.
|
||||
#
|
||||
# The annotations can be put into openAPI definitions are:
|
||||
# "x-kubernetes-annotation": ""
|
||||
# "x-kubernetes-label-selector": ""
|
||||
# "x-kubernetes-identity": ""
|
||||
# "x-kubernetes-object-ref-api-version": "v1",
|
||||
# "x-kubernetes-object-ref-kind": "Secret",
|
||||
# "x-kubernetes-object-ref-name-key": "name",
|
||||
crds:
|
||||
- crds/typeA.yaml
|
||||
- crds/typeB.yaml
|
||||
- crds/typeA.json
|
||||
- crds/typeB.json
|
||||
|
||||
# Vars are used to insert values from resources that cannot be referenced
|
||||
# otherwise. For example if you need to pass a Service's name to the arguments
|
||||
# or environment variables of a program but without hard coding the actual name
|
||||
# of the Service you'd insert `$(MY_SERVICE_NAME)` into the value field of the
|
||||
# env var or into the command or args of the container as shown here:
|
||||
# Vars are used to capture text from one resource's field
|
||||
# and insert that text elsewhere.
|
||||
#
|
||||
# For example, suppose someone specifies the name of a k8s Service
|
||||
# object in a container's command line, and the name of a
|
||||
# k8s Secret object in a container's environment variable,
|
||||
# so that the following would work:
|
||||
# ```
|
||||
# containers:
|
||||
# - image: myimage
|
||||
@@ -203,19 +239,8 @@ crds:
|
||||
# value: $(SOME_SECRET_NAME)
|
||||
# ```
|
||||
#
|
||||
# Then you'll add an entry to `vars:` like shown below with the same name
|
||||
# and a reference to the resource from which to pull the field's value.
|
||||
# The actual field's path is optional and by default it will use
|
||||
# `metadata.name`. Currently only string type fields are supported, no integers
|
||||
# or booleans, etc. Also array access is currently not possible. For example getting
|
||||
# the image field of container number 2 inside of a pod can currently not be done.
|
||||
# To do so, add an entry to `vars:` as follows:
|
||||
#
|
||||
# Not every location of a variable is supported. To see a complete list of locations
|
||||
# see the file [refvars.go](https://github.com/kubernetes-sigs/kustomize/blob/master/pkg/transformers/refvars.go#L20).
|
||||
#
|
||||
# An example of a situation where you'd not use vars is when you'd like to set a
|
||||
# pod's `serviceAccountName`. In that case you would just reference the ServiceAccount
|
||||
# by name and Kustomize will resolve it to the eventual name while building the manifests.
|
||||
vars:
|
||||
- name: SOME_SECRET_NAME
|
||||
objref:
|
||||
@@ -236,23 +261,64 @@ vars:
|
||||
apiVersion: apps/v1
|
||||
fieldref:
|
||||
fieldpath: spec.template.spec.restartPolicy
|
||||
#
|
||||
# A var is a tuple of variable name, object reference and field
|
||||
# reference within that object. That's where the text is found.
|
||||
#
|
||||
# The field reference is optional; it defaults to `metadata.name`,
|
||||
# a normal default, since kustomize is used to generate or
|
||||
# modify the names of resources.
|
||||
#
|
||||
# At time of writing, only string type fields are supported.
|
||||
# No ints, bools, arrays etc. It's not possible to, say,
|
||||
# extract the name of the image in container number 2 of
|
||||
# some pod template.
|
||||
#
|
||||
# A variable reference, i.e. the string '$(FOO)', can only
|
||||
# be placed in particular fields of particular objects as
|
||||
# specified by kustomize's configuration data.
|
||||
#
|
||||
# The default config data for vars is at
|
||||
# https://github.com/kubernetes-sigs/kustomize/blob/master/pkg/transformers/config/defaultconfig/varreference.go
|
||||
# Long story short, the default targets are all
|
||||
# container command args and env value fields.
|
||||
#
|
||||
# Vars should _not_ be used for inserting names in places
|
||||
# where kustomize is already handling that job. E.g.,
|
||||
# a Deployment may reference a ConfigMap by name, and
|
||||
# if kustomize changes the name of a ConfigMap, it knows
|
||||
# to change the name reference in the Deployment.
|
||||
|
||||
# ImageTags modify the tags for images without creating patches.
|
||||
# E.g. Given this fragment of a Deployment:
|
||||
|
||||
# Images modify the name, tags and/or digest for images without creating patches.
|
||||
# E.g. Given this kubernetes Deployment fragment:
|
||||
# ```
|
||||
# containers:
|
||||
# - name: myapp
|
||||
# image: mycontainerregistry/myimage:v0
|
||||
# - name: mypostgresdb
|
||||
# image: postgres:8
|
||||
# - name: nginxapp
|
||||
# image: nginx:1.7.9
|
||||
# - name: myapp
|
||||
# image: my-demo-app:latest
|
||||
# - name: alpine-app
|
||||
# image: alpine:3.7
|
||||
#```
|
||||
# one can change the tag of myimage to v1 and the tag of nginx to 1.8.0 with the following:
|
||||
# one can change the `image` in the following ways:
|
||||
#
|
||||
# - `postgres:8` to `my-registry/my-postgres:v1`,
|
||||
# - nginx tag `1.7.9` to `1.8.0`,
|
||||
# - image name `my-demo-app` to `my-app`,
|
||||
# - alpine's tag `3.7` to a digest value
|
||||
#
|
||||
# It also supports digests. If digest is present newTag is ignored.
|
||||
imageTags:
|
||||
- name: mycontainerregistry/myimage
|
||||
# all with the following *kustomization*:
|
||||
|
||||
images:
|
||||
- name: postgres
|
||||
newName: my-registry/my-postgres
|
||||
newTag: v1
|
||||
- name: nginx
|
||||
newTag: 1.8.0
|
||||
- name: my-demo-app
|
||||
newName: my-app
|
||||
- name: alpine
|
||||
digest: sha256:24a0c4b4a4c0eb97a1aabb8e29f18e917d05abfe1b7a7c07857230879ce7d3d3
|
||||
|
||||
70
docs/version2.0.0.md
Normal file
70
docs/version2.0.0.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# Kustomize 2.0.0
|
||||
|
||||
After security review, a field used in secret generation (see below) was removed from the definition of a kustomization file with no mechanism to convert it to a new form. Also, the set of files accessible from a kustomization file has been further constrained.
|
||||
|
||||
Per the [versioning policy](versioningPolicy.md), backward incompatible changes trigger an increment of the major version number, hence we go from 1.0.11 to 2.0.0. We're taking this major version increment opportunity to remove some already deprecated fields, and the code paths associated with them.
|
||||
|
||||
## Backward Incompatible Changes
|
||||
|
||||
### Kustomization Path Constraints
|
||||
A kustomization file can specify paths to other files, including resources, patches, configmap generation data, secret generation data and bases. In the case of a base, the path can be a git URL instead.
|
||||
|
||||
In 1.x, these paths had to be relative to the current kustomization directory (the location of the kustomization file used in the `build` command).
|
||||
|
||||
In 2.0, bases can continue to specify, via relative paths, kustomizations outside the current kustomization directory.
|
||||
But non-base paths are constrained to terminate in or below the current kustomization directory. Further, bases specified via a git URL may not reference files outside of the directory used to clone the repository.
|
||||
|
||||
### Kustomization Field Removals
|
||||
|
||||
#### patches
|
||||
`patches` was deprecated and replaced by `patchesStrategicMerge` when `patchesJson6902` was introduced.
|
||||
In Kustomize 2.0.0, `patches` is removed. Please use `patchesStrategicMerge` instead.
|
||||
|
||||
#### imageTags
|
||||
`imageTags` is replaced by `images` since `images` can provide more features to change image names, registries, tags and digests.
|
||||
|
||||
#### secretGenerator/commands
|
||||
`commands` is removed from SecretGenerator due to [security concern](https://docs.google.com/document/d/1FYgLVdq-siB_Cef9yuQBmit0PbrE8lsyTBdGI2eA2y8/edit). One can use `files` or `literals`, similar to ConfigMapGenerator, to generate a secret.
|
||||
```
|
||||
secretGenerator:
|
||||
- name: app-tls
|
||||
files:
|
||||
- secret/tls.cert
|
||||
- secret/tls.key
|
||||
type: "kubernetes.io/tls"
|
||||
```
|
||||
|
||||
## Compatible Changes (New Features)
|
||||
As this release is triggered by a security change,
|
||||
there are no major new features to announce. A few things that are worth mentioning in this release are:
|
||||
|
||||
* More than _40_ issues closed since 1.0.11 release (including many extensions to transformation rules).
|
||||
* Users can run `kustomize edit fix` to migrate a kustomization file working with previous versions to one working with 2.0.0. For example, a kustomization.yaml with following content
|
||||
```
|
||||
patches:
|
||||
- deployment-patch.yaml
|
||||
imageTags:
|
||||
- name: postgres
|
||||
newTag: v1
|
||||
```
|
||||
|
||||
will be converted to
|
||||
|
||||
```
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
patchesStrategicMerge:
|
||||
- deployment-patch.yaml
|
||||
images:
|
||||
- name: postgres
|
||||
newTag: v1
|
||||
```
|
||||
|
||||
* Kustomization filename
|
||||
|
||||
In previous versions, the canonical name of a kustomization file is `kustomization.yaml`. Kustomize 2.0.0 is extended to recognize more file names: `kustomization.yaml`, `kustomization.yml` and `Kustomization`. In a directory, only one of those filenames is allowed. If there are more than one found, Kustomize will exit with an error. Please select the best filename for your use cases.
|
||||
* No longer planning to deprecate namespace prefix/suffix. The deprecation warning
|
||||
```
|
||||
Adding nameprefix and namesuffix to Namespace resource will be deprecated in next release.
|
||||
```
|
||||
is removed. Since changing this behavior will break many users' workflow. Kustomize will continue with adding nameprefix and namesuffix to Namespace resources.
|
||||
219
docs/versioningPolicy.md
Normal file
219
docs/versioningPolicy.md
Normal file
@@ -0,0 +1,219 @@
|
||||
# Versioning
|
||||
|
||||
Running `kustomize` means one is running a
|
||||
particular version of a program, reading a
|
||||
particular version of a [kustomization] file.
|
||||
|
||||
## Program Versioning
|
||||
|
||||
The command `kustomize version` prints a three
|
||||
field version tag (e.g. `1.0.11`) that aspires to
|
||||
[semantic versioning].
|
||||
|
||||
When enough changes have accumulated to
|
||||
warrant a new release, a [release process]
|
||||
is followed, and the fields in the version
|
||||
number are bumped per semver.
|
||||
|
||||
## Kustomization File Versioning
|
||||
|
||||
At the time of writing (circa release of v2.0.0):
|
||||
|
||||
- A [kustomization] file is just a YAML file that
|
||||
can be successfully parsed into a particular Go
|
||||
struct defined in the `kustomize` binary.
|
||||
|
||||
- This struct does not have a version number,
|
||||
which is the same as saying that its version
|
||||
number matches the program's version number,
|
||||
since it's compiled in.
|
||||
|
||||
### Field Change Policy
|
||||
|
||||
- A field's meaning cannot be changed.
|
||||
|
||||
- A field may be deprecated, then removed.
|
||||
|
||||
- Deprecation means triggering a _minor_ (semver)
|
||||
version bump in the program, and
|
||||
defining a migration path in a non-fatal
|
||||
error message.
|
||||
|
||||
- Removal means triggering a _major_ (semver)
|
||||
version bump, and fatal error if field encountered
|
||||
(as with any unknown field).
|
||||
|
||||
### The `edit fix` Command
|
||||
|
||||
This `kustomize` command reads a Kustomization
|
||||
file, converts deprecated fields to new
|
||||
fields, and writes it out again in the latest
|
||||
format.
|
||||
|
||||
This is a type version upgrade mechanism that
|
||||
works within _major_ program revisions. There is
|
||||
no downgrade capability, as there's no use case
|
||||
for it (see discussion below).
|
||||
|
||||
### Examples
|
||||
|
||||
At the time of writing, in v1.0.x, there were 12
|
||||
minor releases, with backward compatible
|
||||
deprecations fixable via `edit fix`.
|
||||
|
||||
With the 2.0.0 release, there were three field
|
||||
removals:
|
||||
|
||||
- `imageTag` was deprecated when `images` was
|
||||
introduced, because the latter offers more
|
||||
general features for image data manipulation.
|
||||
`imageTag` was removed in v2.0.0.
|
||||
|
||||
- `patches` was deprecated and replaced by
|
||||
`patchesStrategicMerge` when `patchesJson6902`
|
||||
was introduced, to make a clearer
|
||||
distinction between patch specification formats.
|
||||
`patches` was removed in v2.0.0.
|
||||
|
||||
- `secretGenerator/commands` was removed
|
||||
due to security concerns in v2.0.0
|
||||
with no deprecation period.
|
||||
|
||||
The `edit fix` command in a v2.0.x binary
|
||||
will no longer recognize these fields.
|
||||
|
||||
## Relationship to the k8s API
|
||||
|
||||
### Review of k8s API versioning
|
||||
|
||||
The k8s API has specific [conventions] and a
|
||||
process for making [changes].
|
||||
|
||||
The presence of an `apiVersion` field in a k8s
|
||||
native type signals:
|
||||
|
||||
- its reliability level (alpha vs beta vs
|
||||
generally available),
|
||||
|
||||
- the existence of code to provide default values
|
||||
to fields not present in a serialization,
|
||||
|
||||
- the existence of code to provide both forward
|
||||
and backward conversion between different
|
||||
versions of types.
|
||||
|
||||
The k8s API promises a lossless _conversion_
|
||||
between versions over a specific range. This
|
||||
means that a recent client can write an object
|
||||
bearing the newest possible value for its version,
|
||||
the server will accept it and store it in
|
||||
"versionless" JSON form in storage, and can
|
||||
convert it to a range of older versions should
|
||||
an older client request data.
|
||||
|
||||
For native k8s types, this all requires writing Go
|
||||
code in the kubernetes core repo, to provide
|
||||
defaulting and conversions.
|
||||
|
||||
For CRDs, there's a [proposal] on how to manage
|
||||
versioning (e.g. a remote service can offer type
|
||||
defaulting and conversions).
|
||||
|
||||
### Kustomization file versioning
|
||||
|
||||
The critical difference between k8s API versioning
|
||||
and kustomization file versioning is
|
||||
|
||||
- A k8s API server is able to go _forward_ and
|
||||
_backward_ in versioning, to work with older
|
||||
clients, over [some range].
|
||||
|
||||
- The `kustomize edit fix` command only moves
|
||||
_forward_ within a _major_ program
|
||||
version.
|
||||
|
||||
At the time of writing, the YAML in a
|
||||
kustomization file does not represent a [k8s API]
|
||||
object, and the kustomize command and associated
|
||||
library is neither a server of, nor a client to,
|
||||
the k8s API.
|
||||
|
||||
### Additional Kustomization file rules
|
||||
|
||||
In addition to the [field change policy] described
|
||||
above, kustomization files conform to
|
||||
the following rules.
|
||||
|
||||
#### Eschew classic k8s fields
|
||||
|
||||
Field names with dedicated meaning in k8s
|
||||
(`metadata`, `spec`, `status`, etc.) aren't used.
|
||||
|
||||
This is enforced via code review.
|
||||
|
||||
#### Optional use of k8s `kind` and `apiVersion`
|
||||
|
||||
At the time of writing two [special] k8s
|
||||
resource fields are allowed, but not required, in
|
||||
a kustomization file: [`kind`] and [`apiVersion`].
|
||||
|
||||
If either field is present, they both must be, and
|
||||
they must have the following values:
|
||||
|
||||
``` yaml
|
||||
kind: Kustomization
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
```
|
||||
|
||||
They are allowed to exist and have specific values
|
||||
in a kustomization file only as a sort of
|
||||
domain-squatting behavior for some future API. A
|
||||
kustomize user gains nothing from adding these
|
||||
fields to a kustomization file.
|
||||
|
||||
### Why not require `kind` and `apiVersion`
|
||||
|
||||
#### Ease of use and setting proper expectations
|
||||
|
||||
Use cases for a kustomization file don't include a
|
||||
server storing muliple k8s kinds and offering
|
||||
version downgrades.
|
||||
|
||||
The kustomization file is more akin to a
|
||||
`Makefile`. A kustomize command can either read a
|
||||
kustomization file, or it cannot, and in the later
|
||||
case will complain as specifically as possible
|
||||
about why (e.g. `unknown field Foo`).
|
||||
|
||||
So requiring a `kind` and `apiVersion` would just
|
||||
be boilerplate in a user's files, and in all the
|
||||
examples and tests.
|
||||
|
||||
Nevertheless, _a user still benefits from a
|
||||
versioning policy_ and has a `fix` command to
|
||||
upgrade files as needed.
|
||||
|
||||
#### We can change our minds
|
||||
|
||||
When/if the kustomization struct graduates to some
|
||||
kind of API status, with an expectation of
|
||||
"versionless" storage and downgrade capability,
|
||||
whatever it looks like at that moment can be
|
||||
locked into `/v1beta1` or `/v1` and the `kind`
|
||||
and `apiVersion` fields can be required from that
|
||||
moment forward.
|
||||
|
||||
[field change policy]: #field-change-policy
|
||||
[some range]: https://kubernetes.io/docs/reference/using-api/deprecation-policy
|
||||
[proposal]: https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/customresources-versioning.md
|
||||
[beta-level rules]: https://github.com/kubernetes/community/blob/master/contributors/devel/api_changes.md#alpha-beta-and-stable-versions
|
||||
[changes]: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api_changes.md
|
||||
[adapt]: https://github.com/kubernetes-sigs/kustomize/blob/master/pkg/types/kustomization.go#L166
|
||||
[special]: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#resources
|
||||
[k8s API]: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md
|
||||
[conventions]: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md
|
||||
[release process]: ../build/README.md
|
||||
[kustomization]: glossary.md#kustomization
|
||||
[`kind`]: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#types-kinds
|
||||
[`apiVersion`]: https://kubernetes.io/docs/concepts/overview/kubernetes-api/#api-versioning
|
||||
[semantic versioning]: https://semver.org
|
||||
@@ -13,6 +13,7 @@
|
||||
[resources]: glossary.md#resource
|
||||
[workflowBespoke]: workflowBespoke.jpg
|
||||
[workflowOts]: workflowOts.jpg
|
||||
[kubectl-v1.14.0]:https://kubernetes.io/blog/2019/03/25/kubernetes-1-14-release-announcement/
|
||||
|
||||
# workflows
|
||||
|
||||
@@ -71,6 +72,11 @@ Run kustomize, and pipe the output to [apply].
|
||||
> kustomize build ~/ldap/overlays/production | kubectl apply -f -
|
||||
> ```
|
||||
|
||||
You can also use [kubectl-v1.14.0] to apply your [variants].
|
||||
> ```
|
||||
> kubectl apply -k ~/ldap/overlays/staging
|
||||
> kubectl apply -k ~/ldap/overlays/production
|
||||
> ```
|
||||
|
||||
## Off-the-shelf configuration
|
||||
|
||||
@@ -120,6 +126,12 @@ distinct repository.
|
||||
> kustomize build ~/ldap/overlays/production | kubectl apply -f -
|
||||
> ```
|
||||
|
||||
You can also use [kubectl-v1.14.0] to apply your [variants].
|
||||
> ```
|
||||
> kubectl apply -k ~/ldap/overlays/staging
|
||||
> kubectl apply -k ~/ldap/overlays/production
|
||||
> ```
|
||||
|
||||
#### 5) (optionally) capture changes from upstream
|
||||
|
||||
The user can periodically [rebase] their [base] to
|
||||
|
||||
@@ -28,17 +28,26 @@ go get sigs.k8s.io/kustomize
|
||||
(e.g. devops/SRE and developers).
|
||||
|
||||
* [configGenerations](configGeneration.md) -
|
||||
Rolling update when ConfigMapGenerator changes
|
||||
Rolling update when ConfigMapGenerator changes.
|
||||
|
||||
* [secret generation](kvSourceGoPlugin.md) - Generating secrets.
|
||||
|
||||
* [generatorOptions](generatorOptions.md) -
|
||||
Modifying behavior of all ConfigMap and Secret generators.
|
||||
|
||||
* [breakfast](breakfast.md) - Customize breakfast for
|
||||
Alice and Bob.
|
||||
|
||||
* [container args](wordpress/README.md) - Injecting k8s runtime data into container arguments (e.g. to point wordpress to a SQL service).
|
||||
* [vars](wordpress/README.md) - Injecting k8s runtime data into
|
||||
container arguments (e.g. to point wordpress to a SQL service) by vars.
|
||||
|
||||
* [image tags](imageTags.md) - Updating image tags without applying a patch.
|
||||
* [image names and tags](image.md) - Updating image names and tags without applying a patch.
|
||||
|
||||
* [multibases](multibases/README.md) - Composing three variants (dev, staging, production) with a common base.
|
||||
|
||||
* [remote target](remoteBuild.md) - Building a kustomization from a github URL
|
||||
|
||||
* [json patch](jsonpatch.md) - Apply a json patch in a kustomization
|
||||
|
||||
* [transformer configs](transformerconfigs/README.md) - Customize transformer configurations
|
||||
|
||||
|
||||
@@ -92,9 +92,9 @@ secret holding them (not covering that here).
|
||||
<!--
|
||||
secretGenerator:
|
||||
- name: app-tls
|
||||
commands:
|
||||
tls.crt: "cat tls.cert"
|
||||
tls.key: "cat tls.key"
|
||||
files:
|
||||
tls.crt=tls.cert
|
||||
tls.key=tls.key
|
||||
type: "kubernetes.io/tls"
|
||||
EOF
|
||||
-->
|
||||
@@ -194,6 +194,7 @@ cat <<EOF >$OVERLAYS/development/kustomization.yaml
|
||||
bases:
|
||||
- ../../base
|
||||
namePrefix: dev-
|
||||
nameSuffix: -v1
|
||||
configMapGenerator:
|
||||
- name: my-configmap
|
||||
behavior: merge
|
||||
@@ -215,11 +216,12 @@ kustomize build $OVERLAYS/development
|
||||
The name of the generated `ConfigMap` is visible in this
|
||||
output.
|
||||
|
||||
The name should be something like `dev-my-configmap-b5m75ck895`:
|
||||
The name should be something like `dev-my-configmap-v1-2gccmccgd5`:
|
||||
|
||||
* `"dev-"` comes from the `namePrefix` field,
|
||||
* `"my-configmap"` comes from the `configMapGenerator/name` field,
|
||||
* `"-b5m75ck895"` comes from a deterministic hash that `kustomize`
|
||||
* `"-v1"` comes from the `nameSuffix` field,
|
||||
* `"-2gccmccgd5"` comes from a deterministic hash that `kustomize`
|
||||
computes from the contents of the configMap.
|
||||
|
||||
The hash suffix is critical. If the configMap content
|
||||
@@ -291,7 +293,7 @@ kustomize build $OVERLAYS/production
|
||||
```
|
||||
|
||||
A CICD process could apply this directly to
|
||||
the cluser using:
|
||||
the cluster using:
|
||||
|
||||
> ```
|
||||
> kustomize build $OVERLAYS/production | kubectl apply -f -
|
||||
|
||||
@@ -60,6 +60,7 @@ mkdir -p $OVERLAYS/staging
|
||||
|
||||
cat <<'EOF' >$OVERLAYS/staging/kustomization.yaml
|
||||
namePrefix: staging-
|
||||
nameSuffix: -v1
|
||||
commonLabels:
|
||||
variant: staging
|
||||
org: acmeCorporation
|
||||
@@ -150,13 +151,17 @@ The configMap name is prefixed by _staging-_, per the
|
||||
`namePrefix` field in
|
||||
`$OVERLAYS/staging/kustomization.yaml`.
|
||||
|
||||
The configMap name is suffixed by _-v1_, per the
|
||||
`nameSuffix` field in
|
||||
`$OVERLAYS/staging/kustomization.yaml`.
|
||||
|
||||
The suffix to the configMap name is generated from a
|
||||
hash of the maps content - in this case the name suffix
|
||||
is _hhhhkfmgmk_:
|
||||
is _k25m8k5k5m_:
|
||||
|
||||
<!-- @grepStagingHash @test -->
|
||||
```
|
||||
kustomize build $OVERLAYS/staging | grep hhhhkfmgmk
|
||||
kustomize build $OVERLAYS/staging | grep k25m8k5k5m
|
||||
```
|
||||
|
||||
Now modify the map patch, to change the greeting
|
||||
@@ -164,7 +169,7 @@ the server will use:
|
||||
|
||||
<!-- @changeMap @test -->
|
||||
```
|
||||
sed -i 's/pineapple/kiwi/' $OVERLAYS/staging/map.yaml
|
||||
sed -i.bak 's/pineapple/kiwi/' $OVERLAYS/staging/map.yaml
|
||||
```
|
||||
|
||||
See the new greeting:
|
||||
@@ -183,20 +188,20 @@ kustomize build $OVERLAYS/staging |\
|
||||
```
|
||||
|
||||
Confirm that the change in configMap content resulted
|
||||
in three new names ending in _khk45ktkd9_ - one in the
|
||||
in three new names ending in _cd7kdh48fd_ - one in the
|
||||
configMap name itself, and two in the deployment that
|
||||
uses the map:
|
||||
|
||||
<!-- @countHashes @test -->
|
||||
```
|
||||
test 3 == \
|
||||
$(kustomize build $OVERLAYS/staging | grep khk45ktkd9 | wc -l); \
|
||||
$(kustomize build $OVERLAYS/staging | grep cd7kdh48fd | wc -l); \
|
||||
echo $?
|
||||
```
|
||||
|
||||
Applying these resources to the cluster will result in
|
||||
a rolling update of the deployments pods, retargetting
|
||||
them from the _hhhhkfmgmk_ maps to the _khk45ktkd9_
|
||||
them from the _k25m8k5k5m_ maps to the _cd7kdh48fd_
|
||||
maps. The system will later garbage collect the
|
||||
unused maps.
|
||||
|
||||
|
||||
60
examples/generatorOptions.md
Normal file
60
examples/generatorOptions.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# Generator Options
|
||||
|
||||
Kustomize provides options to modify the behavior of ConfigMap and Secret generators. These options include
|
||||
|
||||
- disable appending a content hash suffix to the names of generated resources
|
||||
- adding labels to generated resources
|
||||
- adding annotations to generated resources
|
||||
|
||||
This demo shows how to use these options. First create a workspace.
|
||||
```
|
||||
DEMO_HOME=$(mktemp -d)
|
||||
```
|
||||
|
||||
Create a kustomization and add a ConfigMap generator to it.
|
||||
|
||||
<!-- @createCMGenerator @test -->
|
||||
```
|
||||
cat > $DEMO_HOME/kustomization.yaml << EOF
|
||||
configMapGenerator:
|
||||
- name: my-configmap
|
||||
literals:
|
||||
- foo=bar
|
||||
- baz=qux
|
||||
EOF
|
||||
```
|
||||
|
||||
Add following generatorOptions
|
||||
<!-- @addGeneratorOptions @test -->
|
||||
```
|
||||
cat >> $DEMO_HOME/kustomization.yaml << EOF
|
||||
generatorOptions:
|
||||
disableNameSuffixHash: true
|
||||
labels:
|
||||
kustomize.generated.resource: somevalue
|
||||
annotations:
|
||||
annotations.only.for.generated: othervalue
|
||||
EOF
|
||||
```
|
||||
Run `kustomize build` and make sure that the generated ConfigMap
|
||||
|
||||
- doesn't have name suffix
|
||||
<!-- @verify @test -->
|
||||
```
|
||||
test 1 == \
|
||||
$(kustomize build $DEMO_HOME | grep "name: my-configmap$" | wc -l); \
|
||||
echo $?
|
||||
```
|
||||
- has label `kustomize.generated.resource: somevalue`
|
||||
```
|
||||
test 1 == \
|
||||
$(kustomize build $DEMO_HOME | grep -A 1 "labels" | grep "kustomize.generated.resource" | wc -l); \
|
||||
echo $?
|
||||
```
|
||||
- has annotation `annotations.only.for.generated: othervalue`
|
||||
```
|
||||
test 1 == \
|
||||
$(kustomize build $DEMO_HOME | grep -A 1 "annotations" | grep "annotations.only.for.generated" | wc -l); \
|
||||
echo $?
|
||||
```
|
||||
|
||||
@@ -108,16 +108,10 @@ label_ applied to all resources:
|
||||
|
||||
<!-- @addLabel @test -->
|
||||
```
|
||||
sed -i 's/app: hello/app: my-hello/' \
|
||||
sed -i.bak 's/app: hello/app: my-hello/' \
|
||||
$BASE/kustomization.yaml
|
||||
```
|
||||
|
||||
On a Mac, use:
|
||||
```
|
||||
sed -i '' $pattern $file
|
||||
```
|
||||
to get in-place editing.
|
||||
|
||||
See the effect:
|
||||
<!-- @checkLabel @test -->
|
||||
```
|
||||
|
||||
76
examples/image.md
Normal file
76
examples/image.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# Demo: change image names and tags
|
||||
|
||||
|
||||
Define a place to work:
|
||||
|
||||
<!-- @makeWorkplace @test -->
|
||||
```
|
||||
DEMO_HOME=$(mktemp -d)
|
||||
```
|
||||
|
||||
Make a `kustomization` containing a pod resource
|
||||
|
||||
<!-- @createKustomization @test -->
|
||||
```
|
||||
cat <<EOF >$DEMO_HOME/kustomization.yaml
|
||||
resources:
|
||||
- pod.yaml
|
||||
EOF
|
||||
```
|
||||
|
||||
Declare the pod resource
|
||||
|
||||
<!-- @createDeployment @test -->
|
||||
```
|
||||
cat <<EOF >$DEMO_HOME/pod.yaml
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: myapp-pod
|
||||
labels:
|
||||
app: myapp
|
||||
spec:
|
||||
containers:
|
||||
- name: myapp-container
|
||||
image: busybox:1.29.0
|
||||
command: ['sh', '-c', 'echo The app is running! && sleep 3600']
|
||||
initContainers:
|
||||
- name: init-mydb
|
||||
image: busybox:1.29.0
|
||||
command: ['sh', '-c', 'until nslookup mydb; do echo waiting for mydb; sleep 2; done;']
|
||||
EOF
|
||||
```
|
||||
|
||||
The `myapp-pod` resource declares an initContainer and a container, both use the image `busybox:1.29.0`.
|
||||
The image `busybox` and tag `1.29.0` can be changed by adding `images` in `kustomization.yaml`.
|
||||
|
||||
|
||||
Add `images`:
|
||||
<!-- @addImages @test -->
|
||||
```
|
||||
cd $DEMO_HOME
|
||||
kustomize edit set image busybox=alpine:3.6
|
||||
```
|
||||
|
||||
The following `images` will be added to `kustomization.yaml`:
|
||||
> ```
|
||||
> images:
|
||||
> - name: busybox
|
||||
> newName: alpine
|
||||
> newTag: 3.6
|
||||
> ```
|
||||
|
||||
Now build this `kustomization`
|
||||
<!-- @kustomizeBuild @test -->
|
||||
```
|
||||
kustomize build $DEMO_HOME
|
||||
```
|
||||
|
||||
Confirm that this replaces _both_ busybox images and tags for `alpine:3.6`:
|
||||
|
||||
<!-- @confirmImages @test -->
|
||||
```
|
||||
test 2 = \
|
||||
$(kustomize build $DEMO_HOME | grep alpine:3.6 | wc -l); \
|
||||
echo $?
|
||||
```
|
||||
@@ -1,75 +0,0 @@
|
||||
# Demo: change image tags
|
||||
|
||||
|
||||
Define a place to work:
|
||||
|
||||
<!-- @makeWorkplace @test -->
|
||||
```
|
||||
DEMO_HOME=$(mktemp -d)
|
||||
```
|
||||
|
||||
Make a `kustomization` containing a pod resource
|
||||
|
||||
<!-- @createKustomization @test -->
|
||||
```
|
||||
cat <<EOF >$DEMO_HOME/kustomization.yaml
|
||||
resources:
|
||||
- pod.yaml
|
||||
EOF
|
||||
```
|
||||
|
||||
Declare the pod resource
|
||||
|
||||
<!-- @createDeployment @test -->
|
||||
```
|
||||
cat <<EOF >$DEMO_HOME/pod.yaml
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: myapp-pod
|
||||
labels:
|
||||
app: myapp
|
||||
spec:
|
||||
containers:
|
||||
- name: myapp-container
|
||||
image: busybox:1.29.0
|
||||
command: ['sh', '-c', 'echo The app is running! && sleep 3600']
|
||||
initContainers:
|
||||
- name: init-mydb
|
||||
image: busybox:1.29.0
|
||||
command: ['sh', '-c', 'until nslookup mydb; do echo waiting for mydb; sleep 2; done;']
|
||||
EOF
|
||||
```
|
||||
|
||||
The `myapp-pod` resource declares an initContainer and a container, both use the image `busybox:1.29.0`.
|
||||
The tag `1.29.0` can be changed by adding `imageTags` in `kustomization.yaml`.
|
||||
|
||||
|
||||
Add `imageTags`:
|
||||
<!-- @addImageTags @test -->
|
||||
```
|
||||
cd $DEMO_HOME
|
||||
kustomize edit set imagetag busybox:1.29.1
|
||||
```
|
||||
|
||||
The `kustomization.yaml` will be added following `imageTags`.
|
||||
> ```
|
||||
> imageTags:
|
||||
> - name: busybox
|
||||
> newTag: 1.29.1
|
||||
> ```
|
||||
|
||||
Now build this `kustomization`
|
||||
<!-- @kustomizeBuild @test -->
|
||||
```
|
||||
kustomize build $DEMO_HOME
|
||||
```
|
||||
|
||||
Confirm that this replaces _both_ busybox tags:
|
||||
|
||||
<!-- @confirmTags @test -->
|
||||
```
|
||||
test 2 == \
|
||||
$(kustomize build $DEMO_HOME | grep busybox:1.29.1 | wc -l); \
|
||||
echo $?
|
||||
```
|
||||
@@ -48,7 +48,7 @@ function setUpEnv {
|
||||
[[ $? -eq 0 ]] || "Failed to cd to $repo"
|
||||
echo "pwd is " `pwd`
|
||||
|
||||
local expectedRepo=kubernetes-sigs/kustomize
|
||||
local expectedRepo=sigs.k8s.io/kustomize
|
||||
if [[ `pwd` != */$expectedRepo ]]; then
|
||||
exitWith "Script must be run from $expectedRepo"
|
||||
fi
|
||||
|
||||
@@ -46,6 +46,25 @@ cat <<EOF >$DEMO_HOME/ingress_patch.json
|
||||
EOF
|
||||
```
|
||||
|
||||
You can also write the patch in YAML format. This example also shows the "add" operation:
|
||||
|
||||
<!-- @addYamlPatch @test -->
|
||||
```
|
||||
cat <<EOF >$DEMO_HOME/ingress_patch.yaml
|
||||
- op: replace
|
||||
path: /spec/rules/0/host
|
||||
value: foo.bar.io
|
||||
|
||||
- op: add
|
||||
path: /spec/rules/0/http/paths/-
|
||||
value:
|
||||
path: '/test'
|
||||
backend:
|
||||
serviceName: my-test
|
||||
servicePort: 8081
|
||||
EOF
|
||||
```
|
||||
|
||||
Apply the patch by adding _patchesJson6902_ field in kustomization.yaml
|
||||
|
||||
<!-- @applyJsonPatch @test -->
|
||||
@@ -61,17 +80,39 @@ patchesJson6902:
|
||||
EOF
|
||||
```
|
||||
|
||||
Running `kustomize build $DEMO_HOME`, in the ourput confirm that host has been updated correctly.
|
||||
Running `kustomize build $DEMO_HOME`, in the output confirm that host has been updated correctly.
|
||||
<!-- @confirmHost @test -->
|
||||
```
|
||||
test 1 == \
|
||||
$(kustomize build $DEMO_HOME | grep "host: foo.bar.io" | wc -l); \
|
||||
echo $?
|
||||
```
|
||||
Running `kustomize build $DEMO_HOME`, in the ourput confirm that the servicePort has been updated correctly.
|
||||
Running `kustomize build $DEMO_HOME`, in the output confirm that the servicePort has been updated correctly.
|
||||
<!-- @confirmServicePort @test -->
|
||||
```
|
||||
test 1 == \
|
||||
$(kustomize build $DEMO_HOME | grep "servicePort: 8080" | wc -l); \
|
||||
echo $?
|
||||
```
|
||||
|
||||
If the patch is YAML-formatted, it will be parsed correctly:
|
||||
|
||||
<!-- @applyYamlPatch @test -->
|
||||
```
|
||||
cat <<EOF >>$DEMO_HOME/kustomization.yaml
|
||||
patchesJson6902:
|
||||
- target:
|
||||
group: extensions
|
||||
version: v1beta1
|
||||
kind: Ingress
|
||||
name: my-ingress
|
||||
path: ingress_patch.yaml
|
||||
EOF
|
||||
```
|
||||
|
||||
<!-- @confirmYamlPatch @test -->
|
||||
```
|
||||
test 1 == \
|
||||
$(kustomize build $DEMO_HOME | grep "path: /test" | wc -l); \
|
||||
echo $?
|
||||
```
|
||||
|
||||
378
examples/kvSourceGoPlugin.md
Normal file
378
examples/kvSourceGoPlugin.md
Normal file
@@ -0,0 +1,378 @@
|
||||
[ConfigMaps]: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.13/#configmap-v1-core
|
||||
[ELF]: https://en.wikipedia.org/wiki/Executable_and_Linkable_Format
|
||||
[Go plugin]: https://golang.org/pkg/plugin
|
||||
[Secrets]: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.13/#secret-v1-core
|
||||
[base64]: https://tools.ietf.org/html/rfc4648#section-4
|
||||
[configuration directory]: https://wiki.archlinux.org/index.php/XDG_Base_Directory#Specification
|
||||
[grpc]: https://grpc.io
|
||||
[tag]: https://github.com/kubernetes-sigs/kustomize/releases
|
||||
[v2.0.3]: https://github.com/kubernetes-sigs/kustomize/releases/tag/v2.0.3
|
||||
[`exec.Command`]: https://golang.org/pkg/os/exec/#Command
|
||||
|
||||
# Generating Secrets
|
||||
|
||||
## What's a Secret?
|
||||
|
||||
Kubernetes [ConfigMaps] and [Secrets] are both
|
||||
key:value (KV) maps, but the latter is intended to
|
||||
signal that its values have a sensitive nature -
|
||||
e.g. ssh keys or passwords.
|
||||
|
||||
Kubernetes assumes that the values in a Secret are
|
||||
[base64] encoded, and decodes them before actual
|
||||
use (as, say, the argument to a container
|
||||
command). The user that creates the Secret must
|
||||
base64 encode the data, or use a tool that does it
|
||||
for them. This encoding doesn't protect the
|
||||
secret from anything other than an
|
||||
over-the-shoulder glance.
|
||||
|
||||
Protecting the actual secrecy of a Secret value is
|
||||
up to the cluster operator. They must lock down
|
||||
the cluster (and its `etcd` data store) as tightly
|
||||
as desired, and likewise protect the bytes that
|
||||
feed into the cluster to ultimately become the
|
||||
content of a Secret value.
|
||||
|
||||
## Make a place to work
|
||||
|
||||
<!-- @establishBase @test -->
|
||||
```
|
||||
DEMO_HOME=$(mktemp -d)
|
||||
```
|
||||
|
||||
## Secret values from local files
|
||||
|
||||
kustomize has three different ways to generate a secret
|
||||
from local files:
|
||||
|
||||
* get them from so-called _env_ files (`NAME=VALUE`, one per line),
|
||||
* consume the entire contents of a file to make one secret value,
|
||||
* get literal values from the kustomization file itself.
|
||||
|
||||
Here's an example combining all three methods:
|
||||
|
||||
Make an env file with some short secrets:
|
||||
|
||||
<!-- @makeEnvFile @test -->
|
||||
```
|
||||
cat <<'EOF' >$DEMO_HOME/foo.env
|
||||
ROUTER_PASSWORD=admin
|
||||
DB_PASSWORD=iloveyou
|
||||
EOF
|
||||
```
|
||||
|
||||
Make a text file with a long secret:
|
||||
|
||||
<!-- @makeLongSecretFile @test -->
|
||||
```
|
||||
cat <<'EOF' >$DEMO_HOME/longsecret.txt
|
||||
Lorem ipsum dolor sit amet,
|
||||
consectetur adipiscing elit,
|
||||
sed do eiusmod tempor incididunt
|
||||
ut labore et dolore magna aliqua.
|
||||
EOF
|
||||
```
|
||||
|
||||
And make a kustomization file referring to the
|
||||
above and additionally defining some literal KV
|
||||
pairs:
|
||||
|
||||
<!-- @makeKustomization1 @test -->
|
||||
```
|
||||
cat <<'EOF' >$DEMO_HOME/kustomization.yaml
|
||||
secretGenerator:
|
||||
- name: mysecrets
|
||||
kvSources:
|
||||
- name: envfiles
|
||||
pluginType: builtin
|
||||
args:
|
||||
- foo.env
|
||||
- name: files
|
||||
pluginType: builtin
|
||||
args:
|
||||
- longsecret.txt
|
||||
- name: literals
|
||||
pluginType: builtin
|
||||
args:
|
||||
- FRUIT=apple
|
||||
- VEGETABLE=carrot
|
||||
EOF
|
||||
```
|
||||
|
||||
> The above syntax is _alpha_ behavior at HEAD, for v2.1+.
|
||||
>
|
||||
> The default value of `pluginType` is `builtin`, so the
|
||||
> `pluginType` fields could be omitted.
|
||||
>
|
||||
> The equivalent [v2.0.3] syntax (still supported) is
|
||||
> ```
|
||||
> secretGenerator:
|
||||
> - name: mysecrets
|
||||
> env: foo.env
|
||||
> files:
|
||||
> - longsecret.txt
|
||||
> literals:
|
||||
> - FRUIT=apple
|
||||
> - VEGETABLE=carrot
|
||||
> ```
|
||||
|
||||
Now generate the Secret:
|
||||
|
||||
<!-- @build1 @test -->
|
||||
```
|
||||
result=$(kustomize build $DEMO_HOME)
|
||||
echo "$result"
|
||||
# Spot check the result:
|
||||
test 1 == $(echo "$result" | grep -c "FRUIT: YXBwbGU=")
|
||||
```
|
||||
|
||||
This emits something like
|
||||
|
||||
> ```
|
||||
> apiVersion: v1
|
||||
> kind: Secret
|
||||
> metadata:
|
||||
> name: mysecrets-hfb5df789h
|
||||
> type: Opaque
|
||||
> data:
|
||||
> FRUIT: YXBwbGU=
|
||||
> VEGETABLE: Y2Fycm90
|
||||
> ROUTER_PASSWORD: YWRtaW4=
|
||||
> DB_PASSWORD: aWxvdmV5b3U=
|
||||
> longsecret.txt: TG9yZW0gaXBzdW0gZG9sb3Igc2l0I... (elided)
|
||||
> ```
|
||||
|
||||
The name of the resource is a prefix, `mysecrets`
|
||||
(as specfied in the kustomization file), followed
|
||||
by a hash of its contents.
|
||||
|
||||
Use your favorite base64 decoder to confirm the raw
|
||||
versions of any of these values.
|
||||
|
||||
The problem that these three approaches share is
|
||||
that the purported secrets must live on disk.
|
||||
|
||||
This adds additional security questions - who can
|
||||
see the files, who installs them, who deletes
|
||||
them, etc.
|
||||
|
||||
|
||||
## Secret values from anywhere
|
||||
|
||||
> New _alpha_ behavior at HEAD, for v2.1+
|
||||
|
||||
A general alternative is to enshrine secret
|
||||
value generation in a [Go plugin].
|
||||
|
||||
The values can then come in via, say, an
|
||||
authenticated and authorized RPC to a password
|
||||
vault service.
|
||||
|
||||
Here's a trivial plugin that provides
|
||||
hardcoded values:
|
||||
|
||||
<!-- @makePlugin @test -->
|
||||
```
|
||||
cat <<'EOF' >$DEMO_HOME/kvMaker.go
|
||||
package main
|
||||
var database = map[string]string{
|
||||
"TREE": "oak",
|
||||
"ROCKET": "Saturn V",
|
||||
"FRUIT": "apple",
|
||||
"VEGETABLE": "carrot",
|
||||
"SIMPSON": "homer",
|
||||
}
|
||||
|
||||
type plugin struct{}
|
||||
var KVSource plugin
|
||||
func (p plugin) Get(
|
||||
root string, args []string) (map[string]string, error) {
|
||||
r := make(map[string]string)
|
||||
for _, k := range args {
|
||||
v, ok := database[k]
|
||||
if ok {
|
||||
r[k] = v
|
||||
}
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
|
||||
The two crucial items needed to
|
||||
load and query the plugin are
|
||||
1) the public symbol `KVSource`,
|
||||
1) its public `Get` method and signature.
|
||||
|
||||
Plugins that generate KV pairs for kustomize
|
||||
must be installed at
|
||||
|
||||
> ```
|
||||
> $XDG_CONFIG_HOME/kustomize/plugin/kvSource
|
||||
> ```
|
||||
|
||||
`XDG_CONFIG_HOME` is an environment variable
|
||||
honored by many programs as the root of a
|
||||
[configuration directory]. If the variable is
|
||||
undefined, the convention is to fall back to
|
||||
`$HOME/.config`.
|
||||
|
||||
The rest of the required directory path
|
||||
establishes that the files found there are
|
||||
kustomize plugins for generating KV pairs.
|
||||
|
||||
Compile and install the plugin:
|
||||
|
||||
<!-- @compilePlugin @test -->
|
||||
```
|
||||
kvSources=$DEMO_HOME/kustomize/plugin/kvSources
|
||||
mkdir -p $kvSources
|
||||
go build -buildmode plugin \
|
||||
-o $kvSources/kvMaker.so \
|
||||
$DEMO_HOME/kvMaker.go
|
||||
```
|
||||
|
||||
Create a new kustomization file
|
||||
referencing this plugin:
|
||||
|
||||
<!-- @makeKustomization2 @test -->
|
||||
```
|
||||
cat <<'EOF' >$DEMO_HOME/kustomization.yaml
|
||||
secretGenerator:
|
||||
- name: mysecrets
|
||||
kvSources:
|
||||
- name: kvMaker
|
||||
pluginType: go
|
||||
args:
|
||||
- FRUIT
|
||||
- VEGETABLE
|
||||
EOF
|
||||
```
|
||||
|
||||
Finally, generate the secret, setting
|
||||
`XDG_CONFIG_HOME` so that the plugin
|
||||
can be found under `$DEMO_HOME`:
|
||||
|
||||
<!-- @build2 @test -->
|
||||
```
|
||||
result=$( \
|
||||
XDG_CONFIG_HOME=$DEMO_HOME \
|
||||
kustomize \
|
||||
--enable_alpha_goplugins_accept_panic_risk \
|
||||
build $DEMO_HOME )
|
||||
echo "$result"
|
||||
# Spot check the result:
|
||||
test 1 == $(echo "$result" | grep -c "FRUIT: YXBwbGU=")
|
||||
```
|
||||
|
||||
This should emit something like:
|
||||
|
||||
> ```
|
||||
> apiVersion: v1
|
||||
> kind: Secret
|
||||
> metadata:
|
||||
> name: mysecrets-bdt27dbkd6
|
||||
> type: Opaque
|
||||
> data:
|
||||
> FRUIT: YXBwbGU=
|
||||
> VEGETABLE: Y2Fycm90
|
||||
> ```
|
||||
|
||||
i.e. a subset of the same values as above.
|
||||
|
||||
### Go Plugin Caveats
|
||||
|
||||
Kustomize supports Go plugins to allow someone to
|
||||
extend kustomize in type-safe fashion against a
|
||||
documented Go interface type, without having to
|
||||
get their code merged into the kustomize
|
||||
repository, and without having to maintain a
|
||||
permanent fork.
|
||||
|
||||
Go plugins work well, but fall short of what many
|
||||
people think of when they hear the word _plugin_.
|
||||
|
||||
Go plugin compilation creates an [ELF] formatted
|
||||
`.so` file, which by definition has no information
|
||||
about the _provenance_ of the file. One cannot
|
||||
know which version of Go was used, which packages
|
||||
were imported (and their version), what value of
|
||||
`GOOS` and `GOARCH` were used, etc. If the skew
|
||||
between the compilation conditions of the main
|
||||
program ELF and the plugin ELF are too great, the
|
||||
program will crash. Also, there's no certificate
|
||||
to check in a `.so` file, so no way to know who
|
||||
wrote it or what it does. A bare `.so` file, not
|
||||
packaged with provenance information, is not a
|
||||
suitable distrubution format. It's not what
|
||||
people expect from decades of adding features
|
||||
IDEs, browsers, CAD tools, graphics tools, etc.
|
||||
via things called _plugins_.
|
||||
|
||||
There's no reason why someone couldn't build a
|
||||
`.so` packaging mechanism into `go` to emit an ELF
|
||||
packaged with provenance allowing ELF
|
||||
compatibility checks, but this isn't supported in
|
||||
kustomize (or Go) at the time of writing.
|
||||
|
||||
To avoid provenance issues simply compile your Go
|
||||
plugins and the main program at the same time.
|
||||
Bundle them into a container image for use by
|
||||
downstream users and/or your continuous delivery
|
||||
bot. This is the intended usage idiom for Go
|
||||
plugins.
|
||||
|
||||
A `kustomize build` attempt with Go plugins that omits
|
||||
the flag
|
||||
|
||||
> `--enable_alpha_goplugins_accept_panic_risk`
|
||||
|
||||
will fail with an error message about skew risks.
|
||||
Flag use is an opt-in acknowledging the absence of
|
||||
`.so` provenance, an absence that doesn't matter
|
||||
to someone building the code from source.
|
||||
|
||||
|
||||
### Leveraging Go plugins to run non-Go code
|
||||
|
||||
|
||||
#### external services
|
||||
|
||||
For particular (user-created) transformations or
|
||||
generations, kustomize could prepare a request,
|
||||
send it to some service, and process a response.
|
||||
How to do this is a [solved problem][grpc]. The
|
||||
communication is struct-to-struct type safe - no
|
||||
need to write parsing code.
|
||||
|
||||
If the service is written in Go, and one can
|
||||
vendor its code, it's simplest to write a small Go
|
||||
plugin that calls it like a library rather than
|
||||
running the service as an independent process.
|
||||
|
||||
If the service is not written in Go, or if the
|
||||
source code is unavailable, one can use a small Go
|
||||
plugin to make the RPC.
|
||||
|
||||
|
||||
#### subprocesses (also known as `exec` plugins)
|
||||
|
||||
In this approach one arranges for executable files
|
||||
to be identified by name or location, and runs
|
||||
them as a kustomize subprocess, sending a
|
||||
'request' to the subprocess its `stdin`, and
|
||||
obtaining a 'response' via its `stdout`.
|
||||
|
||||
An immediate way to use an arbitrary executable
|
||||
with arbitrary i/o requirements is through a Go
|
||||
plugin that runs the executable via
|
||||
[`exec.Command`]. Each special purpose
|
||||
tranformation or generation - needed by `kustomize
|
||||
build` - will require it's own `stdin`/`stdout`
|
||||
processing to convert from/to the Go types that
|
||||
kustomize uses.
|
||||
|
||||
The Go plugin provides this translation layer, and
|
||||
handles process exit codes.
|
||||
@@ -136,7 +136,7 @@ a label, but one can always edit `kustomization.yaml` directly:
|
||||
|
||||
<!-- @customizeLabels @test -->
|
||||
```
|
||||
sed -i 's/app: helloworld/app: prod/' \
|
||||
sed -i.bak 's/app: helloworld/app: prod/' \
|
||||
$DEMO_HOME/kustomization.yaml
|
||||
```
|
||||
|
||||
@@ -181,6 +181,8 @@ patchesStrategicMerge:
|
||||
EOF
|
||||
```
|
||||
|
||||
A `mysql-persistent-storage` persistent disk needs to exist for it to run successfully.
|
||||
|
||||
Lets break this down:
|
||||
|
||||
- In the first step, we created a YAML file named
|
||||
|
||||
@@ -2,7 +2,7 @@ apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: mysql-pass-d2gtcm2t2k
|
||||
name: mysql-pass
|
||||
type: Opaque
|
||||
data:
|
||||
# Default password is "admin".
|
||||
|
||||
@@ -1,33 +1,38 @@
|
||||
# remote targets
|
||||
|
||||
`kustomize build` can be run against a url. The effect is the same as cloing the repo, checking out the specified ref,
|
||||
then running `kustomize build` against the desired directory in the local copy.
|
||||
`kustomize build` can be run on a URL.
|
||||
|
||||
Take `github.com/kubernetes-sigs/kustomize//examples/multibases?ref=v1.0.6` as an example.
|
||||
According to [multibases](multibases/README.md) demo, this kustomization contains three Pod objects with names as
|
||||
`cluster-a-dev-myapp-pod`, `cluster-a-stag-myapp-pod`, `cluster-a-prod-myapp-pod`.
|
||||
Running `kustomize build` against the url gives the same output.
|
||||
The effect is the same as cloning the repo, checking out a particular
|
||||
_ref_ (commit hash, branch name, release tag, etc.),
|
||||
then running `kustomize build` against the desired
|
||||
directory in the local copy.
|
||||
|
||||
To try this immediately, run a build against the kustomization
|
||||
in the [multibases](multibases/README.md) example. There's
|
||||
one pod in the output:
|
||||
|
||||
<!-- @remoteOverlayBuild @test -->
|
||||
|
||||
```
|
||||
target="github.com/kubernetes-sigs/kustomize//examples/multibases/dev/?ref=v1.0.6"
|
||||
test 1 == \
|
||||
$(kustomize build $target | grep dev-myapp-pod | wc -l); \
|
||||
echo $?
|
||||
```
|
||||
|
||||
Run against the overlay in that example to get three pods
|
||||
(the overlay combines the dev, staging and prod bases for
|
||||
someone who wants to send them all at the same time):
|
||||
|
||||
<!-- @remoteBuild @test -->
|
||||
```
|
||||
target=github.com/kubernetes-sigs/kustomize//examples/multibases?ref=v1.0.6
|
||||
target="https://github.com/kubernetes-sigs/kustomize//examples/multibases?ref=v1.0.6"
|
||||
test 3 == \
|
||||
$(kustomize build $target | grep cluster-a-.*-myapp-pod | wc -l); \
|
||||
echo $?
|
||||
```
|
||||
|
||||
Overlays can be remote as well:
|
||||
|
||||
<!-- @remoteOverlayBuild @test -->
|
||||
|
||||
```
|
||||
target=github.com/kubernetes-sigs/kustomize//examples/multibases/dev/?ref=v1.0.6
|
||||
test 1 == \
|
||||
$(kustomize build $target | grep cluster-a-dev-myapp-pod | wc -l); \
|
||||
echo $?
|
||||
```
|
||||
|
||||
A base can also be specified as a URL:
|
||||
A base can be a URL:
|
||||
|
||||
<!-- @createOverlay @test -->
|
||||
```
|
||||
@@ -39,7 +44,10 @@ bases:
|
||||
namePrefix: remote-
|
||||
EOF
|
||||
```
|
||||
Running `kustomize build $DEMO_HOME` and confirm the output contains three Pods and all have `remote-` prefix.
|
||||
|
||||
Build this to confirm that all three pods from the base
|
||||
have the `remote-` prefix.
|
||||
|
||||
<!-- @remoteBases @test -->
|
||||
```
|
||||
test 3 == \
|
||||
@@ -48,6 +56,7 @@ test 3 == \
|
||||
```
|
||||
|
||||
## URL format
|
||||
|
||||
The url should follow
|
||||
[hashicorp/go-getter URL format](https://github.com/hashicorp/go-getter#url-format).
|
||||
Here are some example urls pointing to Github repos following this convention.
|
||||
|
||||
168
examples/transformerconfigs/README.md
Normal file
168
examples/transformerconfigs/README.md
Normal file
@@ -0,0 +1,168 @@
|
||||
# Transformer Configurations
|
||||
|
||||
Kustomize creates new resources by applying a series of transformations to an original
|
||||
set of resources. Kustomize provides the following default transformers:
|
||||
|
||||
- annotations
|
||||
- images
|
||||
- labels
|
||||
- name reference
|
||||
- namespace
|
||||
- prefix/suffix
|
||||
- variable reference
|
||||
|
||||
A `fieldSpec` list, in a transformer's configuration, determines which resource types and which fields
|
||||
within those types the transformer can modify.
|
||||
|
||||
## FieldSpec
|
||||
|
||||
FieldSpec is a type that represents a path to a field in one kind of resource.
|
||||
|
||||
```yaml
|
||||
group: some-group
|
||||
version: some-version
|
||||
kind: some-kind
|
||||
path: path/to/the/field
|
||||
create: false
|
||||
```
|
||||
|
||||
If `create` is set to `true`, the transformer creates the path to the field in the resource if the path is not already found. This is most useful for label and annotation transformers, where the path for labels or annotations may not be set before the transformation.
|
||||
|
||||
## Images transformer
|
||||
|
||||
The default images transformer updates the specified image key values found in paths that include
|
||||
`containers` and `initcontainers` sub-paths.
|
||||
If found, the `image` key value is customized by the values set in the `newName`, `newTag`, and `digest` fields.
|
||||
The `name` field should match the `image` key value in a resource.
|
||||
|
||||
Example kustomization.yaml:
|
||||
|
||||
```yaml
|
||||
images:
|
||||
- name: postgres
|
||||
newName: my-registry/my-postgres
|
||||
newTag: v1
|
||||
- name: nginx
|
||||
newTag: 1.8.0
|
||||
- name: my-demo-app
|
||||
newName: my-app
|
||||
- name: alpine
|
||||
digest: sha256:25a0d4
|
||||
```
|
||||
|
||||
Image transformer configurations can be customized by creating a list of `images` containing the `path` and `kind` fields.
|
||||
The images transformation tutorial shows how to specify the default images transformer and customize the [images transformer configuration](images/README.md).
|
||||
|
||||
## Prefix/suffix transformer
|
||||
|
||||
The prefix/suffix transformer adds a prefix/suffix to the `metadata/name` field for all resources. Here is the default prefix transformer configuration:
|
||||
|
||||
```yaml
|
||||
namePrefix:
|
||||
- path: metadata/name
|
||||
```
|
||||
|
||||
Example kustomization.yaml:
|
||||
|
||||
```yaml
|
||||
|
||||
namePrefix:
|
||||
alices-
|
||||
|
||||
nameSuffix:
|
||||
-v2
|
||||
```
|
||||
|
||||
## Labels transformer
|
||||
|
||||
The labels transformer adds labels to the `metadata/labels` field for all resources. It also adds labels to the `spec/selector` field in all Service resources as well as the `spec/selector/matchLabels` field in all Deployment resources.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
commonLabels:
|
||||
- path: metadata/labels
|
||||
create: true
|
||||
|
||||
- path: spec/selector
|
||||
create: true
|
||||
version: v1
|
||||
kind: Service
|
||||
|
||||
- path: spec/selector/matchLabels
|
||||
create: true
|
||||
kind: Deployment
|
||||
```
|
||||
|
||||
Example kustomization.yaml:
|
||||
|
||||
```yaml
|
||||
commonLabels:
|
||||
someName: someValue
|
||||
owner: alice
|
||||
app: bingo
|
||||
```
|
||||
|
||||
## Annotations transformer
|
||||
|
||||
The annotations transformer adds annotations to the `metadata/annotations` field for all resources.
|
||||
Annotations are also added to `spec/template/metadata/annotations` for Deployment,
|
||||
ReplicaSet, DaemonSet, StatefulSet, Job, and CronJob resources, and `spec/jobTemplate/spec/template/metadata/annotations`
|
||||
for CronJob resources.
|
||||
|
||||
Example kustomization.yaml
|
||||
|
||||
```yaml
|
||||
commonAnnotations:
|
||||
oncallPager: 800-555-1212
|
||||
```
|
||||
|
||||
## Name reference transformer
|
||||
|
||||
Name reference transformer's configuration is different from all other transformers. It contains a list of `nameReferences`, which represent all of the possible fields that a type could be used as a reference in other types of resources. A `nameReference` contains a type such as ConfigMap as well as a list of `fieldSpecs` where ConfigMap is referenced in other resources. Here is an example:
|
||||
|
||||
```yaml
|
||||
kind: ConfigMap
|
||||
version: v1
|
||||
fieldSpecs:
|
||||
- kind: Pod
|
||||
version: v1
|
||||
path: spec/volumes/configMap/name
|
||||
- kind: Deployment
|
||||
path: spec/template/spec/volumes/configMap/name
|
||||
- kind: Job
|
||||
path: spec/template/spec/volumes/configMap/name
|
||||
```
|
||||
|
||||
Name reference transformer's configuration contains a list of `nameReferences` for resources such as ConfigMap, Secret, Service, Role, and ServiceAccount. Here is an example configuration:
|
||||
|
||||
```yaml
|
||||
nameReference:
|
||||
- kind: ConfigMap
|
||||
version: v1
|
||||
fieldSpecs:
|
||||
- path: spec/volumes/configMap/name
|
||||
version: v1
|
||||
kind: Pod
|
||||
- path: spec/containers/env/valueFrom/configMapKeyRef/name
|
||||
version: v1
|
||||
kind: Pod
|
||||
# ...
|
||||
- kind: Secret
|
||||
version: v1
|
||||
fieldSpecs:
|
||||
- path: spec/volumes/secret/secretName
|
||||
version: v1
|
||||
kind: Pod
|
||||
- path: spec/containers/env/valueFrom/secretKeyRef/name
|
||||
version: v1
|
||||
kind: Pod
|
||||
```
|
||||
|
||||
## Customizing transformer configurations
|
||||
|
||||
In addition to the default transformers, you can create custom transformer configurations. Save the default transformer configurations to a local directory by calling `kustomize config save -d`, and modify and use these configurations. This tutorial shows how to create custom transformer configurations:
|
||||
|
||||
- [support a CRD type](crd/README.md)
|
||||
- add extra fields for variable substitution
|
||||
- add extra fields for name reference
|
||||
139
examples/transformerconfigs/crd/README.md
Normal file
139
examples/transformerconfigs/crd/README.md
Normal file
@@ -0,0 +1,139 @@
|
||||
## Supporting Custom Resources (defined by a CRD)
|
||||
|
||||
This tutorial shows how to add transformer configurations to support a custom resource.
|
||||
|
||||
Create a workspace by
|
||||
<!-- @createws @test -->
|
||||
```
|
||||
DEMO_HOME=$(mktemp -d)
|
||||
```
|
||||
|
||||
### Adding a custom resource
|
||||
|
||||
Consider a CRD of kind `MyKind` with fields
|
||||
- `.spec.secretRef.name` reference a Secret
|
||||
- `.spec.beeRef.name` reference an instance of CRD `Bee`
|
||||
- `.spec.containers.command` as the list of container commands
|
||||
- `.spec.selectors` as the label selectors
|
||||
|
||||
Add the following file to configure the transformers for the above fields
|
||||
<!-- @addConfig @test -->
|
||||
```
|
||||
mkdir $DEMO_HOME/kustomizeconfig
|
||||
cat > $DEMO_HOME/kustomizeconfig/mykind.yaml << EOF
|
||||
|
||||
commonLabels:
|
||||
- path: spec/selectors
|
||||
create: true
|
||||
kind: MyKind
|
||||
|
||||
nameReference:
|
||||
- kind: Bee
|
||||
fieldSpecs:
|
||||
- path: spec/beeRef/name
|
||||
kind: MyKind
|
||||
- kind: Secret
|
||||
fieldSpecs:
|
||||
- path: spec/secretRef/name
|
||||
kind: MyKind
|
||||
|
||||
varReference:
|
||||
- path: spec/containers/command
|
||||
kind: MyKind
|
||||
- path: spec/beeRef/action
|
||||
kind: MyKind
|
||||
|
||||
EOF
|
||||
```
|
||||
|
||||
### Apply config
|
||||
|
||||
Create a file with some resources that
|
||||
includes an instance of `MyKind`:
|
||||
|
||||
<!-- @createResource @test -->
|
||||
```
|
||||
cat > $DEMO_HOME/resources.yaml << EOF
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: crdsecret
|
||||
data:
|
||||
PATH: YmJiYmJiYmIK
|
||||
---
|
||||
apiVersion: v1beta1
|
||||
kind: Bee
|
||||
metadata:
|
||||
name: bee
|
||||
spec:
|
||||
action: fly
|
||||
---
|
||||
apiVersion: jingfang.example.com/v1beta1
|
||||
kind: MyKind
|
||||
metadata:
|
||||
name: mykind
|
||||
spec:
|
||||
secretRef:
|
||||
name: crdsecret
|
||||
beeRef:
|
||||
name: bee
|
||||
action: \$(BEE_ACTION)
|
||||
containers:
|
||||
- command:
|
||||
- "echo"
|
||||
- "\$(BEE_ACTION)"
|
||||
image: myapp
|
||||
EOF
|
||||
```
|
||||
|
||||
Create a kustomization referring to it:
|
||||
|
||||
<!-- @createKustomization @test -->
|
||||
```
|
||||
cat > $DEMO_HOME/kustomization.yaml << EOF
|
||||
resources:
|
||||
- resources.yaml
|
||||
|
||||
namePrefix: test-
|
||||
|
||||
commonLabels:
|
||||
foo: bar
|
||||
|
||||
vars:
|
||||
- name: BEE_ACTION
|
||||
objref:
|
||||
kind: Bee
|
||||
name: bee
|
||||
apiVersion: v1beta1
|
||||
fieldref:
|
||||
fieldpath: spec.action
|
||||
EOF
|
||||
```
|
||||
|
||||
Use the customized transformer configurations by specifying them
|
||||
in the kustomization file:
|
||||
<!-- @addTransformerConfigs @test -->
|
||||
```
|
||||
cat >> $DEMO_HOME/kustomization.yaml << EOF
|
||||
configurations:
|
||||
- kustomizeconfig/mykind.yaml
|
||||
EOF
|
||||
```
|
||||
|
||||
Run `kustomize build` and verify that the namereference is correctly resolved.
|
||||
|
||||
<!-- @build @test -->
|
||||
```
|
||||
test 2 == \
|
||||
$(kustomize build $DEMO_HOME | grep -A 2 ".*Ref" | grep "test-" | wc -l); \
|
||||
echo $?
|
||||
```
|
||||
|
||||
Run `kustomize build` and verify that the vars correctly resolved.
|
||||
|
||||
<!-- @verify @test -->
|
||||
```
|
||||
test 0 == \
|
||||
$(kustomize build $DEMO_HOME | grep "BEE_ACTION" | wc -l); \
|
||||
echo $?
|
||||
```
|
||||
16
examples/transformerconfigs/crd/kustomization.yaml
Normal file
16
examples/transformerconfigs/crd/kustomization.yaml
Normal file
@@ -0,0 +1,16 @@
|
||||
resources:
|
||||
- resources.yaml
|
||||
|
||||
namePrefix: test-
|
||||
|
||||
commonLabels:
|
||||
foo: bar
|
||||
|
||||
vars:
|
||||
- name: BEE_ACTION
|
||||
objref:
|
||||
kind: Bee
|
||||
name: bee
|
||||
apiVersion: v1beta1
|
||||
fieldref:
|
||||
fieldpath: spec.action
|
||||
29
examples/transformerconfigs/crd/resources.yaml
Normal file
29
examples/transformerconfigs/crd/resources.yaml
Normal file
@@ -0,0 +1,29 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: crdsecret
|
||||
data:
|
||||
PATH: YmJiYmJiYmIK
|
||||
---
|
||||
apiVersion: v1beta1
|
||||
kind: Bee
|
||||
metadata:
|
||||
name: bee
|
||||
spec:
|
||||
action: fly
|
||||
---
|
||||
apiVersion: jingfang.example.com/v1beta1
|
||||
kind: MyKind
|
||||
metadata:
|
||||
name: mykind
|
||||
spec:
|
||||
secretRef:
|
||||
name: crdsecret
|
||||
beeRef:
|
||||
name: bee
|
||||
action: $(BEE_ACTION)
|
||||
containers:
|
||||
- command:
|
||||
- "echo"
|
||||
- "$(BEE_ACTION)"
|
||||
image: myapp
|
||||
128
examples/transformerconfigs/images/README.md
Normal file
128
examples/transformerconfigs/images/README.md
Normal file
@@ -0,0 +1,128 @@
|
||||
## Images transformations
|
||||
|
||||
This tutorial shows how to modify images in resources, and create a custom images transformer configuration.
|
||||
|
||||
Create a workspace by
|
||||
<!-- @createws @test -->
|
||||
```
|
||||
DEMO_HOME=$(mktemp -d)
|
||||
```
|
||||
|
||||
### Adding a custom resource
|
||||
|
||||
Consider a Custom Resource Definition(CRD) of kind `MyKind` with field
|
||||
- `.spec.runLatest.container.image` referencing an image
|
||||
|
||||
Add the following file to configure the images transformer for the CRD:
|
||||
|
||||
<!-- @addConfig @test -->
|
||||
```
|
||||
mkdir $DEMO_HOME/kustomizeconfig
|
||||
cat > $DEMO_HOME/kustomizeconfig/mykind.yaml << EOF
|
||||
|
||||
images:
|
||||
- path: spec/runLatest/container/image
|
||||
kind: MyKind
|
||||
EOF
|
||||
```
|
||||
|
||||
### Apply config
|
||||
|
||||
Create a file with some resources that includes an instance of `MyKind`:
|
||||
|
||||
<!-- @createResource @test -->
|
||||
```
|
||||
cat > $DEMO_HOME/resources.yaml << EOF
|
||||
|
||||
apiVersion: config/v1
|
||||
kind: MyKind
|
||||
metadata:
|
||||
name: testSvc
|
||||
spec:
|
||||
runLatest:
|
||||
container:
|
||||
image: crd-image
|
||||
containers:
|
||||
- image: docker
|
||||
name: ecosystem
|
||||
- image: my-mysql
|
||||
name: testing-1
|
||||
---
|
||||
group: apps
|
||||
apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy1
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
initContainers:
|
||||
- name: nginx2
|
||||
image: my-app
|
||||
- name: init-alpine
|
||||
image: alpine:1.8.0
|
||||
EOF
|
||||
```
|
||||
|
||||
Create a kustomization.yaml referring to it:
|
||||
|
||||
<!-- @createKustomization @test -->
|
||||
```
|
||||
cat > $DEMO_HOME/kustomization.yaml << EOF
|
||||
resources:
|
||||
- resources.yaml
|
||||
|
||||
images:
|
||||
- name: crd-image
|
||||
newName: new-crd-image
|
||||
newTag: new-v1-tag
|
||||
- name: my-app
|
||||
newName: new-app-1
|
||||
newTag: MYNEWTAG-1
|
||||
- name: my-mysql
|
||||
newName: prod-mysql
|
||||
newTag: v3
|
||||
- name: docker
|
||||
newName: my-docker2
|
||||
digest: sha256:25a0d4
|
||||
EOF
|
||||
```
|
||||
|
||||
Use the customized transformer configurations by specifying them
|
||||
in the kustomization file:
|
||||
<!-- @addTransformerConfigs @test -->
|
||||
```
|
||||
cat >> $DEMO_HOME/kustomization.yaml << EOF
|
||||
configurations:
|
||||
- kustomizeconfig/mykind.yaml
|
||||
EOF
|
||||
```
|
||||
|
||||
Run `kustomize build` and verify that the images have been updated.
|
||||
|
||||
<!-- @build @test -->
|
||||
```
|
||||
test 1 == \
|
||||
$(kustomize build $DEMO_HOME | grep -A 2 ".*image" | grep "new-crd-image:new-v1-tag" | wc -l); \
|
||||
echo $?
|
||||
```
|
||||
|
||||
<!-- @build @test -->
|
||||
```
|
||||
test 1 == \
|
||||
$(kustomize build $DEMO_HOME | grep -A 2 ".*image" | grep "new-app-1:MYNEWTAG-1" | wc -l); \
|
||||
echo $?
|
||||
```
|
||||
|
||||
<!-- @build @test -->
|
||||
```
|
||||
test 1 == \
|
||||
$(kustomize build $DEMO_HOME | grep -A 2 ".*image" | grep "my-docker2@sha" | wc -l); \
|
||||
echo $?
|
||||
```
|
||||
<!-- @build @test -->
|
||||
```
|
||||
test 1 == \
|
||||
$(kustomize build $DEMO_HOME | grep -A 2 ".*image" | grep "prod-mysql:v3" | wc -l); \
|
||||
echo $?
|
||||
```
|
||||
19
examples/transformerconfigs/images/kustomization.yaml
Normal file
19
examples/transformerconfigs/images/kustomization.yaml
Normal file
@@ -0,0 +1,19 @@
|
||||
resources:
|
||||
- resources.yaml
|
||||
|
||||
configurations:
|
||||
- kustomizeconfig/mykind.yaml
|
||||
|
||||
images:
|
||||
- name: crd-image
|
||||
newName: new-crd-image
|
||||
newTag: new-v1-tag
|
||||
- name: my-app
|
||||
newName: new-app-1
|
||||
newTag: MYNEWTAG-1
|
||||
- name: my-mysql
|
||||
newName: prod-mysql
|
||||
newTag: v3
|
||||
- name: docker
|
||||
newName: my-docker2
|
||||
digest: sha256:25a0d4
|
||||
3
examples/transformerconfigs/images/mykind.yaml
Normal file
3
examples/transformerconfigs/images/mykind.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
images:
|
||||
- path: spec/runLatest/container/image
|
||||
kind: MyKind
|
||||
27
examples/transformerconfigs/images/resources.yaml
Normal file
27
examples/transformerconfigs/images/resources.yaml
Normal file
@@ -0,0 +1,27 @@
|
||||
apiVersion: config/v1
|
||||
kind: MyKind
|
||||
metadata:
|
||||
name: testSvc
|
||||
spec:
|
||||
runLatest:
|
||||
container:
|
||||
image: crd-image
|
||||
containers:
|
||||
- image: docker
|
||||
name: ecosystem
|
||||
- image: my-mysql
|
||||
name: testing-1
|
||||
---
|
||||
group: apps
|
||||
apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy1
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
initContainers:
|
||||
- name: nginx2
|
||||
image: my-app
|
||||
- name: init-alpine
|
||||
image: alpine:1.8.0
|
||||
@@ -1,6 +1,6 @@
|
||||
# Demo: Injecting k8s runtime data into containers
|
||||
|
||||
In this tutorial, you will learn how to use `kustomize` to declare a variable reference and substitute it in container's command.
|
||||
In this tutorial, you will learn how to use `kustomize` to declare a variable reference and substitute it in container's command. Note that, the substitution is not for arbitrary fields, it is only applicable to container env, args and command.
|
||||
|
||||
To run WordPress, it's necessary to
|
||||
|
||||
|
||||
39
go.mod
Normal file
39
go.mod
Normal file
@@ -0,0 +1,39 @@
|
||||
module sigs.k8s.io/kustomize
|
||||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/PuerkitoBio/purell v1.1.0 // indirect
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||
github.com/emicklei/go-restful v2.9.3+incompatible // indirect
|
||||
github.com/evanphx/json-patch v3.0.0+incompatible
|
||||
github.com/ghodss/yaml v1.0.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.0.0-20180322222829-3a0015ad55fa // indirect
|
||||
github.com/go-openapi/jsonreference v0.0.0-20180322222742-3fb327e6747d // indirect
|
||||
github.com/go-openapi/spec v0.0.0-20180415031709-bcff419492ee
|
||||
github.com/go-openapi/swag v0.0.0-20180405201759-811b1089cde9 // indirect
|
||||
github.com/gogo/protobuf v1.0.0 // indirect
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect
|
||||
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf // indirect
|
||||
github.com/googleapis/gnostic v0.1.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/json-iterator/go v0.0.0-20180315132816-ca39e5af3ece // indirect
|
||||
github.com/mailru/easyjson v0.0.0-20180606163543-3fdea8d05856 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v0.0.0-20180228065516-1df9eeb2bb81 // indirect
|
||||
github.com/onsi/ginkgo v1.8.0 // indirect
|
||||
github.com/onsi/gomega v1.5.0 // indirect
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/spf13/cobra v0.0.2
|
||||
github.com/spf13/pflag v1.0.1
|
||||
github.com/stretchr/testify v1.3.0 // indirect
|
||||
golang.org/x/net v0.0.0-20190225153610-fe579d43d832 // indirect
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.1
|
||||
k8s.io/api v0.0.0-20180510062335-53d615ae3f44
|
||||
k8s.io/apimachinery v0.0.0-20180510061931-13b73596e4b6
|
||||
k8s.io/client-go v7.0.0+incompatible
|
||||
k8s.io/kube-openapi v0.0.0-20180510204742-b3f03f553288
|
||||
sigs.k8s.io/yaml v1.1.0
|
||||
)
|
||||
90
go.sum
Normal file
90
go.sum
Normal file
@@ -0,0 +1,90 @@
|
||||
github.com/PuerkitoBio/purell v1.1.0 h1:rmGxhojJlM0tuKtfdvliR84CFHljx9ag64t2xmVkjK4=
|
||||
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/emicklei/go-restful v2.9.3+incompatible h1:2OwhVdhtzYUp5P5wuGsVDPagKSRd9JK72sJCHVCXh5g=
|
||||
github.com/emicklei/go-restful v2.9.3+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
github.com/evanphx/json-patch v3.0.0+incompatible h1:l91aby7TzBXBdmF8heZqjskeH9f3g7ZOL8/sSe+vTlU=
|
||||
github.com/evanphx/json-patch v3.0.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-openapi/jsonpointer v0.0.0-20180322222829-3a0015ad55fa h1:hr8WVDjg4JKtQptZpzyb196TmruCs7PIsdJz8KAOZp8=
|
||||
github.com/go-openapi/jsonpointer v0.0.0-20180322222829-3a0015ad55fa/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
|
||||
github.com/go-openapi/jsonreference v0.0.0-20180322222742-3fb327e6747d h1:k3UQ7Z8yFYq0BNkYykKIheY0HlZBl1Hku+pO9HE9FNU=
|
||||
github.com/go-openapi/jsonreference v0.0.0-20180322222742-3fb327e6747d/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
|
||||
github.com/go-openapi/spec v0.0.0-20180415031709-bcff419492ee h1:eo0HQoNFtbiEc7+1gRF9pgW6azx8a1cO2fXcqq1MuD0=
|
||||
github.com/go-openapi/spec v0.0.0-20180415031709-bcff419492ee/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
|
||||
github.com/go-openapi/swag v0.0.0-20180405201759-811b1089cde9 h1:+vsw187FKvA2QUGAcE+vQSfyxqLbUXixPYRRMAzwu04=
|
||||
github.com/go-openapi/swag v0.0.0-20180405201759-811b1089cde9/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
|
||||
github.com/gogo/protobuf v1.0.0 h1:2jyBKDKU/8v3v2xVR2PtiWQviFUyiaGk2rpfyFT8rTM=
|
||||
github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck=
|
||||
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
|
||||
github.com/googleapis/gnostic v0.1.0 h1:rVsPeBmXbYv4If/cumu1AzZPwV58q433hvONV1UEZoI=
|
||||
github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/json-iterator/go v0.0.0-20180315132816-ca39e5af3ece h1:3HJXp/18JmMk5sjBP3LDUBtWjczCvynxaeAF6b6kWp8=
|
||||
github.com/json-iterator/go v0.0.0-20180315132816-ca39e5af3ece/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/mailru/easyjson v0.0.0-20180606163543-3fdea8d05856 h1:hOnidOuIWNsFRPcxxStGeN3NNm4n4+w6KJ9cVJIh70o=
|
||||
github.com/mailru/easyjson v0.0.0-20180606163543-3fdea8d05856/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180228065516-1df9eeb2bb81 h1:ImOHKpmdLPXWX5KSYquUWXKaopEPuY7TPPUo18u9aOI=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180228065516-1df9eeb2bb81/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w=
|
||||
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo=
|
||||
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/spf13/cobra v0.0.2 h1:NfkwRbgViGoyjBKsLI0QMDcuMnhM+SBg3T0cGfpvKDE=
|
||||
github.com/spf13/cobra v0.0.2/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4=
|
||||
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190225153610-fe579d43d832 h1:2IdId8zoI92l1bUzjAOygcAOkmCe13HY1j0rqPPPzB8=
|
||||
golang.org/x/net v0.0.0-20190225153610-fe579d43d832/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 h1:bjcUS9ztw9kFmmIxJInhon/0Is3p+EHBKNgquIzo1OI=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
k8s.io/api v0.0.0-20180510062335-53d615ae3f44 h1:zQ8YhMpuc1QJoor+Vm1moP9iEOyaQgOjSj3bo/zUEXE=
|
||||
k8s.io/api v0.0.0-20180510062335-53d615ae3f44/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA=
|
||||
k8s.io/apimachinery v0.0.0-20180510061931-13b73596e4b6 h1:pJrzRmry9HLPxkVGMk57cfeGRy/WG0oYXuji9t4zD1M=
|
||||
k8s.io/apimachinery v0.0.0-20180510061931-13b73596e4b6/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0=
|
||||
k8s.io/client-go v7.0.0+incompatible h1:kiH+Y6hn+pc78QS/mtBfMJAMIIaWevHi++JvOGEEQp4=
|
||||
k8s.io/client-go v7.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s=
|
||||
k8s.io/kube-openapi v0.0.0-20180510204742-b3f03f553288 h1:AhFqcaw5JbAAaZHxTe1fT+Jtek0pZmIwwt6FbsMA9to=
|
||||
k8s.io/kube-openapi v0.0.0-20180510204742-b3f03f553288/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc=
|
||||
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
@@ -15,7 +15,7 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
// Package error has contextual error types.
|
||||
package error
|
||||
package kusterr
|
||||
|
||||
import "fmt"
|
||||
|
||||
@@ -14,17 +14,14 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package error
|
||||
package kusterr
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/pkg/constants"
|
||||
)
|
||||
|
||||
func TestConfigmapError_Error(t *testing.T) {
|
||||
filepath := "/path/to/" + constants.KustomizationFileName
|
||||
errorMsg := "configmap name is missing"
|
||||
me := ConfigmapError{Path: filepath, ErrorMsg: errorMsg}
|
||||
|
||||
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package error
|
||||
package kusterr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -14,17 +14,14 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package error
|
||||
package kusterr
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/pkg/constants"
|
||||
)
|
||||
|
||||
func TestPatchError_Error(t *testing.T) {
|
||||
filepath := "/path/to/" + constants.KustomizationFileName
|
||||
patchfilepath := "/path/to/patch/patch.yaml"
|
||||
errorMsg := "file not found"
|
||||
me := PatchError{KustomizationPath: filepath, PatchFilepath: patchfilepath, ErrorMsg: errorMsg}
|
||||
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package error
|
||||
package kusterr
|
||||
|
||||
import "fmt"
|
||||
|
||||
@@ -14,17 +14,14 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package error
|
||||
package kusterr
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/pkg/constants"
|
||||
)
|
||||
|
||||
func TestResourceError_Error(t *testing.T) {
|
||||
filepath := "/path/to/" + constants.KustomizationFileName
|
||||
resourcefilepath := "/path/to/resource/deployment.yaml"
|
||||
errorMsg := "file not found"
|
||||
me := ResourceError{KustomizationPath: filepath, ResourceFilepath: resourcefilepath, ErrorMsg: errorMsg}
|
||||
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package error
|
||||
package kusterr
|
||||
|
||||
import "fmt"
|
||||
|
||||
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package error
|
||||
package kusterr
|
||||
|
||||
import (
|
||||
"strings"
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
)
|
||||
|
||||
func TestSecretError_Error(t *testing.T) {
|
||||
filepath := "/path/to/secret.yaml"
|
||||
errorMsg := "missing a command"
|
||||
me := SecretError{KustomizationPath: filepath, ErrorMsg: errorMsg}
|
||||
if !strings.Contains(me.Error(), filepath) {
|
||||
@@ -15,12 +15,11 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
// Package error has contextual error types.
|
||||
package error
|
||||
package kusterr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// YamlFormatError represents error with yaml file name where json/yaml format error happens.
|
||||
@@ -35,11 +34,15 @@ func (e YamlFormatError) Error() string {
|
||||
|
||||
// Handler handles YamlFormatError
|
||||
func Handler(e error, path string) error {
|
||||
if err, ok := e.(yaml.YAMLSyntaxError); ok {
|
||||
if isYAMLSyntaxError(e) {
|
||||
return YamlFormatError{
|
||||
Path: path,
|
||||
ErrorMsg: err.Error(),
|
||||
ErrorMsg: e.Error(),
|
||||
}
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
func isYAMLSyntaxError(e error) bool {
|
||||
return strings.Contains(e.Error(), "error converting YAML to JSON") || strings.Contains(e.Error(), "error unmarshaling JSON")
|
||||
}
|
||||
@@ -14,26 +14,17 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package error
|
||||
package kusterr
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
"sigs.k8s.io/kustomize/pkg/constants"
|
||||
)
|
||||
|
||||
var (
|
||||
filepath = "/path/to/" + constants.KustomizationFileName
|
||||
expected = "YAML file [/path/to/kustomization.yaml] encounters a format error.\n" +
|
||||
const (
|
||||
filepath = "/path/to/whatever"
|
||||
expected = "YAML file [/path/to/whatever] encounters a format error.\n" +
|
||||
"error converting YAML to JSON: yaml: line 2: found character that cannot start any token\n"
|
||||
doc = `
|
||||
foo:
|
||||
- fiz
|
||||
- fu
|
||||
`
|
||||
)
|
||||
|
||||
func TestYamlFormatError_Error(t *testing.T) {
|
||||
@@ -47,8 +38,7 @@ func TestYamlFormatError_Error(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestErrorHandler(t *testing.T) {
|
||||
f := foo{}
|
||||
err := yaml.NewYAMLToJSONDecoder(bytes.NewReader([]byte(doc))).Decode(&f)
|
||||
err := fmt.Errorf("error converting YAML to JSON: yaml: line 2: found character that cannot start any token")
|
||||
testErr := Handler(err, filepath)
|
||||
expectedErr := fmt.Errorf("format error message")
|
||||
fmtErr := Handler(expectedErr, filepath)
|
||||
@@ -62,6 +52,3 @@ func TestErrorHandler(t *testing.T) {
|
||||
t.Errorf("Expected : %s\n, but found : %s\n", expected, testErr.Error())
|
||||
}
|
||||
}
|
||||
|
||||
//type foo struct
|
||||
type foo struct{}
|
||||
88
internal/loadertest/fakeloader.go
Normal file
88
internal/loadertest/fakeloader.go
Normal file
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package loadertest holds a fake for the Loader interface.
|
||||
package loadertest
|
||||
|
||||
import (
|
||||
"log"
|
||||
"sigs.k8s.io/kustomize/pkg/fs"
|
||||
"sigs.k8s.io/kustomize/pkg/ifc"
|
||||
"sigs.k8s.io/kustomize/pkg/loader"
|
||||
)
|
||||
|
||||
// FakeLoader encapsulates the delegate Loader and the fake file system.
|
||||
type FakeLoader struct {
|
||||
fs fs.FileSystem
|
||||
delegate ifc.Loader
|
||||
}
|
||||
|
||||
// NewFakeLoader returns a Loader that uses a fake filesystem.
|
||||
// The loader will be restricted to root only.
|
||||
// The initialDir argument should be an absolute file path.
|
||||
func NewFakeLoader(initialDir string) FakeLoader {
|
||||
return NewFakeLoaderWithRestrictor(
|
||||
loader.RestrictionRootOnly, initialDir)
|
||||
}
|
||||
|
||||
// NewFakeLoaderWithRestrictor returns a Loader that
|
||||
// uses a fake filesystem.
|
||||
// The initialDir argument should be an absolute file path.
|
||||
func NewFakeLoaderWithRestrictor(
|
||||
lr loader.LoadRestrictorFunc, initialDir string) FakeLoader {
|
||||
// Create fake filesystem and inject it into initial Loader.
|
||||
fSys := fs.MakeFakeFS()
|
||||
fSys.Mkdir(initialDir)
|
||||
ldr, err := loader.NewLoader(lr, initialDir, fSys)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to make loader: %v", err)
|
||||
}
|
||||
return FakeLoader{fs: fSys, delegate: ldr}
|
||||
}
|
||||
|
||||
// AddFile adds a fake file to the file system.
|
||||
func (f FakeLoader) AddFile(fullFilePath string, content []byte) error {
|
||||
return f.fs.WriteFile(fullFilePath, content)
|
||||
}
|
||||
|
||||
// AddDirectory adds a fake directory to the file system.
|
||||
func (f FakeLoader) AddDirectory(fullDirPath string) error {
|
||||
return f.fs.Mkdir(fullDirPath)
|
||||
}
|
||||
|
||||
// Root returns root.
|
||||
func (f FakeLoader) Root() string {
|
||||
return f.delegate.Root()
|
||||
}
|
||||
|
||||
// New creates a new loader from a new root.
|
||||
func (f FakeLoader) New(newRoot string) (ifc.Loader, error) {
|
||||
l, err := f.delegate.New(newRoot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return FakeLoader{fs: f.fs, delegate: l}, nil
|
||||
}
|
||||
|
||||
// Load performs load from a given location.
|
||||
func (f FakeLoader) Load(location string) ([]byte, error) {
|
||||
return f.delegate.Load(location)
|
||||
}
|
||||
|
||||
// Cleanup does nothing
|
||||
func (f FakeLoader) Cleanup() error {
|
||||
return nil
|
||||
}
|
||||
129
internal/plugintest/plugintestenv.go
Normal file
129
internal/plugintest/plugintestenv.go
Normal file
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
Copyright 2019 The Kubernetes Authors.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package plugintest_test
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/k8sdeps/kv/plugin"
|
||||
"sigs.k8s.io/kustomize/pkg/pgmconfig"
|
||||
"sigs.k8s.io/kustomize/pkg/plugins"
|
||||
)
|
||||
|
||||
// PluginTestEnv manages the plugin test environment.
|
||||
// It sets/resets XDG_CONFIG_HOME, makes/removes a temp objRoot.
|
||||
type PluginTestEnv struct {
|
||||
t *testing.T
|
||||
compiler *plugins.Compiler
|
||||
workDir string
|
||||
oldXdg string
|
||||
wasSet bool
|
||||
}
|
||||
|
||||
func NewPluginTestEnv(t *testing.T) *PluginTestEnv {
|
||||
return &PluginTestEnv{t: t}
|
||||
}
|
||||
|
||||
func (x *PluginTestEnv) Set() *PluginTestEnv {
|
||||
x.createWorkDir()
|
||||
x.compiler = x.makeCompiler()
|
||||
x.setEnv()
|
||||
return x
|
||||
}
|
||||
|
||||
func (x *PluginTestEnv) Reset() {
|
||||
x.resetEnv()
|
||||
x.removeWorkDir()
|
||||
}
|
||||
|
||||
func (x *PluginTestEnv) BuildGoPlugin(g, v, k string) {
|
||||
err := x.compiler.Compile(g, v, k)
|
||||
if err != nil {
|
||||
x.t.Errorf("compile failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *PluginTestEnv) BuildExecPlugin(name ...string) {
|
||||
obj := filepath.Join(
|
||||
append([]string{x.workDir, pgmconfig.ProgramName, plugin.PluginRoot}, name...)...)
|
||||
|
||||
srcRoot, err := plugins.DefaultSrcRoot()
|
||||
if err != nil {
|
||||
x.t.Error(err)
|
||||
}
|
||||
|
||||
src := filepath.Join(
|
||||
append([]string{srcRoot}, name...)...)
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(obj), 0755); err != nil {
|
||||
x.t.Errorf("error making directory: %s", filepath.Dir(obj))
|
||||
}
|
||||
cmd := exec.Command("cp", src, obj)
|
||||
cmd.Env = os.Environ()
|
||||
if err := cmd.Run(); err != nil {
|
||||
x.t.Errorf("error copying %s: %v", src, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *PluginTestEnv) makeCompiler() *plugins.Compiler {
|
||||
// The plugin loader wants to find object code under
|
||||
// $XDG_CONFIG_HOME/kustomize/plugins
|
||||
// and the compiler writes object code to
|
||||
// $objRoot
|
||||
// so set things up accordingly.
|
||||
objRoot := filepath.Join(
|
||||
x.workDir, pgmconfig.ProgramName, plugin.PluginRoot)
|
||||
err := os.MkdirAll(objRoot, os.ModePerm)
|
||||
if err != nil {
|
||||
x.t.Error(err)
|
||||
}
|
||||
srcRoot, err := plugins.DefaultSrcRoot()
|
||||
if err != nil {
|
||||
x.t.Error(err)
|
||||
}
|
||||
return plugins.NewCompiler(srcRoot, objRoot)
|
||||
}
|
||||
|
||||
func (x *PluginTestEnv) createWorkDir() {
|
||||
var err error
|
||||
x.workDir, err = ioutil.TempDir("", "kustomize-plugin-tests")
|
||||
if err != nil {
|
||||
x.t.Errorf("failed to make work dir: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *PluginTestEnv) removeWorkDir() {
|
||||
err := os.RemoveAll(x.workDir)
|
||||
if err != nil {
|
||||
x.t.Errorf(
|
||||
"removing work dir: %s %v", x.workDir, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *PluginTestEnv) setEnv() {
|
||||
x.oldXdg, x.wasSet = os.LookupEnv(pgmconfig.XDG_CONFIG_HOME)
|
||||
os.Setenv(pgmconfig.XDG_CONFIG_HOME, x.workDir)
|
||||
}
|
||||
|
||||
func (x *PluginTestEnv) resetEnv() {
|
||||
if x.wasSet {
|
||||
os.Setenv(pgmconfig.XDG_CONFIG_HOME, x.oldXdg)
|
||||
} else {
|
||||
os.Unsetenv(pgmconfig.XDG_CONFIG_HOME)
|
||||
}
|
||||
}
|
||||
148
k8sdeps/configmapandsecret/basefactory.go
Normal file
148
k8sdeps/configmapandsecret/basefactory.go
Normal file
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
Copyright 2019 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package configmapandsecret
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
"sigs.k8s.io/kustomize/k8sdeps/kv"
|
||||
"sigs.k8s.io/kustomize/k8sdeps/kv/plugin"
|
||||
"sigs.k8s.io/kustomize/pkg/ifc"
|
||||
"sigs.k8s.io/kustomize/pkg/types"
|
||||
)
|
||||
|
||||
// baseFactory holds code shared by Factory and SecretFactory.
|
||||
type baseFactory struct {
|
||||
ldr ifc.Loader
|
||||
options *types.GeneratorOptions
|
||||
reg plugin.Registry
|
||||
}
|
||||
|
||||
func (bf baseFactory) loadKvPairs(
|
||||
args types.GeneratorArgs) (all []kv.Pair, err error) {
|
||||
pairs, err := bf.keyValuesFromPlugins(args.KVSources)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf(
|
||||
"plugins: %s",
|
||||
args.EnvSource))
|
||||
}
|
||||
all = append(all, pairs...)
|
||||
|
||||
pairs, err = bf.keyValuesFromEnvFile(args.EnvSource)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf(
|
||||
"env source file: %s",
|
||||
args.EnvSource))
|
||||
}
|
||||
all = append(all, pairs...)
|
||||
|
||||
pairs, err = keyValuesFromLiteralSources(args.LiteralSources)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf(
|
||||
"literal sources %v", args.LiteralSources))
|
||||
}
|
||||
all = append(all, pairs...)
|
||||
|
||||
pairs, err = bf.keyValuesFromFileSources(args.FileSources)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf(
|
||||
"file sources: %v", args.FileSources))
|
||||
}
|
||||
return append(all, pairs...), nil
|
||||
}
|
||||
|
||||
const keyExistsErrorMsg = "cannot add key %s, another key by that name already exists: %v"
|
||||
|
||||
func errIfInvalidKey(keyName string) error {
|
||||
if errs := validation.IsConfigMapKey(keyName); len(errs) != 0 {
|
||||
return fmt.Errorf("%q is not a valid key name: %s",
|
||||
keyName, strings.Join(errs, ";"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func keyValuesFromLiteralSources(sources []string) ([]kv.Pair, error) {
|
||||
var kvs []kv.Pair
|
||||
for _, s := range sources {
|
||||
k, v, err := kv.ParseLiteralSource(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
kvs = append(kvs, kv.Pair{Key: k, Value: v})
|
||||
}
|
||||
return kvs, nil
|
||||
}
|
||||
|
||||
func (bf baseFactory) keyValuesFromPlugins(sources []types.KVSource) ([]kv.Pair, error) {
|
||||
var result []kv.Pair
|
||||
for _, s := range sources {
|
||||
plug, err := bf.reg.Load(s.PluginType, s.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
kvs, err := plug.Get(bf.reg.Root(), s.Args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, k := range sortedKeys(kvs) {
|
||||
result = append(result, kv.Pair{Key: k, Value: kvs[k]})
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func sortedKeys(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
|
||||
}
|
||||
|
||||
func (bf baseFactory) keyValuesFromFileSources(sources []string) ([]kv.Pair, error) {
|
||||
var kvs []kv.Pair
|
||||
for _, s := range sources {
|
||||
k, fPath, err := kv.ParseFileSource(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
content, err := bf.ldr.Load(fPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
kvs = append(kvs, kv.Pair{Key: k, Value: string(content)})
|
||||
}
|
||||
return kvs, nil
|
||||
}
|
||||
|
||||
func (bf baseFactory) keyValuesFromEnvFile(path string) ([]kv.Pair, error) {
|
||||
if path == "" {
|
||||
return nil, nil
|
||||
}
|
||||
content, err := bf.ldr.Load(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return kv.KeyValuesFromLines(content)
|
||||
}
|
||||
139
k8sdeps/configmapandsecret/basefactory_test.go
Normal file
139
k8sdeps/configmapandsecret/basefactory_test.go
Normal file
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
Copyright 2019 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package configmapandsecret
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/k8sdeps/kv"
|
||||
"sigs.k8s.io/kustomize/k8sdeps/kv/plugin"
|
||||
"sigs.k8s.io/kustomize/pkg/fs"
|
||||
"sigs.k8s.io/kustomize/pkg/loader"
|
||||
"sigs.k8s.io/kustomize/pkg/types"
|
||||
)
|
||||
|
||||
func TestKeyValuesFromFileSources(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
sources []string
|
||||
expected []kv.Pair
|
||||
}{
|
||||
{
|
||||
description: "create kvs from file sources",
|
||||
sources: []string{"files/app-init.ini"},
|
||||
expected: []kv.Pair{
|
||||
{
|
||||
Key: "app-init.ini",
|
||||
Value: "FOO=bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
fSys := fs.MakeFakeFS()
|
||||
fSys.WriteFile("/files/app-init.ini", []byte("FOO=bar"))
|
||||
ldr := loader.NewFileLoaderAtRoot(fSys)
|
||||
reg := plugin.NewRegistry(ldr)
|
||||
bf := baseFactory{loader.NewFileLoaderAtRoot(fSys), nil, reg}
|
||||
for _, tc := range tests {
|
||||
kvs, err := bf.keyValuesFromFileSources(tc.sources)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(kvs, tc.expected) {
|
||||
t.Fatalf("in testcase: %q updated:\n%#v\ndoesn't match expected:\n%#v\n", tc.description, kvs, tc.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyValuesFromPlugins(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
sources []types.KVSource
|
||||
expected []kv.Pair
|
||||
}{
|
||||
{
|
||||
description: "Create kv.Pairs from builtin literals plugin",
|
||||
sources: []types.KVSource{
|
||||
{
|
||||
PluginType: "builtin",
|
||||
Name: "literals",
|
||||
Args: []string{"FOO=bar", "BAR=baz"},
|
||||
},
|
||||
},
|
||||
expected: []kv.Pair{
|
||||
{
|
||||
Key: "BAR",
|
||||
Value: "baz",
|
||||
},
|
||||
{
|
||||
Key: "FOO",
|
||||
Value: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Create kv.Pairs from builtin files plugin",
|
||||
sources: []types.KVSource{
|
||||
{
|
||||
PluginType: "builtin",
|
||||
Name: "files",
|
||||
Args: []string{"files/app-init.ini"},
|
||||
},
|
||||
},
|
||||
expected: []kv.Pair{
|
||||
{
|
||||
Key: "app-init.ini",
|
||||
Value: "FOO=bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Create kv.Pairs from builtin envfiles plugin",
|
||||
sources: []types.KVSource{
|
||||
{
|
||||
PluginType: "builtin",
|
||||
Name: "envfiles",
|
||||
Args: []string{"files/app-init.ini"},
|
||||
},
|
||||
},
|
||||
expected: []kv.Pair{
|
||||
{
|
||||
Key: "FOO",
|
||||
Value: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
fSys := fs.MakeFakeFS()
|
||||
fSys.WriteFile("/files/app-init.ini", []byte("FOO=bar"))
|
||||
ldr := loader.NewFileLoaderAtRoot(fSys)
|
||||
reg := plugin.NewRegistry(ldr)
|
||||
bf := baseFactory{ldr, nil, reg}
|
||||
|
||||
for _, tc := range tests {
|
||||
kvs, err := bf.keyValuesFromPlugins(tc.sources)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(kvs, tc.expected) {
|
||||
t.Fatalf("in testcase: %q updated:\n%#v\ndoesn't match expected:\n%#v\n", tc.description, kvs, tc.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
97
k8sdeps/configmapandsecret/configmapfactory.go
Normal file
97
k8sdeps/configmapandsecret/configmapfactory.go
Normal file
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package configmapandsecret generates configmaps and secrets per generator rules.
|
||||
package configmapandsecret
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unicode/utf8"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"sigs.k8s.io/kustomize/k8sdeps/kv/plugin"
|
||||
"sigs.k8s.io/kustomize/pkg/ifc"
|
||||
"sigs.k8s.io/kustomize/pkg/types"
|
||||
)
|
||||
|
||||
// Factory makes ConfigMaps and Secrets.
|
||||
type Factory struct {
|
||||
baseFactory
|
||||
}
|
||||
|
||||
// NewFactory returns a new Factory.
|
||||
func NewFactory(
|
||||
l ifc.Loader, o *types.GeneratorOptions, reg plugin.Registry) *Factory {
|
||||
return &Factory{baseFactory{ldr: l, options: o, reg: reg}}
|
||||
}
|
||||
|
||||
func makeFreshConfigMap(
|
||||
args *types.ConfigMapArgs) *v1.ConfigMap {
|
||||
cm := &v1.ConfigMap{}
|
||||
cm.APIVersion = "v1"
|
||||
cm.Kind = "ConfigMap"
|
||||
cm.Name = args.Name
|
||||
cm.Namespace = args.Namespace
|
||||
cm.Data = map[string]string{}
|
||||
return cm
|
||||
}
|
||||
|
||||
// MakeConfigMap returns a new ConfigMap, or nil and an error.
|
||||
func (f *Factory) MakeConfigMap(
|
||||
args *types.ConfigMapArgs) (*v1.ConfigMap, error) {
|
||||
all, err := f.loadKvPairs(args.GeneratorArgs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cm := makeFreshConfigMap(args)
|
||||
for _, p := range all {
|
||||
err = addKvToConfigMap(cm, p.Key, p.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if f.options != nil {
|
||||
cm.SetLabels(f.options.Labels)
|
||||
cm.SetAnnotations(f.options.Annotations)
|
||||
}
|
||||
return cm, nil
|
||||
}
|
||||
|
||||
// addKvToConfigMap adds the given key and data to the given config map.
|
||||
// Error if key invalid, or already exists.
|
||||
func addKvToConfigMap(configMap *v1.ConfigMap, keyName, data string) error {
|
||||
if err := errIfInvalidKey(keyName); err != nil {
|
||||
return err
|
||||
}
|
||||
// If the configmap data contains byte sequences that are all in the UTF-8
|
||||
// range, we will write it to .Data
|
||||
if utf8.Valid([]byte(data)) {
|
||||
if _, entryExists := configMap.Data[keyName]; entryExists {
|
||||
return fmt.Errorf(keyExistsErrorMsg, keyName, configMap.Data)
|
||||
}
|
||||
configMap.Data[keyName] = data
|
||||
return nil
|
||||
}
|
||||
// otherwise, it's BinaryData
|
||||
if configMap.BinaryData == nil {
|
||||
configMap.BinaryData = map[string][]byte{}
|
||||
}
|
||||
if _, entryExists := configMap.BinaryData[keyName]; entryExists {
|
||||
return fmt.Errorf(keyExistsErrorMsg, keyName, configMap.BinaryData)
|
||||
}
|
||||
configMap.BinaryData[keyName] = []byte(data)
|
||||
return nil
|
||||
}
|
||||
157
k8sdeps/configmapandsecret/configmapfactory_test.go
Normal file
157
k8sdeps/configmapandsecret/configmapfactory_test.go
Normal file
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package configmapandsecret
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/kustomize/k8sdeps/kv/plugin"
|
||||
"sigs.k8s.io/kustomize/pkg/fs"
|
||||
"sigs.k8s.io/kustomize/pkg/loader"
|
||||
"sigs.k8s.io/kustomize/pkg/types"
|
||||
)
|
||||
|
||||
func makeEnvConfigMap(name string) *corev1.ConfigMap {
|
||||
return &corev1.ConfigMap{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "ConfigMap",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
},
|
||||
Data: map[string]string{
|
||||
"DB_USERNAME": "admin",
|
||||
"DB_PASSWORD": "somepw",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func makeFileConfigMap(name string) *corev1.ConfigMap {
|
||||
return &corev1.ConfigMap{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "ConfigMap",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
},
|
||||
Data: map[string]string{
|
||||
"app-init.ini": `FOO=bar
|
||||
BAR=baz
|
||||
`,
|
||||
},
|
||||
BinaryData: map[string][]byte{
|
||||
"app.bin": {0xff, 0xfd},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func makeLiteralConfigMap(name string) *corev1.ConfigMap {
|
||||
cm := &corev1.ConfigMap{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "ConfigMap",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
},
|
||||
Data: map[string]string{
|
||||
"a": "x",
|
||||
"b": "y",
|
||||
"c": "Hello World",
|
||||
"d": "true",
|
||||
},
|
||||
}
|
||||
cm.SetLabels(map[string]string{"foo": "bar"})
|
||||
return cm
|
||||
}
|
||||
|
||||
func TestConstructConfigMap(t *testing.T) {
|
||||
type testCase struct {
|
||||
description string
|
||||
input types.ConfigMapArgs
|
||||
options *types.GeneratorOptions
|
||||
expected *corev1.ConfigMap
|
||||
}
|
||||
|
||||
testCases := []testCase{
|
||||
{
|
||||
description: "construct config map from env",
|
||||
input: types.ConfigMapArgs{
|
||||
GeneratorArgs: types.GeneratorArgs{
|
||||
Name: "envConfigMap",
|
||||
DataSources: types.DataSources{
|
||||
EnvSource: "configmap/app.env",
|
||||
},
|
||||
},
|
||||
},
|
||||
options: nil,
|
||||
expected: makeEnvConfigMap("envConfigMap"),
|
||||
},
|
||||
{
|
||||
description: "construct config map from file",
|
||||
input: types.ConfigMapArgs{
|
||||
GeneratorArgs: types.GeneratorArgs{
|
||||
Name: "fileConfigMap",
|
||||
DataSources: types.DataSources{
|
||||
FileSources: []string{"configmap/app-init.ini", "configmap/app.bin"},
|
||||
},
|
||||
},
|
||||
},
|
||||
options: nil,
|
||||
expected: makeFileConfigMap("fileConfigMap"),
|
||||
},
|
||||
{
|
||||
description: "construct config map from literal",
|
||||
input: types.ConfigMapArgs{
|
||||
GeneratorArgs: types.GeneratorArgs{
|
||||
Name: "literalConfigMap",
|
||||
DataSources: types.DataSources{
|
||||
LiteralSources: []string{"a=x", "b=y", "c=\"Hello World\"", "d='true'"},
|
||||
},
|
||||
},
|
||||
},
|
||||
options: &types.GeneratorOptions{
|
||||
Labels: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
expected: makeLiteralConfigMap("literalConfigMap"),
|
||||
},
|
||||
}
|
||||
|
||||
fSys := fs.MakeFakeFS()
|
||||
fSys.WriteFile("/configmap/app.env", []byte("DB_USERNAME=admin\nDB_PASSWORD=somepw\n"))
|
||||
fSys.WriteFile("/configmap/app-init.ini", []byte("FOO=bar\nBAR=baz\n"))
|
||||
fSys.WriteFile("/configmap/app.bin", []byte{0xff, 0xfd})
|
||||
ldr := loader.NewFileLoaderAtRoot(fSys)
|
||||
reg := plugin.NewRegistry(ldr)
|
||||
for _, tc := range testCases {
|
||||
f := NewFactory(ldr, tc.options, reg)
|
||||
cm, err := f.MakeConfigMap(&tc.input)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(*cm, *tc.expected) {
|
||||
t.Fatalf("in testcase: %q updated:\n%#v\ndoesn't match expected:\n%#v\n", tc.description, *cm, tc.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
71
k8sdeps/configmapandsecret/secretfactory.go
Normal file
71
k8sdeps/configmapandsecret/secretfactory.go
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package configmapandsecret
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"sigs.k8s.io/kustomize/pkg/types"
|
||||
)
|
||||
|
||||
func makeFreshSecret(
|
||||
args *types.SecretArgs) *corev1.Secret {
|
||||
s := &corev1.Secret{}
|
||||
s.APIVersion = "v1"
|
||||
s.Kind = "Secret"
|
||||
s.Name = args.Name
|
||||
s.Namespace = args.Namespace
|
||||
s.Type = corev1.SecretType(args.Type)
|
||||
if s.Type == "" {
|
||||
s.Type = corev1.SecretTypeOpaque
|
||||
}
|
||||
s.Data = map[string][]byte{}
|
||||
return s
|
||||
}
|
||||
|
||||
// MakeSecret returns a new secret.
|
||||
func (f *Factory) MakeSecret(
|
||||
args *types.SecretArgs) (*corev1.Secret, error) {
|
||||
all, err := f.loadKvPairs(args.GeneratorArgs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s := makeFreshSecret(args)
|
||||
for _, p := range all {
|
||||
err = addKvToSecret(s, p.Key, p.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if f.options != nil {
|
||||
s.SetLabels(f.options.Labels)
|
||||
s.SetAnnotations(f.options.Annotations)
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func addKvToSecret(secret *corev1.Secret, keyName, data string) error {
|
||||
if err := errIfInvalidKey(keyName); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, entryExists := secret.Data[keyName]; entryExists {
|
||||
return fmt.Errorf(keyExistsErrorMsg, keyName, secret.Data)
|
||||
}
|
||||
secret.Data[keyName] = []byte(data)
|
||||
return nil
|
||||
}
|
||||
154
k8sdeps/configmapandsecret/secretfactory_test.go
Normal file
154
k8sdeps/configmapandsecret/secretfactory_test.go
Normal file
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package configmapandsecret
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/kustomize/k8sdeps/kv/plugin"
|
||||
"sigs.k8s.io/kustomize/pkg/fs"
|
||||
"sigs.k8s.io/kustomize/pkg/loader"
|
||||
"sigs.k8s.io/kustomize/pkg/types"
|
||||
)
|
||||
|
||||
func makeEnvSecret(name string) *corev1.Secret {
|
||||
return &corev1.Secret{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "Secret",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"DB_PASSWORD": []byte("somepw"),
|
||||
"DB_USERNAME": []byte("admin"),
|
||||
},
|
||||
Type: "Opaque",
|
||||
}
|
||||
}
|
||||
|
||||
func makeFileSecret(name string) *corev1.Secret {
|
||||
return &corev1.Secret{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "Secret",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"app-init.ini": []byte(`FOO=bar
|
||||
BAR=baz
|
||||
`),
|
||||
},
|
||||
Type: "Opaque",
|
||||
}
|
||||
}
|
||||
|
||||
func makeLiteralSecret(name string) *corev1.Secret {
|
||||
s := &corev1.Secret{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "Secret",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"a": []byte("x"),
|
||||
"b": []byte("y"),
|
||||
},
|
||||
Type: "Opaque",
|
||||
}
|
||||
s.SetLabels(map[string]string{"foo": "bar"})
|
||||
return s
|
||||
}
|
||||
|
||||
func TestConstructSecret(t *testing.T) {
|
||||
type testCase struct {
|
||||
description string
|
||||
input types.SecretArgs
|
||||
options *types.GeneratorOptions
|
||||
expected *corev1.Secret
|
||||
}
|
||||
|
||||
testCases := []testCase{
|
||||
{
|
||||
description: "construct secret from env",
|
||||
input: types.SecretArgs{
|
||||
GeneratorArgs: types.GeneratorArgs{
|
||||
Name: "envSecret",
|
||||
DataSources: types.DataSources{
|
||||
EnvSource: "secret/app.env",
|
||||
},
|
||||
},
|
||||
},
|
||||
options: nil,
|
||||
expected: makeEnvSecret("envSecret"),
|
||||
},
|
||||
{
|
||||
description: "construct secret from file",
|
||||
input: types.SecretArgs{
|
||||
GeneratorArgs: types.GeneratorArgs{
|
||||
Name: "fileSecret",
|
||||
DataSources: types.DataSources{
|
||||
FileSources: []string{"secret/app-init.ini"},
|
||||
},
|
||||
},
|
||||
},
|
||||
options: nil,
|
||||
expected: makeFileSecret("fileSecret"),
|
||||
},
|
||||
{
|
||||
description: "construct secret from literal",
|
||||
input: types.SecretArgs{
|
||||
GeneratorArgs: types.GeneratorArgs{
|
||||
Name: "literalSecret",
|
||||
DataSources: types.DataSources{
|
||||
LiteralSources: []string{"a=x", "b=y"},
|
||||
},
|
||||
},
|
||||
},
|
||||
options: &types.GeneratorOptions{
|
||||
Labels: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
expected: makeLiteralSecret("literalSecret"),
|
||||
},
|
||||
}
|
||||
|
||||
fSys := fs.MakeFakeFS()
|
||||
fSys.WriteFile("/secret/app.env", []byte("DB_USERNAME=admin\nDB_PASSWORD=somepw\n"))
|
||||
fSys.WriteFile("/secret/app-init.ini", []byte("FOO=bar\nBAR=baz\n"))
|
||||
ldr := loader.NewFileLoaderAtRoot(fSys)
|
||||
reg := plugin.NewRegistry(ldr)
|
||||
for _, tc := range testCases {
|
||||
f := NewFactory(ldr, tc.options, reg)
|
||||
cm, err := f.MakeSecret(&tc.input)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(*cm, *tc.expected) {
|
||||
t.Fatalf("in testcase: %q updated:\n%#v\ndoesn't match expected:\n%#v\n", tc.description, *cm, tc.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
76
k8sdeps/doc.go
Normal file
76
k8sdeps/doc.go
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// It's possible that kustomize's features will be vendored into
|
||||
// the kubernetes/kubernetes repo and made available to kubectl
|
||||
// commands, while at the same time the kustomize program will
|
||||
// continue to exist as an independent CLI. Vendoring snapshots
|
||||
// would be taken just before a kubectl release.
|
||||
//
|
||||
// This creates a problem in that freestanding-kustomize depends on
|
||||
// (for example):
|
||||
//
|
||||
// https://github.com/kubernetes/apimachinery/
|
||||
// tree/master/pkg/util/yaml
|
||||
//
|
||||
// It vendors that package into
|
||||
// sigs.k8s.io/kustomize/vendor/k8s.io/apimachinery/
|
||||
//
|
||||
// Whereas kubectl-kustomize would have to depend on the "staging"
|
||||
// version of this code, located at
|
||||
//
|
||||
// https://github.com/kubernetes/kubernetes/
|
||||
// blob/master/staging/src/k8s.io/apimachinery/pkg/util/yaml
|
||||
//
|
||||
// which is "vendored" via symlinks:
|
||||
// k8s.io/kubernetes/vendor/k8s.io/apimachinery
|
||||
// is a symlink to
|
||||
// ../../staging/src/k8s.io/apimachinery
|
||||
//
|
||||
// The staging version is the canonical, under-development
|
||||
// version of the code that kubectl depends on, whereas the packages
|
||||
// at kubernetes/apimachinery are periodic snapshots of staging made
|
||||
// for outside tools to depend on.
|
||||
//
|
||||
// apimachinery isn't the only package that poses this problem, just
|
||||
// using it as a specific example.
|
||||
//
|
||||
// The kubectl binary cannot vendor in kustomize code that in
|
||||
// turn vendors in the non-staging packages.
|
||||
//
|
||||
// One way to fix some of this would be to copy code - a hard fork.
|
||||
// This has all the problems associated with a hard forking.
|
||||
//
|
||||
// Another way would be to break the kustomize repo into three:
|
||||
//
|
||||
// (1) kustomize - repo with the main() function,
|
||||
// vendoring (2) and (3).
|
||||
//
|
||||
// (2) kustomize-libs - packages used by (1) with no
|
||||
// apimachinery dependence.
|
||||
//
|
||||
// (3) kustomize-k8sdeps - A thin code layer that depends
|
||||
// on (vendors) apimachinery to provide thin implementations
|
||||
// to interfaces used in (2).
|
||||
//
|
||||
// The kubectl repo would then vendor from (2) only, and have
|
||||
// a local implementation of (3). With that in mind, it's clear
|
||||
// that (3) doesn't have to be a repo; the kustomize version of
|
||||
// the thin layer can live directly in (1).
|
||||
//
|
||||
// This package is the code in (3), meant for kustomize.
|
||||
|
||||
package k8sdeps
|
||||
131
k8sdeps/kunstruct/factory.go
Normal file
131
k8sdeps/kunstruct/factory.go
Normal file
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package kunstruct
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
"sigs.k8s.io/kustomize/k8sdeps/configmapandsecret"
|
||||
"sigs.k8s.io/kustomize/k8sdeps/kv/plugin"
|
||||
"sigs.k8s.io/kustomize/pkg/ifc"
|
||||
"sigs.k8s.io/kustomize/pkg/types"
|
||||
)
|
||||
|
||||
// KunstructuredFactoryImpl hides construction using apimachinery types.
|
||||
type KunstructuredFactoryImpl struct {
|
||||
generatorMetaArgs *types.GeneratorMetaArgs
|
||||
}
|
||||
|
||||
var _ ifc.KunstructuredFactory = &KunstructuredFactoryImpl{}
|
||||
|
||||
// NewKunstructuredFactoryImpl returns a factory.
|
||||
func NewKunstructuredFactoryImpl() ifc.KunstructuredFactory {
|
||||
return NewKunstructuredFactoryWithGeneratorArgs(
|
||||
&types.GeneratorMetaArgs{})
|
||||
}
|
||||
|
||||
// NewKunstructuredFactoryWithGeneratorArgs returns a factory.
|
||||
func NewKunstructuredFactoryWithGeneratorArgs(
|
||||
gma *types.GeneratorMetaArgs) ifc.KunstructuredFactory {
|
||||
return &KunstructuredFactoryImpl{gma}
|
||||
}
|
||||
|
||||
// SliceFromBytes returns a slice of Kunstructured.
|
||||
func (kf *KunstructuredFactoryImpl) SliceFromBytes(
|
||||
in []byte) ([]ifc.Kunstructured, error) {
|
||||
decoder := yaml.NewYAMLOrJSONDecoder(bytes.NewReader(in), 1024)
|
||||
var result []ifc.Kunstructured
|
||||
var err error
|
||||
for err == nil || isEmptyYamlError(err) {
|
||||
var out unstructured.Unstructured
|
||||
err = decoder.Decode(&out)
|
||||
if err == nil {
|
||||
if len(out.Object) == 0 {
|
||||
continue
|
||||
}
|
||||
err = kf.validate(out)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result = append(result, &UnstructAdapter{Unstructured: out})
|
||||
}
|
||||
}
|
||||
if err != io.EOF {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func isEmptyYamlError(err error) bool {
|
||||
return strings.Contains(err.Error(), "is missing in 'null'")
|
||||
}
|
||||
|
||||
// FromMap returns an instance of Kunstructured.
|
||||
func (kf *KunstructuredFactoryImpl) FromMap(
|
||||
m map[string]interface{}) ifc.Kunstructured {
|
||||
return &UnstructAdapter{Unstructured: unstructured.Unstructured{Object: m}}
|
||||
}
|
||||
|
||||
// MakeConfigMap returns an instance of Kunstructured for ConfigMap
|
||||
func (kf *KunstructuredFactoryImpl) MakeConfigMap(
|
||||
ldr ifc.Loader,
|
||||
options *types.GeneratorOptions,
|
||||
args *types.ConfigMapArgs) (ifc.Kunstructured, error) {
|
||||
o, err := configmapandsecret.NewFactory(
|
||||
ldr, options,
|
||||
plugin.NewConfiguredRegistry(
|
||||
ldr, kf.generatorMetaArgs.PluginConfig)).MakeConfigMap(args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewKunstructuredFromObject(o)
|
||||
}
|
||||
|
||||
// MakeSecret returns an instance of Kunstructured for Secret
|
||||
func (kf *KunstructuredFactoryImpl) MakeSecret(
|
||||
ldr ifc.Loader,
|
||||
options *types.GeneratorOptions,
|
||||
args *types.SecretArgs) (ifc.Kunstructured, error) {
|
||||
o, err := configmapandsecret.NewFactory(
|
||||
ldr, options,
|
||||
plugin.NewConfiguredRegistry(
|
||||
ldr, kf.generatorMetaArgs.PluginConfig)).MakeSecret(args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewKunstructuredFromObject(o)
|
||||
}
|
||||
|
||||
// validate validates that u has kind and name
|
||||
// except for kind `List`, which doesn't require a name
|
||||
func (kf *KunstructuredFactoryImpl) validate(u unstructured.Unstructured) error {
|
||||
kind := u.GetKind()
|
||||
if kind == "" {
|
||||
return fmt.Errorf("missing kind in object %v", u)
|
||||
} else if strings.HasSuffix(kind, "List") {
|
||||
return nil
|
||||
}
|
||||
if u.GetName() == "" {
|
||||
return fmt.Errorf("missing metadata.name in object %v", u)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
202
k8sdeps/kunstruct/factory_test.go
Normal file
202
k8sdeps/kunstruct/factory_test.go
Normal file
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package kunstruct
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/pkg/ifc"
|
||||
)
|
||||
|
||||
func TestSliceFromBytes(t *testing.T) {
|
||||
factory := NewKunstructuredFactoryImpl()
|
||||
testConfigMap := factory.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "winnie",
|
||||
},
|
||||
})
|
||||
testList := factory.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "List",
|
||||
"items": []interface{}{
|
||||
testConfigMap.Map(),
|
||||
testConfigMap.Map(),
|
||||
},
|
||||
})
|
||||
testConfigMapList := factory.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMapList",
|
||||
"items": []interface{}{
|
||||
testConfigMap.Map(),
|
||||
testConfigMap.Map(),
|
||||
},
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input []byte
|
||||
expectedOut []ifc.Kunstructured
|
||||
expectedErr bool
|
||||
}{
|
||||
{
|
||||
name: "garbage",
|
||||
input: []byte("garbageIn: garbageOut"),
|
||||
expectedOut: []ifc.Kunstructured{},
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "noBytes",
|
||||
input: []byte{},
|
||||
expectedOut: []ifc.Kunstructured{},
|
||||
expectedErr: false,
|
||||
},
|
||||
{
|
||||
name: "goodJson",
|
||||
input: []byte(`
|
||||
{"apiVersion":"v1","kind":"ConfigMap","metadata":{"name":"winnie"}}
|
||||
`),
|
||||
expectedOut: []ifc.Kunstructured{testConfigMap},
|
||||
expectedErr: false,
|
||||
},
|
||||
{
|
||||
name: "goodYaml1",
|
||||
input: []byte(`
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: winnie
|
||||
`),
|
||||
expectedOut: []ifc.Kunstructured{testConfigMap},
|
||||
expectedErr: false,
|
||||
},
|
||||
{
|
||||
name: "goodYaml2",
|
||||
input: []byte(`
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: winnie
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: winnie
|
||||
`),
|
||||
expectedOut: []ifc.Kunstructured{testConfigMap, testConfigMap},
|
||||
expectedErr: false,
|
||||
},
|
||||
{
|
||||
name: "garbageInOneOfTwoObjects",
|
||||
input: []byte(`
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: winnie
|
||||
---
|
||||
WOOOOOOOOOOOOOOOOOOOOOOOOT: woot
|
||||
`),
|
||||
expectedOut: []ifc.Kunstructured{},
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "emptyObjects",
|
||||
input: []byte(`
|
||||
---
|
||||
#a comment
|
||||
|
||||
---
|
||||
|
||||
`),
|
||||
expectedOut: []ifc.Kunstructured{},
|
||||
expectedErr: false,
|
||||
},
|
||||
{
|
||||
name: "Missing .metadata.name in object",
|
||||
input: []byte(`
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
annotations:
|
||||
foo: bar
|
||||
`),
|
||||
expectedOut: nil,
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "List",
|
||||
input: []byte(`
|
||||
apiVersion: v1
|
||||
kind: List
|
||||
items:
|
||||
- apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: winnie
|
||||
- apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: winnie
|
||||
`),
|
||||
expectedOut: []ifc.Kunstructured{testList},
|
||||
expectedErr: false,
|
||||
},
|
||||
{
|
||||
name: "ConfigMapList",
|
||||
input: []byte(`
|
||||
apiVersion: v1
|
||||
kind: ConfigMapList
|
||||
items:
|
||||
- apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: winnie
|
||||
- apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: winnie
|
||||
`),
|
||||
expectedOut: []ifc.Kunstructured{testConfigMapList},
|
||||
expectedErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
rs, err := factory.SliceFromBytes(test.input)
|
||||
if test.expectedErr && err == nil {
|
||||
t.Fatalf("%v: should return error", test.name)
|
||||
}
|
||||
if !test.expectedErr && err != nil {
|
||||
t.Fatalf("%v: unexpected error: %s", test.name, err)
|
||||
}
|
||||
if len(rs) != len(test.expectedOut) {
|
||||
t.Fatalf("%s: length mismatch %d != %d",
|
||||
test.name, len(rs), len(test.expectedOut))
|
||||
}
|
||||
for i := range rs {
|
||||
if !reflect.DeepEqual(test.expectedOut[i], rs[i]) {
|
||||
t.Fatalf("%s: Got: %v\nexpected:%v",
|
||||
test.name, test.expectedOut[i], rs[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
71
k8sdeps/kunstruct/helper.go
Normal file
71
k8sdeps/kunstruct/helper.go
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package kunstruct provides unstructured from api machinery and factory for creating unstructured
|
||||
package kunstruct
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func parseFields(path string) ([]string, error) {
|
||||
if !strings.Contains(path, "[") {
|
||||
return strings.Split(path, "."), nil
|
||||
}
|
||||
|
||||
var fields []string
|
||||
start := 0
|
||||
insideParentheses := false
|
||||
for i := range path {
|
||||
switch path[i] {
|
||||
case '.':
|
||||
if !insideParentheses {
|
||||
fields = append(fields, path[start:i])
|
||||
start = i + 1
|
||||
}
|
||||
case '[':
|
||||
if !insideParentheses {
|
||||
if i == start {
|
||||
start = i + 1
|
||||
} else {
|
||||
fields = append(fields, path[start:i])
|
||||
start = i + 1
|
||||
}
|
||||
insideParentheses = true
|
||||
} else {
|
||||
return nil, fmt.Errorf("nested parentheses are not allowed: %s", path)
|
||||
}
|
||||
case ']':
|
||||
if insideParentheses {
|
||||
fields = append(fields, path[start:i])
|
||||
start = i + 1
|
||||
insideParentheses = false
|
||||
} else {
|
||||
return nil, fmt.Errorf("invalid field path %s", path)
|
||||
}
|
||||
}
|
||||
}
|
||||
if start < len(path)-1 {
|
||||
fields = append(fields, path[start:])
|
||||
}
|
||||
for i, f := range fields {
|
||||
if strings.HasPrefix(f, "\"") || strings.HasPrefix(f, "'") {
|
||||
fields[i] = strings.Trim(f, "\"'")
|
||||
}
|
||||
}
|
||||
return fields, nil
|
||||
}
|
||||
106
k8sdeps/kunstruct/kunstruct.go
Normal file
106
k8sdeps/kunstruct/kunstruct.go
Normal file
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package kunstruct provides unstructured from api machinery and factory for creating unstructured
|
||||
package kunstruct
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"sigs.k8s.io/kustomize/pkg/types"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/kustomize/pkg/gvk"
|
||||
"sigs.k8s.io/kustomize/pkg/ifc"
|
||||
)
|
||||
|
||||
var _ ifc.Kunstructured = &UnstructAdapter{}
|
||||
|
||||
// UnstructAdapter wraps unstructured.Unstructured from
|
||||
// https://github.com/kubernetes/apimachinery/blob/master/
|
||||
// pkg/apis/meta/v1/unstructured/unstructured.go
|
||||
// to isolate dependence on apimachinery.
|
||||
type UnstructAdapter struct {
|
||||
unstructured.Unstructured
|
||||
}
|
||||
|
||||
// NewKunstructuredFromObject returns a new instance of Kunstructured.
|
||||
func NewKunstructuredFromObject(obj runtime.Object) (ifc.Kunstructured, error) {
|
||||
// Convert obj to a byte stream, then convert that to JSON (Unstructured).
|
||||
marshaled, err := json.Marshal(obj)
|
||||
if err != nil {
|
||||
return &UnstructAdapter{}, err
|
||||
}
|
||||
var u unstructured.Unstructured
|
||||
err = u.UnmarshalJSON(marshaled)
|
||||
// creationTimestamp always 'null', remove it
|
||||
u.SetCreationTimestamp(metav1.Time{})
|
||||
return &UnstructAdapter{Unstructured: u}, err
|
||||
}
|
||||
|
||||
// GetGvk returns the Gvk name of the object.
|
||||
func (fs *UnstructAdapter) GetGvk() gvk.Gvk {
|
||||
x := fs.GroupVersionKind()
|
||||
return gvk.Gvk{
|
||||
Group: x.Group,
|
||||
Version: x.Version,
|
||||
Kind: x.Kind,
|
||||
}
|
||||
}
|
||||
|
||||
// Copy provides a copy behind an interface.
|
||||
func (fs *UnstructAdapter) Copy() ifc.Kunstructured {
|
||||
return &UnstructAdapter{*fs.DeepCopy()}
|
||||
}
|
||||
|
||||
// Map returns the unstructured content map.
|
||||
func (fs *UnstructAdapter) Map() map[string]interface{} {
|
||||
return fs.Object
|
||||
}
|
||||
|
||||
// SetMap overrides the unstructured content map.
|
||||
func (fs *UnstructAdapter) SetMap(m map[string]interface{}) {
|
||||
fs.Object = m
|
||||
}
|
||||
|
||||
// GetFieldValue returns value at the given fieldpath.
|
||||
func (fs *UnstructAdapter) GetFieldValue(path string) (string, error) {
|
||||
fields, err := parseFields(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
s, found, err := unstructured.NestedString(
|
||||
fs.UnstructuredContent(), fields...)
|
||||
if found || err != nil {
|
||||
return s, err
|
||||
}
|
||||
return "", types.NoFieldError{Field: path}
|
||||
}
|
||||
|
||||
// GetStringSlice returns value at the given fieldpath.
|
||||
func (fs *UnstructAdapter) GetStringSlice(path string) ([]string, error) {
|
||||
fields, err := parseFields(path)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
s, found, err := unstructured.NestedStringSlice(
|
||||
fs.UnstructuredContent(), fields...)
|
||||
if found || err != nil {
|
||||
return s, err
|
||||
}
|
||||
return []string{}, types.NoFieldError{Field: path}
|
||||
}
|
||||
148
k8sdeps/kunstruct/kunstruct_test.go
Normal file
148
k8sdeps/kunstruct/kunstruct_test.go
Normal file
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package kunstruct
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetFieldValue(t *testing.T) {
|
||||
factory := NewKunstructuredFactoryImpl()
|
||||
kunstructured := factory.FromMap(map[string]interface{}{
|
||||
"Kind": "Service",
|
||||
"metadata": map[string]interface{}{
|
||||
"labels": map[string]string{
|
||||
"app": "application-name",
|
||||
},
|
||||
"name": "service-name",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"ports": map[string]interface{}{
|
||||
"port": "80",
|
||||
},
|
||||
},
|
||||
"this": map[string]interface{}{
|
||||
"is": map[string]interface{}{
|
||||
"aNumber": 1000,
|
||||
"aNilValue": nil,
|
||||
"anEmptyMap": map[string]interface{}{},
|
||||
"unrecognizable": testing.InternalExample{
|
||||
Name: "fooBar",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
pathToField string
|
||||
expectedValue string
|
||||
errorExpected bool
|
||||
errorMsg string
|
||||
}{
|
||||
{
|
||||
name: "oneField",
|
||||
pathToField: "Kind",
|
||||
expectedValue: "Service",
|
||||
errorExpected: false,
|
||||
},
|
||||
{
|
||||
name: "twoFields",
|
||||
pathToField: "metadata.name",
|
||||
expectedValue: "service-name",
|
||||
errorExpected: false,
|
||||
},
|
||||
{
|
||||
name: "threeFields",
|
||||
pathToField: "spec.ports.port",
|
||||
expectedValue: "80",
|
||||
errorExpected: false,
|
||||
},
|
||||
{
|
||||
name: "empty",
|
||||
pathToField: "",
|
||||
errorExpected: true,
|
||||
errorMsg: "no field named ''",
|
||||
},
|
||||
{
|
||||
name: "emptyDotEmpty",
|
||||
pathToField: ".",
|
||||
errorExpected: true,
|
||||
errorMsg: "no field named '.'",
|
||||
},
|
||||
{
|
||||
name: "twoFieldsOneMissing",
|
||||
pathToField: "metadata.banana",
|
||||
errorExpected: true,
|
||||
errorMsg: "no field named 'metadata.banana'",
|
||||
},
|
||||
{
|
||||
name: "deeperMissingField",
|
||||
pathToField: "this.is.aDeep.field.that.does.not.exist",
|
||||
errorExpected: true,
|
||||
errorMsg: "no field named 'this.is.aDeep.field.that.does.not.exist'",
|
||||
},
|
||||
{
|
||||
name: "emptyMap",
|
||||
pathToField: "this.is.anEmptyMap",
|
||||
errorExpected: true,
|
||||
errorMsg: ".this.is.anEmptyMap accessor error: map[] is of the type map[string]interface {}, expected string",
|
||||
},
|
||||
{
|
||||
name: "numberAsValue",
|
||||
pathToField: "this.is.aNumber",
|
||||
errorExpected: true,
|
||||
errorMsg: ".this.is.aNumber accessor error: 1000 is of the type int, expected string",
|
||||
},
|
||||
{
|
||||
name: "nilAsValue",
|
||||
pathToField: "this.is.aNilValue",
|
||||
errorExpected: true,
|
||||
errorMsg: ".this.is.aNilValue accessor error: <nil> is of the type <nil>, expected string",
|
||||
},
|
||||
{
|
||||
name: "unrecognizable",
|
||||
pathToField: "this.is.unrecognizable.Name",
|
||||
errorExpected: true,
|
||||
errorMsg: ".this.is.unrecognizable.Name accessor error: {fooBar <nil> false} is of the type testing.InternalExample, expected map[string]interface{}",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
s, err := kunstructured.GetFieldValue(test.pathToField)
|
||||
if test.errorExpected {
|
||||
if err == nil {
|
||||
t.Fatalf("%q; path %q - should return error, but no error returned",
|
||||
test.name, test.pathToField)
|
||||
}
|
||||
if test.errorMsg != err.Error() {
|
||||
t.Fatalf("%q; path %q - expected error: \"%s\", got error: \"%v\"",
|
||||
test.name, test.pathToField, test.errorMsg, err.Error())
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("%q; path %q - unexpected error %v",
|
||||
test.name, test.pathToField, err)
|
||||
}
|
||||
if test.expectedValue != s {
|
||||
t.Fatalf("%q; Got: %s expected: %s",
|
||||
test.name, s, test.expectedValue)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
Copyright 2019 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package configmapandsecret
|
||||
package kv
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
@@ -28,17 +28,16 @@ import (
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
)
|
||||
|
||||
// kvPair represents a key value pair.
|
||||
type kvPair struct {
|
||||
key string
|
||||
value string
|
||||
type Pair struct {
|
||||
Key string
|
||||
Value string
|
||||
}
|
||||
|
||||
var utf8bom = []byte{0xEF, 0xBB, 0xBF}
|
||||
|
||||
// keyValuesFromLines parses given content in to a list of key-value pairs.
|
||||
func keyValuesFromLines(content []byte) ([]kvPair, error) {
|
||||
var kvs []kvPair
|
||||
// KeyValuesFromLines parses given content in to a list of key-value pairs.
|
||||
func KeyValuesFromLines(content []byte) ([]Pair, error) {
|
||||
var kvs []Pair
|
||||
|
||||
scanner := bufio.NewScanner(bytes.NewReader(content))
|
||||
currentLine := 0
|
||||
@@ -46,13 +45,13 @@ func keyValuesFromLines(content []byte) ([]kvPair, error) {
|
||||
// Process the current line, retrieving a key/value pair if
|
||||
// possible.
|
||||
scannedBytes := scanner.Bytes()
|
||||
kv, err := kvFromLine(scannedBytes, currentLine)
|
||||
kv, err := KeyValuesFromLine(scannedBytes, currentLine)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
currentLine++
|
||||
|
||||
if len(kv.key) == 0 {
|
||||
if len(kv.Key) == 0 {
|
||||
// no key means line was empty or a comment
|
||||
continue
|
||||
}
|
||||
@@ -62,10 +61,10 @@ func keyValuesFromLines(content []byte) ([]kvPair, error) {
|
||||
return kvs, nil
|
||||
}
|
||||
|
||||
// kvFromLine returns a kv with blank key if the line is empty or a comment.
|
||||
// KeyValuesFromLine returns a kv with blank key if the line is empty or a comment.
|
||||
// The value will be retrieved from the environment if necessary.
|
||||
func kvFromLine(line []byte, currentLine int) (kvPair, error) {
|
||||
kv := kvPair{}
|
||||
func KeyValuesFromLine(line []byte, currentLine int) (Pair, error) {
|
||||
kv := Pair{}
|
||||
|
||||
if !utf8.Valid(line) {
|
||||
return kv, fmt.Errorf("line %d has invalid utf8 bytes : %v", line, string(line))
|
||||
@@ -91,12 +90,12 @@ func kvFromLine(line []byte, currentLine int) (kvPair, error) {
|
||||
}
|
||||
|
||||
if len(data) == 2 {
|
||||
kv.value = data[1]
|
||||
kv.Value = data[1]
|
||||
} else {
|
||||
// No value (no `=` in the line) is a signal to obtain the value
|
||||
// from the environment.
|
||||
kv.value = os.Getenv(key)
|
||||
kv.Value = os.Getenv(key)
|
||||
}
|
||||
kv.key = key
|
||||
kv.Key = key
|
||||
return kv, nil
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
Copyright 2019 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -13,7 +13,8 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package configmapandsecret
|
||||
|
||||
package kv
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
@@ -24,7 +25,7 @@ func TestKeyValuesFromLines(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
content string
|
||||
expectedPairs []kvPair
|
||||
expectedPairs []Pair
|
||||
expectedErr bool
|
||||
}{
|
||||
{
|
||||
@@ -33,9 +34,9 @@ func TestKeyValuesFromLines(t *testing.T) {
|
||||
k1=v1
|
||||
k2=v2
|
||||
`,
|
||||
expectedPairs: []kvPair{
|
||||
{key: "k1", value: "v1"},
|
||||
{key: "k2", value: "v2"},
|
||||
expectedPairs: []Pair{
|
||||
{Key: "k1", Value: "v1"},
|
||||
{Key: "k2", Value: "v2"},
|
||||
},
|
||||
expectedErr: false,
|
||||
},
|
||||
@@ -45,8 +46,8 @@ func TestKeyValuesFromLines(t *testing.T) {
|
||||
k1=v1
|
||||
#k2=v2
|
||||
`,
|
||||
expectedPairs: []kvPair{
|
||||
{key: "k1", value: "v1"},
|
||||
expectedPairs: []Pair{
|
||||
{Key: "k1", Value: "v1"},
|
||||
},
|
||||
expectedErr: false,
|
||||
},
|
||||
@@ -54,7 +55,7 @@ func TestKeyValuesFromLines(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
pairs, err := keyValuesFromLines([]byte(test.content))
|
||||
pairs, err := KeyValuesFromLines([]byte(test.content))
|
||||
if test.expectedErr && err == nil {
|
||||
t.Fatalf("%s should not return error", test.desc)
|
||||
}
|
||||
51
k8sdeps/kv/plugin/builtin/envfiles.go
Normal file
51
k8sdeps/kv/plugin/builtin/envfiles.go
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
Copyright 2019 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package builtin
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/k8sdeps/kv"
|
||||
"sigs.k8s.io/kustomize/pkg/ifc"
|
||||
)
|
||||
|
||||
// EnvFiles format should be a path to a file to read lines of key=val
|
||||
// pairs to create a configmap.
|
||||
// i.e. a Docker .env file or a .ini file.
|
||||
type EnvFiles struct {
|
||||
Ldr ifc.Loader
|
||||
}
|
||||
|
||||
// Get implements the interface for kv plugins.
|
||||
func (p EnvFiles) Get(root string, args []string) (map[string]string, error) {
|
||||
all := make(map[string]string)
|
||||
for _, path := range args {
|
||||
if path == "" {
|
||||
return nil, nil
|
||||
}
|
||||
content, err := p.Ldr.Load(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
kvs, err := kv.KeyValuesFromLines(content)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, pair := range kvs {
|
||||
all[pair.Key] = pair.Value
|
||||
}
|
||||
}
|
||||
return all, nil
|
||||
}
|
||||
49
k8sdeps/kv/plugin/builtin/files.go
Normal file
49
k8sdeps/kv/plugin/builtin/files.go
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
Copyright 2019 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package builtin
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/k8sdeps/kv"
|
||||
"sigs.k8s.io/kustomize/pkg/ifc"
|
||||
)
|
||||
|
||||
// Files is a list of file sources.
|
||||
// Each file source can be specified using its file path, in which case file
|
||||
// basename will be used as configmap key, or optionally with a key and file
|
||||
// path, in which case the given key will be used.
|
||||
// Specifying a directory will iterate each named file in the directory
|
||||
// whose basename is a valid configmap key.
|
||||
type Files struct {
|
||||
Ldr ifc.Loader
|
||||
}
|
||||
|
||||
// Get implements the interface for kv plugins.
|
||||
func (p Files) Get(root string, args []string) (map[string]string, error) {
|
||||
kvs := make(map[string]string)
|
||||
for _, s := range args {
|
||||
k, fPath, err := kv.ParseFileSource(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
content, err := p.Ldr.Load(fPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
kvs[k] = string(content)
|
||||
}
|
||||
return kvs, nil
|
||||
}
|
||||
39
k8sdeps/kv/plugin/builtin/literals.go
Normal file
39
k8sdeps/kv/plugin/builtin/literals.go
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
Copyright 2019 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package builtin
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/k8sdeps/kv"
|
||||
)
|
||||
|
||||
// Literals takes a list of literals.
|
||||
// Each literal source should be a key and literal value,
|
||||
// e.g. `somekey=somevalue`
|
||||
type Literals struct{}
|
||||
|
||||
// Get implements the interface for kv plugins.
|
||||
func (p Literals) Get(root string, args []string) (map[string]string, error) {
|
||||
kvs := make(map[string]string)
|
||||
for _, s := range args {
|
||||
k, v, err := kv.ParseLiteralSource(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
kvs[k] = v
|
||||
}
|
||||
return kvs, nil
|
||||
}
|
||||
47
k8sdeps/kv/plugin/builtinplugin.go
Normal file
47
k8sdeps/kv/plugin/builtinplugin.go
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
Copyright 2019 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"sigs.k8s.io/kustomize/k8sdeps/kv/plugin/builtin"
|
||||
"sigs.k8s.io/kustomize/pkg/ifc"
|
||||
)
|
||||
|
||||
var _ Factory = &builtinFactory{}
|
||||
|
||||
type builtinFactory struct {
|
||||
plugins map[string]KVSource
|
||||
}
|
||||
|
||||
func newBuiltinFactory(ldr ifc.Loader) *builtinFactory {
|
||||
return &builtinFactory{
|
||||
plugins: map[string]KVSource{
|
||||
"literals": builtin.Literals{},
|
||||
"files": builtin.Files{Ldr: ldr},
|
||||
"envfiles": builtin.EnvFiles{Ldr: ldr},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *builtinFactory) load(name string) (KVSource, error) {
|
||||
if plug, ok := p.plugins[name]; ok {
|
||||
return plug, nil
|
||||
}
|
||||
return nil, fmt.Errorf("plugin %s not found", name)
|
||||
}
|
||||
94
k8sdeps/kv/plugin/goplugin.go
Normal file
94
k8sdeps/kv/plugin/goplugin.go
Normal file
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
Copyright 2019 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"plugin"
|
||||
|
||||
"sigs.k8s.io/kustomize/pkg/types"
|
||||
)
|
||||
|
||||
var _ Factory = &goFactory{}
|
||||
|
||||
const (
|
||||
kvSourcesDir = "kvSources"
|
||||
EnableGoPluginsFlagName = "enable_alpha_goplugins_accept_panic_risk"
|
||||
EnableGoPluginsFlagHelp = `The main program may panic and exit on an attempt
|
||||
to use a goplugin that was compiled under conditions
|
||||
differing from the those in effect when main was
|
||||
compiled. It's safest to use this flag in the
|
||||
context of a container image holding both the main
|
||||
and the goplugins it needs, all built on the same
|
||||
machine, with the same transitive libs and the same
|
||||
compiler version.`
|
||||
errorFmt = `
|
||||
enable go plugins by specifying flag
|
||||
--%s
|
||||
Place .so files in
|
||||
%s
|
||||
%s`
|
||||
)
|
||||
|
||||
func newGoFactory(c *types.PluginConfig) *goFactory {
|
||||
return &goFactory{
|
||||
config: c,
|
||||
plugins: make(map[string]KVSource),
|
||||
}
|
||||
}
|
||||
|
||||
type goFactory struct {
|
||||
config *types.PluginConfig
|
||||
plugins map[string]KVSource
|
||||
}
|
||||
|
||||
func (p *goFactory) load(name string) (KVSource, error) {
|
||||
if plug, ok := p.plugins[name]; ok {
|
||||
return plug, nil
|
||||
}
|
||||
|
||||
dir := filepath.Join(
|
||||
p.config.DirectoryPath,
|
||||
kvSourcesDir)
|
||||
if !p.config.GoEnabled {
|
||||
return nil, fmt.Errorf(
|
||||
errorFmt,
|
||||
EnableGoPluginsFlagName,
|
||||
dir,
|
||||
EnableGoPluginsFlagHelp)
|
||||
}
|
||||
|
||||
goPlugin, err := plugin.Open(
|
||||
filepath.Join(dir, name+".so"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
symbol, err := goPlugin.Lookup("KVSource")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
plug, ok := symbol.(KVSource)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("plugin %s not found", name)
|
||||
}
|
||||
|
||||
p.plugins[name] = plug
|
||||
return plug, nil
|
||||
}
|
||||
28
k8sdeps/kv/plugin/plugin.go
Normal file
28
k8sdeps/kv/plugin/plugin.go
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
Copyright 2019 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package plugin provides a plugin abstraction layer.
|
||||
package plugin
|
||||
|
||||
// KVSource is the interface for kv source plugins.
|
||||
type KVSource interface {
|
||||
Get(root string, args []string) (map[string]string, error)
|
||||
}
|
||||
|
||||
// Factory is the interface for new kv source plugin implementations.
|
||||
type Factory interface {
|
||||
load(string) (KVSource, error)
|
||||
}
|
||||
89
k8sdeps/kv/plugin/registry.go
Normal file
89
k8sdeps/kv/plugin/registry.go
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
Copyright 2019 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"sigs.k8s.io/kustomize/pkg/ifc"
|
||||
"sigs.k8s.io/kustomize/pkg/pgmconfig"
|
||||
"sigs.k8s.io/kustomize/pkg/types"
|
||||
)
|
||||
|
||||
// Registry holds all the plugin factories.
|
||||
type Registry struct {
|
||||
factories map[types.PluginType]Factory
|
||||
ldr ifc.Loader
|
||||
}
|
||||
|
||||
const (
|
||||
PluginSymbol = "KustomizePlugin"
|
||||
PluginRoot = "plugin"
|
||||
pluginTypeGo = types.PluginType("go")
|
||||
pluginTypeBuiltIn = types.PluginType("builtin")
|
||||
)
|
||||
|
||||
func ActivePluginConfig() *types.PluginConfig {
|
||||
pc := DefaultPluginConfig()
|
||||
pc.GoEnabled = true
|
||||
return pc
|
||||
}
|
||||
|
||||
func DefaultPluginConfig() *types.PluginConfig {
|
||||
return &types.PluginConfig{
|
||||
GoEnabled: false,
|
||||
DirectoryPath: filepath.Join(
|
||||
pgmconfig.ConfigRoot(), PluginRoot),
|
||||
}
|
||||
}
|
||||
|
||||
// NewConfiguredRegistry returns a new Registry loaded
|
||||
// with all the factories and a custom PluginConfig.
|
||||
func NewConfiguredRegistry(
|
||||
ldr ifc.Loader, pc *types.PluginConfig) Registry {
|
||||
return Registry{
|
||||
ldr: ldr,
|
||||
factories: map[types.PluginType]Factory{
|
||||
pluginTypeGo: newGoFactory(pc),
|
||||
pluginTypeBuiltIn: newBuiltinFactory(ldr),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewRegistry returns a new Registry with default config.
|
||||
func NewRegistry(ldr ifc.Loader) Registry {
|
||||
return NewConfiguredRegistry(ldr, &types.PluginConfig{})
|
||||
}
|
||||
|
||||
// Load returns a plugin by type and name.
|
||||
func (r *Registry) Load(
|
||||
pt types.PluginType, name string) (KVSource, error) {
|
||||
if pt.IsUndefined() {
|
||||
pt = pluginTypeBuiltIn
|
||||
}
|
||||
factory, exists := r.factories[pt]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("%s is not a valid plugin type", pt)
|
||||
}
|
||||
return factory.load(name)
|
||||
}
|
||||
|
||||
// Root returns the root of the plugins kustomization file.
|
||||
func (r *Registry) Root() string {
|
||||
return r.ldr.Root()
|
||||
}
|
||||
65
k8sdeps/kv/util.go
Normal file
65
k8sdeps/kv/util.go
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
Copyright 2019 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package kv
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ParseFileSource parses the source given.
|
||||
//
|
||||
// Acceptable formats include:
|
||||
// 1. source-path: the basename will become the key name
|
||||
// 2. source-name=source-path: the source-name will become the key name and
|
||||
// source-path is the path to the key file.
|
||||
//
|
||||
// Key names cannot include '='.
|
||||
func ParseFileSource(source string) (keyName, filePath string, err error) {
|
||||
numSeparators := strings.Count(source, "=")
|
||||
switch {
|
||||
case numSeparators == 0:
|
||||
return path.Base(source), source, nil
|
||||
case numSeparators == 1 && strings.HasPrefix(source, "="):
|
||||
return "", "", fmt.Errorf("key name for file path %v missing", strings.TrimPrefix(source, "="))
|
||||
case numSeparators == 1 && strings.HasSuffix(source, "="):
|
||||
return "", "", fmt.Errorf("file path for key name %v missing", strings.TrimSuffix(source, "="))
|
||||
case numSeparators > 1:
|
||||
return "", "", errors.New("key names or file paths cannot contain '='")
|
||||
default:
|
||||
components := strings.Split(source, "=")
|
||||
return components[0], components[1], nil
|
||||
}
|
||||
}
|
||||
|
||||
// ParseLiteralSource parses the source key=val pair into its component pieces.
|
||||
// This functionality is distinguished from strings.SplitN(source, "=", 2) since
|
||||
// it returns an error in the case of empty keys, values, or a missing equals sign.
|
||||
func ParseLiteralSource(source string) (keyName, value string, err error) {
|
||||
// leading equal is invalid
|
||||
if strings.Index(source, "=") == 0 {
|
||||
return "", "", fmt.Errorf("invalid literal source %v, expected key=value", source)
|
||||
}
|
||||
// split after the first equal (so values can have the = character)
|
||||
items := strings.SplitN(source, "=", 2)
|
||||
if len(items) != 2 {
|
||||
return "", "", fmt.Errorf("invalid literal source %v, expected key=value", source)
|
||||
}
|
||||
return items[0], strings.Trim(items[1], "\"'"), nil
|
||||
}
|
||||
49
k8sdeps/transformer/factory.go
Normal file
49
k8sdeps/transformer/factory.go
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package transformer provides transformer factory
|
||||
package transformer
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/k8sdeps/transformer/hash"
|
||||
"sigs.k8s.io/kustomize/k8sdeps/transformer/inventory"
|
||||
"sigs.k8s.io/kustomize/k8sdeps/transformer/patch"
|
||||
"sigs.k8s.io/kustomize/pkg/resource"
|
||||
"sigs.k8s.io/kustomize/pkg/transformers"
|
||||
"sigs.k8s.io/kustomize/pkg/types"
|
||||
)
|
||||
|
||||
// FactoryImpl makes patch transformer and name hash transformer
|
||||
type FactoryImpl struct{}
|
||||
|
||||
// NewFactoryImpl makes a new factoryImpl instance
|
||||
func NewFactoryImpl() *FactoryImpl {
|
||||
return &FactoryImpl{}
|
||||
}
|
||||
|
||||
// MakePatchTransformer makes a new patch transformer
|
||||
func (p *FactoryImpl) MakePatchTransformer(slice []*resource.Resource, rf *resource.Factory) (transformers.Transformer, error) {
|
||||
return patch.NewPatchTransformer(slice, rf)
|
||||
}
|
||||
|
||||
// MakeHashTransformer makes a new name hash transformer
|
||||
func (p *FactoryImpl) MakeHashTransformer() transformers.Transformer {
|
||||
return hash.NewNameHashTransformer()
|
||||
}
|
||||
|
||||
func (p *FactoryImpl) MakeInventoryTransformer(arg *types.Inventory, namespace string, append bool) transformers.Transformer {
|
||||
return inventory.NewInventoryTransformer(arg, namespace, append)
|
||||
}
|
||||
184
k8sdeps/transformer/hash/hash.go
Normal file
184
k8sdeps/transformer/hash/hash.go
Normal file
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package hash
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
// KustHash compute hash for unstructured objects
|
||||
type KustHash struct{}
|
||||
|
||||
// NewKustHash returns a KustHash object
|
||||
func NewKustHash() *KustHash {
|
||||
return &KustHash{}
|
||||
}
|
||||
|
||||
// Hash returns a hash of either a ConfigMap or a Secret
|
||||
func (h *KustHash) Hash(m map[string]interface{}) (string, error) {
|
||||
u := unstructured.Unstructured{
|
||||
Object: m,
|
||||
}
|
||||
kind := u.GetKind()
|
||||
switch kind {
|
||||
case "ConfigMap":
|
||||
cm, err := unstructuredToConfigmap(u)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return ConfigMapHash(cm)
|
||||
case "Secret":
|
||||
sec, err := unstructuredToSecret(u)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return SecretHash(sec)
|
||||
default:
|
||||
return "", fmt.Errorf("type %s is supported for hashing in %v", kind, m)
|
||||
}
|
||||
}
|
||||
|
||||
// ConfigMapHash returns a hash of the ConfigMap.
|
||||
// The Data, Kind, and Name are taken into account.
|
||||
func ConfigMapHash(cm *v1.ConfigMap) (string, error) {
|
||||
encoded, err := encodeConfigMap(cm)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
h, err := encodeHash(hash(encoded))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// SecretHash returns a hash of the Secret.
|
||||
// The Data, Kind, Name, and Type are taken into account.
|
||||
func SecretHash(sec *v1.Secret) (string, error) {
|
||||
encoded, err := encodeSecret(sec)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
h, err := encodeHash(hash(encoded))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// SortArrayAndComputeHash sorts a string array and
|
||||
// returns a hash for it
|
||||
func SortArrayAndComputeHash(s []string) (string, error) {
|
||||
sort.Strings(s)
|
||||
data, err := json.Marshal(s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
h, err := encodeHash(hash(string(data)))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// encodeConfigMap encodes a ConfigMap.
|
||||
// Data, Kind, and Name are taken into account.
|
||||
func encodeConfigMap(cm *v1.ConfigMap) (string, error) {
|
||||
// json.Marshal sorts the keys in a stable order in the encoding
|
||||
m := map[string]interface{}{"kind": "ConfigMap", "name": cm.Name, "data": cm.Data}
|
||||
if len(cm.BinaryData) > 0 {
|
||||
m["binaryData"] = cm.BinaryData
|
||||
}
|
||||
data, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
// encodeSecret encodes a Secret.
|
||||
// Data, Kind, Name, and Type are taken into account.
|
||||
func encodeSecret(sec *v1.Secret) (string, error) {
|
||||
// json.Marshal sorts the keys in a stable order in the encoding
|
||||
data, err := json.Marshal(map[string]interface{}{"kind": "Secret", "type": sec.Type, "name": sec.Name, "data": sec.Data})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
// encodeHash extracts the first 40 bits of the hash from the hex string
|
||||
// (1 hex char represents 4 bits), and then maps vowels and vowel-like hex
|
||||
// characters to consonants to prevent bad words from being formed (the theory
|
||||
// is that no vowels makes it really hard to make bad words). Since the string
|
||||
// is hex, the only vowels it can contain are 'a' and 'e'.
|
||||
// We picked some arbitrary consonants to map to from the same character set as GenerateName.
|
||||
// See: https://github.com/kubernetes/apimachinery/blob/dc1f89aff9a7509782bde3b68824c8043a3e58cc/pkg/util/rand/rand.go#L75
|
||||
// If the hex string contains fewer than ten characters, returns an error.
|
||||
func encodeHash(hex string) (string, error) {
|
||||
if len(hex) < 10 {
|
||||
return "", fmt.Errorf("the hex string must contain at least 10 characters")
|
||||
}
|
||||
enc := []rune(hex[:10])
|
||||
for i := range enc {
|
||||
switch enc[i] {
|
||||
case '0':
|
||||
enc[i] = 'g'
|
||||
case '1':
|
||||
enc[i] = 'h'
|
||||
case '3':
|
||||
enc[i] = 'k'
|
||||
case 'a':
|
||||
enc[i] = 'm'
|
||||
case 'e':
|
||||
enc[i] = 't'
|
||||
}
|
||||
}
|
||||
return string(enc), nil
|
||||
}
|
||||
|
||||
// hash hashes `data` with sha256 and returns the hex string
|
||||
func hash(data string) string {
|
||||
return fmt.Sprintf("%x", sha256.Sum256([]byte(data)))
|
||||
}
|
||||
|
||||
func unstructuredToConfigmap(u unstructured.Unstructured) (*v1.ConfigMap, error) {
|
||||
marshaled, err := json.Marshal(u.Object)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var out v1.ConfigMap
|
||||
err = json.Unmarshal(marshaled, &out)
|
||||
return &out, err
|
||||
}
|
||||
|
||||
func unstructuredToSecret(u unstructured.Unstructured) (*v1.Secret, error) {
|
||||
marshaled, err := json.Marshal(u.Object)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var out v1.Secret
|
||||
err = json.Unmarshal(marshaled, &out)
|
||||
return &out, err
|
||||
}
|
||||
@@ -22,8 +22,14 @@ import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"sigs.k8s.io/kustomize/pkg/gvk"
|
||||
)
|
||||
|
||||
var service = gvk.Gvk{Version: "v1", Kind: "Service"}
|
||||
var secret = gvk.Gvk{Version: "v1", Kind: "Secret"}
|
||||
var cmap = gvk.Gvk{Version: "v1", Kind: "ConfigMap"}
|
||||
var deploy = gvk.Gvk{Group: "apps", Version: "v1", Kind: "Deployment"}
|
||||
|
||||
func TestConfigMapHash(t *testing.T) {
|
||||
cases := []struct {
|
||||
desc string
|
||||
@@ -84,6 +90,28 @@ func TestSecretHash(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestArrayHash(t *testing.T) {
|
||||
array1 := []string{"a", "b", "c"}
|
||||
array2 := []string{"c", "b", "a"}
|
||||
h1, err := SortArrayAndComputeHash(array1)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error %v", err)
|
||||
}
|
||||
if h1 == "" {
|
||||
t.Errorf("failed to hash %v", array1)
|
||||
}
|
||||
h2, err := SortArrayAndComputeHash(array2)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error %v", err)
|
||||
}
|
||||
if h2 == "" {
|
||||
t.Errorf("failed to hash %v", array2)
|
||||
}
|
||||
if h1 != h2 {
|
||||
t.Errorf("hash is not consistent with reordered list: %s %s", h1, h2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeConfigMap(t *testing.T) {
|
||||
cases := []struct {
|
||||
desc string
|
||||
47
k8sdeps/transformer/hash/namehash.go
Normal file
47
k8sdeps/transformer/hash/namehash.go
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package hash
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"sigs.k8s.io/kustomize/pkg/resmap"
|
||||
"sigs.k8s.io/kustomize/pkg/transformers"
|
||||
)
|
||||
|
||||
type nameHashTransformer struct{}
|
||||
|
||||
var _ transformers.Transformer = &nameHashTransformer{}
|
||||
|
||||
// NewNameHashTransformer construct a nameHashTransformer.
|
||||
func NewNameHashTransformer() transformers.Transformer {
|
||||
return &nameHashTransformer{}
|
||||
}
|
||||
|
||||
// Transform appends hash to generated resources.
|
||||
func (o *nameHashTransformer) Transform(m resmap.ResMap) error {
|
||||
for _, res := range m {
|
||||
if res.NeedHashSuffix() {
|
||||
h, err := NewKustHash().Hash(res.Map())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res.SetName(fmt.Sprintf("%s-%s", res.GetName(), h))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -14,19 +14,24 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package transformers
|
||||
package hash
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/k8sdeps/kunstruct"
|
||||
"sigs.k8s.io/kustomize/pkg/resid"
|
||||
"sigs.k8s.io/kustomize/pkg/resmap"
|
||||
"sigs.k8s.io/kustomize/pkg/resource"
|
||||
"sigs.k8s.io/kustomize/pkg/types"
|
||||
)
|
||||
|
||||
func TestNameHashTransformer(t *testing.T) {
|
||||
rf := resource.NewFactory(
|
||||
kunstruct.NewKunstructuredFactoryImpl())
|
||||
objs := resmap.ResMap{
|
||||
resource.NewResId(cmap, "cm1"): resource.NewResourceFromMap(
|
||||
resid.NewResId(cmap, "cm1"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
@@ -34,7 +39,7 @@ func TestNameHashTransformer(t *testing.T) {
|
||||
"name": "cm1",
|
||||
},
|
||||
}),
|
||||
resource.NewResId(deploy, "deploy1"): resource.NewResourceFromMap(
|
||||
resid.NewResId(deploy, "deploy1"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"group": "apps",
|
||||
"apiVersion": "v1",
|
||||
@@ -60,7 +65,7 @@ func TestNameHashTransformer(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}),
|
||||
resource.NewResId(service, "svc1"): resource.NewResourceFromMap(
|
||||
resid.NewResId(service, "svc1"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Service",
|
||||
@@ -76,18 +81,18 @@ func TestNameHashTransformer(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}),
|
||||
resource.NewResId(secret, "secret1"): resource.NewResourceFromMap(
|
||||
resid.NewResId(secret, "secret1"): rf.FromMapAndOption(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Secret",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "secret1",
|
||||
},
|
||||
}).SetBehavior(resource.BehaviorCreate),
|
||||
}, &types.GeneratorArgs{Behavior: "create"}, &types.GeneratorOptions{DisableNameSuffixHash: false}),
|
||||
}
|
||||
|
||||
expected := resmap.ResMap{
|
||||
resource.NewResId(cmap, "cm1"): resource.NewResourceFromMap(
|
||||
resid.NewResId(cmap, "cm1"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
@@ -95,7 +100,7 @@ func TestNameHashTransformer(t *testing.T) {
|
||||
"name": "cm1",
|
||||
},
|
||||
}),
|
||||
resource.NewResId(deploy, "deploy1"): resource.NewResourceFromMap(
|
||||
resid.NewResId(deploy, "deploy1"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"group": "apps",
|
||||
"apiVersion": "v1",
|
||||
@@ -121,7 +126,7 @@ func TestNameHashTransformer(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}),
|
||||
resource.NewResId(service, "svc1"): resource.NewResourceFromMap(
|
||||
resid.NewResId(service, "svc1"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Service",
|
||||
@@ -137,14 +142,14 @@ func TestNameHashTransformer(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}),
|
||||
resource.NewResId(secret, "secret1"): resource.NewResourceFromMap(
|
||||
resid.NewResId(secret, "secret1"): rf.FromMapAndOption(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Secret",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "secret1-7kc45hd5f7",
|
||||
},
|
||||
}).SetBehavior(resource.BehaviorCreate),
|
||||
}, &types.GeneratorArgs{Behavior: "create"}, &types.GeneratorOptions{DisableNameSuffixHash: false}),
|
||||
}
|
||||
|
||||
tran := NewNameHashTransformer()
|
||||
111
k8sdeps/transformer/inventory/inventory.go
Normal file
111
k8sdeps/transformer/inventory/inventory.go
Normal file
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
Copyright 2019 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package inventory
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sigs.k8s.io/kustomize/k8sdeps/kunstruct"
|
||||
"sigs.k8s.io/kustomize/k8sdeps/transformer/hash"
|
||||
"sigs.k8s.io/kustomize/pkg/gvk"
|
||||
"sigs.k8s.io/kustomize/pkg/resid"
|
||||
"sigs.k8s.io/kustomize/pkg/resmap"
|
||||
"sigs.k8s.io/kustomize/pkg/resource"
|
||||
"sigs.k8s.io/kustomize/pkg/transformers"
|
||||
"sigs.k8s.io/kustomize/pkg/types"
|
||||
)
|
||||
|
||||
//const PruneAnnotation = "kustomize.k8s.io/PruneRevision"
|
||||
const PruneAnnotation = "current"
|
||||
|
||||
// inventoryTransformer compute the ConfigMap used in prune
|
||||
type inventoryTransformer struct {
|
||||
append bool
|
||||
cmName string
|
||||
cmNamespace string
|
||||
}
|
||||
|
||||
var _ transformers.Transformer = &inventoryTransformer{}
|
||||
|
||||
// NewInventoryTransformer makes a inventoryTransformer.
|
||||
func NewInventoryTransformer(p *types.Inventory, namespace string, append bool) transformers.Transformer {
|
||||
if p == nil || p.Type != "ConfigMap" || p.ConfigMap.Namespace != namespace {
|
||||
return transformers.NewNoOpTransformer()
|
||||
}
|
||||
return &inventoryTransformer{
|
||||
append: append,
|
||||
cmName: p.ConfigMap.Name,
|
||||
cmNamespace: p.ConfigMap.Namespace,
|
||||
}
|
||||
}
|
||||
|
||||
// Transform generates an inventory ConfigMap based on the input ResMap.
|
||||
// this tranformer doesn't change existing resources -
|
||||
// it just visits resources and accumulates information to make a new ConfigMap.
|
||||
// The prune ConfigMap is used to support the pruning command in the client side tool,
|
||||
// which is proposed in https://github.com/kubernetes/enhancements/pull/810
|
||||
func (o *inventoryTransformer) Transform(m resmap.ResMap) error {
|
||||
var keys []string
|
||||
for _, r := range m {
|
||||
s := r.PruneString()
|
||||
keys = append(keys, s)
|
||||
for _, refid := range r.GetRefBy() {
|
||||
ref := m[refid]
|
||||
keys = append(keys, s+"---"+ref.PruneString())
|
||||
}
|
||||
}
|
||||
h, err := hash.SortArrayAndComputeHash(keys)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := &types.ConfigMapArgs{}
|
||||
args.Name = o.cmName
|
||||
args.Namespace = o.cmNamespace
|
||||
for _, key := range keys {
|
||||
args.LiteralSources = append(args.LiteralSources,
|
||||
key+"="+h)
|
||||
}
|
||||
opts := &types.GeneratorOptions{
|
||||
Annotations: make(map[string]string),
|
||||
}
|
||||
opts.Annotations[PruneAnnotation] = h
|
||||
|
||||
kf := kunstruct.NewKunstructuredFactoryImpl()
|
||||
k, err := kf.MakeConfigMap(nil, opts, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !o.append {
|
||||
for k := range m {
|
||||
delete(m, k)
|
||||
}
|
||||
}
|
||||
|
||||
id := resid.NewResIdWithPrefixNamespace(
|
||||
gvk.Gvk{
|
||||
Version: "v1",
|
||||
Kind: "ConfigMap",
|
||||
},
|
||||
o.cmName,
|
||||
"", o.cmNamespace)
|
||||
if _, ok := m[id]; ok {
|
||||
return fmt.Errorf("id %v is already used, please use a different name in the prune field", id)
|
||||
}
|
||||
m[id] = resource.NewFactory(kf).FromKunstructured(k)
|
||||
return nil
|
||||
}
|
||||
170
k8sdeps/transformer/inventory/inventory_test.go
Normal file
170
k8sdeps/transformer/inventory/inventory_test.go
Normal file
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
Copyright 2019 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package inventory
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/k8sdeps/kunstruct"
|
||||
"sigs.k8s.io/kustomize/pkg/gvk"
|
||||
"sigs.k8s.io/kustomize/pkg/resid"
|
||||
"sigs.k8s.io/kustomize/pkg/resmap"
|
||||
"sigs.k8s.io/kustomize/pkg/resource"
|
||||
"sigs.k8s.io/kustomize/pkg/types"
|
||||
)
|
||||
|
||||
var secret = gvk.Gvk{Version: "v1", Kind: "Secret"}
|
||||
var cmap = gvk.Gvk{Version: "v1", Kind: "ConfigMap"}
|
||||
var deploy = gvk.Gvk{Group: "apps", Version: "v1", Kind: "Deployment"}
|
||||
|
||||
func makeResMap() resmap.ResMap {
|
||||
rf := resource.NewFactory(
|
||||
kunstruct.NewKunstructuredFactoryImpl())
|
||||
objs := resmap.ResMap{
|
||||
resid.NewResId(cmap, "cm1"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "cm1",
|
||||
},
|
||||
}),
|
||||
resid.NewResId(secret, "secret1"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Secret",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "secret1",
|
||||
},
|
||||
}),
|
||||
resid.NewResId(deploy, "deploy1"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "deploy1",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"template": map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"containers": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "nginx",
|
||||
"image": "nginx:1.7.9",
|
||||
"env": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "CM_FOO",
|
||||
"valueFrom": map[string]interface{}{
|
||||
"configMapKeyRef": map[string]interface{}{
|
||||
"name": "cm1",
|
||||
"key": "somekey",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"envFrom": []interface{}{
|
||||
map[string]interface{}{
|
||||
"configMapRef": map[string]interface{}{
|
||||
"name": "cm1",
|
||||
"key": "somekey",
|
||||
},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"secretRef": map[string]interface{}{
|
||||
"name": "secret1",
|
||||
"key": "somekey",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}
|
||||
objs[resid.NewResId(cmap, "cm1")].AppendRefBy(resid.NewResId(deploy, "deploy1"))
|
||||
objs[resid.NewResId(secret, "secret1")].AppendRefBy(resid.NewResId(deploy, "deploy1"))
|
||||
return objs
|
||||
}
|
||||
|
||||
func TestInventoryTransformer(t *testing.T) {
|
||||
rf := resource.NewFactory(
|
||||
kunstruct.NewKunstructuredFactoryImpl())
|
||||
|
||||
// hash is derived based on all keys in the ConfigMap data field.
|
||||
// It is added to annotations as
|
||||
// current: hash
|
||||
// When seeing the same annotation, prune binary assumes no
|
||||
// clean up is needed
|
||||
hash := "k777d7h45b"
|
||||
// This is the root or inventory object which tracks all
|
||||
// the applied resources - this is the thing we expect the transformer to create.
|
||||
pruneMap := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "pruneCM",
|
||||
"namespace": "default",
|
||||
"annotations": map[string]interface{}{
|
||||
"current": hash,
|
||||
},
|
||||
},
|
||||
"data": map[string]interface{}{
|
||||
"_ConfigMap__cm1": hash,
|
||||
"_Secret__secret1": hash,
|
||||
"apps_Deployment__deploy1": hash,
|
||||
"_ConfigMap__cm1---apps_Deployment__deploy1": hash,
|
||||
"_Secret__secret1---apps_Deployment__deploy1": hash,
|
||||
},
|
||||
})
|
||||
expected := resmap.ResMap{
|
||||
resid.NewResIdWithPrefixNamespace(cmap, "pruneCM", "", "default"): pruneMap,
|
||||
}
|
||||
|
||||
p := &types.Inventory{
|
||||
Type: "ConfigMap",
|
||||
ConfigMap: types.NameArgs{
|
||||
Name: "pruneCM",
|
||||
Namespace: "default",
|
||||
},
|
||||
}
|
||||
objs := makeResMap()
|
||||
|
||||
// include the original resmap; only return the ConfigMap for pruning
|
||||
tran := NewInventoryTransformer(p, "default", false)
|
||||
tran.Transform(objs)
|
||||
|
||||
if !reflect.DeepEqual(objs, expected) {
|
||||
err := expected.ErrorIfNotEqual(objs)
|
||||
t.Fatalf("actual doesn't match expected: %v", err)
|
||||
}
|
||||
|
||||
objs = makeResMap()
|
||||
expected = objs.DeepCopy(rf)
|
||||
expected[resid.NewResIdWithPrefixNamespace(cmap, "pruneCM", "", "default")] = pruneMap
|
||||
// append the ConfigMap for pruning to the original resmap
|
||||
tran = NewInventoryTransformer(p, "default", true)
|
||||
tran.Transform(objs)
|
||||
|
||||
if !reflect.DeepEqual(objs, expected) {
|
||||
err := expected.ErrorIfNotEqual(objs)
|
||||
t.Fatalf("actual doesn't match expected: %v", err)
|
||||
}
|
||||
}
|
||||
174
k8sdeps/transformer/patch/patch.go
Normal file
174
k8sdeps/transformer/patch/patch.go
Normal file
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package patch
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/evanphx/json-patch"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/strategicpatch"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"sigs.k8s.io/kustomize/pkg/gvk"
|
||||
"sigs.k8s.io/kustomize/pkg/resmap"
|
||||
"sigs.k8s.io/kustomize/pkg/resource"
|
||||
"sigs.k8s.io/kustomize/pkg/transformers"
|
||||
)
|
||||
|
||||
// patchTransformer applies patches.
|
||||
type patchTransformer struct {
|
||||
patches []*resource.Resource
|
||||
rf *resource.Factory
|
||||
}
|
||||
|
||||
var _ transformers.Transformer = &patchTransformer{}
|
||||
|
||||
// NewPatchTransformer constructs a patchTransformer.
|
||||
func NewPatchTransformer(
|
||||
slice []*resource.Resource, rf *resource.Factory) (transformers.Transformer, error) {
|
||||
if len(slice) == 0 {
|
||||
return transformers.NewNoOpTransformer(), nil
|
||||
}
|
||||
return &patchTransformer{patches: slice, rf: rf}, nil
|
||||
}
|
||||
|
||||
// Transform apply the patches on top of the base resources.
|
||||
func (pt *patchTransformer) Transform(baseResourceMap resmap.ResMap) error {
|
||||
// Merge and then index the patches by Id.
|
||||
patches, err := pt.mergePatches()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Strategic merge the resources exist in both base and patches.
|
||||
for _, patch := range patches {
|
||||
// Merge patches with base resource.
|
||||
id := patch.Id()
|
||||
matchedIds := baseResourceMap.GetMatchingIds(id.GvknEquals)
|
||||
if len(matchedIds) == 0 {
|
||||
return fmt.Errorf("failed to find an object with %s to apply the patch", id.GvknString())
|
||||
}
|
||||
if len(matchedIds) > 1 {
|
||||
return fmt.Errorf("found multiple objects %#v targeted by patch %#v (ambiguous)", matchedIds, id)
|
||||
}
|
||||
id = matchedIds[0]
|
||||
base := baseResourceMap[id]
|
||||
merged := map[string]interface{}{}
|
||||
versionedObj, err := scheme.Scheme.New(toSchemaGvk(id.Gvk()))
|
||||
baseName := base.GetName()
|
||||
switch {
|
||||
case runtime.IsNotRegisteredError(err):
|
||||
// Use JSON merge patch to handle types w/o schema
|
||||
baseBytes, err := json.Marshal(base.Map())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
patchBytes, err := json.Marshal(patch.Map())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mergedBytes, err := jsonpatch.MergePatch(baseBytes, patchBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = json.Unmarshal(mergedBytes, &merged)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case err != nil:
|
||||
return err
|
||||
default:
|
||||
// Use Strategic-Merge-Patch to handle types w/ schema
|
||||
// TODO: Change this to use the new Merge package.
|
||||
// Store the name of the base object, because this name may have been munged.
|
||||
// Apply this name to the patched object.
|
||||
lookupPatchMeta, err := strategicpatch.NewPatchMetaFromStruct(versionedObj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
merged, err = strategicpatch.StrategicMergeMapPatchUsingLookupPatchMeta(
|
||||
base.Map(),
|
||||
patch.Map(),
|
||||
lookupPatchMeta)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
base.SetName(baseName)
|
||||
baseResourceMap[id].SetMap(merged)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// mergePatches merge and index patches by Id.
|
||||
// It errors out if there is conflict between patches.
|
||||
func (pt *patchTransformer) mergePatches() (resmap.ResMap, error) {
|
||||
rc := resmap.ResMap{}
|
||||
for ix, patch := range pt.patches {
|
||||
id := patch.Id()
|
||||
existing, found := rc[id]
|
||||
if !found {
|
||||
rc[id] = patch
|
||||
continue
|
||||
}
|
||||
|
||||
versionedObj, err := scheme.Scheme.New(toSchemaGvk(id.Gvk()))
|
||||
if err != nil && !runtime.IsNotRegisteredError(err) {
|
||||
return nil, err
|
||||
}
|
||||
var cd conflictDetector
|
||||
if err != nil {
|
||||
cd = newJMPConflictDetector(pt.rf)
|
||||
} else {
|
||||
cd, err = newSMPConflictDetector(versionedObj, pt.rf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
conflict, err := cd.hasConflict(existing, patch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if conflict {
|
||||
conflictingPatch, err := cd.findConflict(ix, pt.patches)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, fmt.Errorf(
|
||||
"conflict between %#v and %#v",
|
||||
conflictingPatch.Map(), patch.Map())
|
||||
}
|
||||
merged, err := cd.mergePatches(existing, patch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rc[id] = merged
|
||||
}
|
||||
return rc, nil
|
||||
}
|
||||
|
||||
// toSchemaGvk converts to a schema.GroupVersionKind.
|
||||
func toSchemaGvk(x gvk.Gvk) schema.GroupVersionKind {
|
||||
return schema.GroupVersionKind{
|
||||
Group: x.Group,
|
||||
Version: x.Version,
|
||||
Kind: x.Kind,
|
||||
}
|
||||
}
|
||||
563
k8sdeps/transformer/patch/patch_test.go
Normal file
563
k8sdeps/transformer/patch/patch_test.go
Normal file
@@ -0,0 +1,563 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package patch
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/k8sdeps/kunstruct"
|
||||
"sigs.k8s.io/kustomize/pkg/gvk"
|
||||
"sigs.k8s.io/kustomize/pkg/resid"
|
||||
"sigs.k8s.io/kustomize/pkg/resmap"
|
||||
"sigs.k8s.io/kustomize/pkg/resource"
|
||||
)
|
||||
|
||||
var rf = resource.NewFactory(
|
||||
kunstruct.NewKunstructuredFactoryImpl())
|
||||
var deploy = gvk.Gvk{Group: "apps", Version: "v1", Kind: "Deployment"}
|
||||
var foo = gvk.Gvk{Group: "example.com", Version: "v1", Kind: "Foo"}
|
||||
|
||||
func TestOverlayRun(t *testing.T) {
|
||||
base := resmap.ResMap{
|
||||
resid.NewResId(deploy, "deploy1"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "deploy1",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"template": map[string]interface{}{
|
||||
"metadata": map[string]interface{}{
|
||||
"labels": map[string]interface{}{
|
||||
"old-label": "old-value",
|
||||
},
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"containers": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "nginx",
|
||||
"image": "nginx",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}
|
||||
patch := []*resource.Resource{
|
||||
rf.FromMap(map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "deploy1",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"template": map[string]interface{}{
|
||||
"metadata": map[string]interface{}{
|
||||
"labels": map[string]interface{}{
|
||||
"another-label": "foo",
|
||||
},
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"containers": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "nginx",
|
||||
"image": "nginx:latest",
|
||||
"env": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "SOMEENV",
|
||||
"value": "BAR",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
}
|
||||
expected := resmap.ResMap{
|
||||
resid.NewResId(deploy, "deploy1"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "deploy1",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"template": map[string]interface{}{
|
||||
"metadata": map[string]interface{}{
|
||||
"labels": map[string]interface{}{
|
||||
"old-label": "old-value",
|
||||
"another-label": "foo",
|
||||
},
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"containers": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "nginx",
|
||||
"image": "nginx:latest",
|
||||
"env": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "SOMEENV",
|
||||
"value": "BAR",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}
|
||||
lt, err := NewPatchTransformer(patch, rf)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
err = lt.Transform(base)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(base, expected) {
|
||||
err = expected.ErrorIfNotEqual(base)
|
||||
t.Fatalf("actual doesn't match expected: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiplePatches(t *testing.T) {
|
||||
base := resmap.ResMap{
|
||||
resid.NewResId(deploy, "deploy1"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "deploy1",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"template": map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"containers": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "nginx",
|
||||
"image": "nginx",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}
|
||||
patch := []*resource.Resource{
|
||||
rf.FromMap(map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "deploy1",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"template": map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"containers": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "nginx",
|
||||
"image": "nginx:latest",
|
||||
"env": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "SOMEENV",
|
||||
"value": "BAR",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
rf.FromMap(map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "deploy1",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"template": map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"containers": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "nginx",
|
||||
"env": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "ANOTHERENV",
|
||||
"value": "HELLO",
|
||||
},
|
||||
},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"name": "busybox",
|
||||
"image": "busybox",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
}
|
||||
expected := resmap.ResMap{
|
||||
resid.NewResId(deploy, "deploy1"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "deploy1",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"template": map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"containers": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "nginx",
|
||||
"image": "nginx:latest",
|
||||
"env": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "ANOTHERENV",
|
||||
"value": "HELLO",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"name": "SOMEENV",
|
||||
"value": "BAR",
|
||||
},
|
||||
},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"name": "busybox",
|
||||
"image": "busybox",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}
|
||||
lt, err := NewPatchTransformer(patch, rf)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
err = lt.Transform(base)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(base, expected) {
|
||||
err = expected.ErrorIfNotEqual(base)
|
||||
t.Fatalf("actual doesn't match expected: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiplePatchesWithConflict(t *testing.T) {
|
||||
base := resmap.ResMap{
|
||||
resid.NewResId(deploy, "deploy1"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "deploy1",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"template": map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"containers": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "nginx",
|
||||
"image": "nginx",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}
|
||||
patch := []*resource.Resource{
|
||||
rf.FromMap(map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "deploy1",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"template": map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"containers": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "nginx",
|
||||
"image": "nginx:latest",
|
||||
"env": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "SOMEENV",
|
||||
"value": "BAR",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
rf.FromMap(map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "deploy1",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"template": map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"containers": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "nginx",
|
||||
"image": "nginx:1.7.9",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
}
|
||||
|
||||
lt, err := NewPatchTransformer(patch, rf)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
err = lt.Transform(base)
|
||||
if err == nil {
|
||||
t.Fatalf("did not get expected error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "conflict") {
|
||||
t.Fatalf("expected error to contain %q but get %v", "conflict", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoSchemaOverlayRun(t *testing.T) {
|
||||
base := resmap.ResMap{
|
||||
resid.NewResId(foo, "my-foo"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "example.com/v1",
|
||||
"kind": "Foo",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "my-foo",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"bar": map[string]interface{}{
|
||||
"A": "X",
|
||||
"B": "Y",
|
||||
},
|
||||
},
|
||||
}),
|
||||
}
|
||||
patch := []*resource.Resource{
|
||||
rf.FromMap(map[string]interface{}{
|
||||
"apiVersion": "example.com/v1",
|
||||
"kind": "Foo",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "my-foo",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"bar": map[string]interface{}{
|
||||
"B": nil,
|
||||
"C": "Z",
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
}
|
||||
expected := resmap.ResMap{
|
||||
resid.NewResId(foo, "my-foo"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "example.com/v1",
|
||||
"kind": "Foo",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "my-foo",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"bar": map[string]interface{}{
|
||||
"A": "X",
|
||||
"C": "Z",
|
||||
},
|
||||
},
|
||||
}),
|
||||
}
|
||||
|
||||
lt, err := NewPatchTransformer(patch, rf)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
err = lt.Transform(base)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if err = expected.ErrorIfNotEqual(base); err != nil {
|
||||
t.Fatalf("actual doesn't match expected: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoSchemaMultiplePatches(t *testing.T) {
|
||||
base := resmap.ResMap{
|
||||
resid.NewResId(foo, "my-foo"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "example.com/v1",
|
||||
"kind": "Foo",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "my-foo",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"bar": map[string]interface{}{
|
||||
"A": "X",
|
||||
"B": "Y",
|
||||
},
|
||||
},
|
||||
}),
|
||||
}
|
||||
patch := []*resource.Resource{
|
||||
rf.FromMap(map[string]interface{}{
|
||||
"apiVersion": "example.com/v1",
|
||||
"kind": "Foo",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "my-foo",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"bar": map[string]interface{}{
|
||||
"B": nil,
|
||||
"C": "Z",
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
rf.FromMap(map[string]interface{}{
|
||||
"apiVersion": "example.com/v1",
|
||||
"kind": "Foo",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "my-foo",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"bar": map[string]interface{}{
|
||||
"C": "Z",
|
||||
"D": "W",
|
||||
},
|
||||
"baz": map[string]interface{}{
|
||||
"hello": "world",
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
}
|
||||
expected := resmap.ResMap{
|
||||
resid.NewResId(foo, "my-foo"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "example.com/v1",
|
||||
"kind": "Foo",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "my-foo",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"bar": map[string]interface{}{
|
||||
"A": "X",
|
||||
"C": "Z",
|
||||
"D": "W",
|
||||
},
|
||||
"baz": map[string]interface{}{
|
||||
"hello": "world",
|
||||
},
|
||||
},
|
||||
}),
|
||||
}
|
||||
|
||||
lt, err := NewPatchTransformer(patch, rf)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
err = lt.Transform(base)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if err = expected.ErrorIfNotEqual(base); err != nil {
|
||||
t.Fatalf("actual doesn't match expected: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoSchemaMultiplePatchesWithConflict(t *testing.T) {
|
||||
base := resmap.ResMap{
|
||||
resid.NewResId(foo, "my-foo"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "example.com/v1",
|
||||
"kind": "Foo",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "my-foo",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"bar": map[string]interface{}{
|
||||
"A": "X",
|
||||
"B": "Y",
|
||||
},
|
||||
},
|
||||
}),
|
||||
}
|
||||
patch := []*resource.Resource{
|
||||
rf.FromMap(map[string]interface{}{
|
||||
"apiVersion": "example.com/v1",
|
||||
"kind": "Foo",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "my-foo",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"bar": map[string]interface{}{
|
||||
"B": nil,
|
||||
"C": "Z",
|
||||
},
|
||||
},
|
||||
}),
|
||||
rf.FromMap(map[string]interface{}{
|
||||
"apiVersion": "example.com/v1",
|
||||
"kind": "Foo",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "my-foo",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"bar": map[string]interface{}{
|
||||
"C": "NOT_Z",
|
||||
},
|
||||
},
|
||||
}),
|
||||
}
|
||||
|
||||
lt, err := NewPatchTransformer(patch, rf)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
err = lt.Transform(base)
|
||||
if err == nil {
|
||||
t.Fatalf("did not get expected error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "conflict") {
|
||||
t.Fatalf("expected error to contain %q but get %v", "conflict", err)
|
||||
}
|
||||
}
|
||||
137
k8sdeps/transformer/patch/patchconflictdetector.go
Normal file
137
k8sdeps/transformer/patch/patchconflictdetector.go
Normal file
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package patch
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/evanphx/json-patch"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/mergepatch"
|
||||
"k8s.io/apimachinery/pkg/util/strategicpatch"
|
||||
"sigs.k8s.io/kustomize/pkg/resource"
|
||||
)
|
||||
|
||||
type conflictDetector interface {
|
||||
hasConflict(patch1, patch2 *resource.Resource) (bool, error)
|
||||
findConflict(conflictingPatchIdx int, patches []*resource.Resource) (*resource.Resource, error)
|
||||
mergePatches(patch1, patch2 *resource.Resource) (*resource.Resource, error)
|
||||
}
|
||||
|
||||
type jsonMergePatch struct {
|
||||
rf *resource.Factory
|
||||
}
|
||||
|
||||
var _ conflictDetector = &jsonMergePatch{}
|
||||
|
||||
func newJMPConflictDetector(rf *resource.Factory) conflictDetector {
|
||||
return &jsonMergePatch{rf: rf}
|
||||
}
|
||||
|
||||
func (jmp *jsonMergePatch) hasConflict(
|
||||
patch1, patch2 *resource.Resource) (bool, error) {
|
||||
return mergepatch.HasConflicts(patch1.Map(), patch2.Map())
|
||||
}
|
||||
|
||||
func (jmp *jsonMergePatch) findConflict(
|
||||
conflictingPatchIdx int, patches []*resource.Resource) (*resource.Resource, error) {
|
||||
for i, patch := range patches {
|
||||
if i == conflictingPatchIdx {
|
||||
continue
|
||||
}
|
||||
if !patches[conflictingPatchIdx].Id().GvknEquals(patch.Id()) {
|
||||
continue
|
||||
}
|
||||
conflict, err := mergepatch.HasConflicts(
|
||||
patch.Map(),
|
||||
patches[conflictingPatchIdx].Map())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if conflict {
|
||||
return patch, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (jmp *jsonMergePatch) mergePatches(
|
||||
patch1, patch2 *resource.Resource) (*resource.Resource, error) {
|
||||
baseBytes, err := json.Marshal(patch1.Map())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
patchBytes, err := json.Marshal(patch2.Map())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mergedBytes, err := jsonpatch.MergeMergePatches(baseBytes, patchBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mergedMap := make(map[string]interface{})
|
||||
err = json.Unmarshal(mergedBytes, &mergedMap)
|
||||
return jmp.rf.FromMap(mergedMap), err
|
||||
}
|
||||
|
||||
type strategicMergePatch struct {
|
||||
lookupPatchMeta strategicpatch.LookupPatchMeta
|
||||
rf *resource.Factory
|
||||
}
|
||||
|
||||
var _ conflictDetector = &strategicMergePatch{}
|
||||
|
||||
func newSMPConflictDetector(
|
||||
versionedObj runtime.Object,
|
||||
rf *resource.Factory) (conflictDetector, error) {
|
||||
lookupPatchMeta, err := strategicpatch.NewPatchMetaFromStruct(versionedObj)
|
||||
return &strategicMergePatch{lookupPatchMeta: lookupPatchMeta, rf: rf}, err
|
||||
}
|
||||
|
||||
func (smp *strategicMergePatch) hasConflict(p1, p2 *resource.Resource) (bool, error) {
|
||||
return strategicpatch.MergingMapsHaveConflicts(
|
||||
p1.Map(), p2.Map(), smp.lookupPatchMeta)
|
||||
}
|
||||
|
||||
func (smp *strategicMergePatch) findConflict(
|
||||
conflictingPatchIdx int, patches []*resource.Resource) (*resource.Resource, error) {
|
||||
for i, patch := range patches {
|
||||
if i == conflictingPatchIdx {
|
||||
continue
|
||||
}
|
||||
if !patches[conflictingPatchIdx].Id().GvknEquals(patch.Id()) {
|
||||
continue
|
||||
}
|
||||
conflict, err := strategicpatch.MergingMapsHaveConflicts(
|
||||
patch.Map(),
|
||||
patches[conflictingPatchIdx].Map(),
|
||||
smp.lookupPatchMeta)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if conflict {
|
||||
return patch, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (smp *strategicMergePatch) mergePatches(patch1, patch2 *resource.Resource) (*resource.Resource, error) {
|
||||
mergeJSONMap, err := strategicpatch.MergeStrategicMergeMapPatchUsingLookupPatchMeta(
|
||||
smp.lookupPatchMeta, patch1.Map(), patch2.Map())
|
||||
return smp.rf.FromMap(mergeJSONMap), err
|
||||
}
|
||||
61
k8sdeps/validator/validators.go
Normal file
61
k8sdeps/validator/validators.go
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package validator provides functions to validate labels, annotations, namespace using apimachinery
|
||||
package validator
|
||||
|
||||
import (
|
||||
"errors"
|
||||
apivalidation "k8s.io/apimachinery/pkg/api/validation"
|
||||
v1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
// KustValidator validates Labels and annotations by apimachinery
|
||||
type KustValidator struct{}
|
||||
|
||||
// NewKustValidator returns a KustValidator object
|
||||
func NewKustValidator() *KustValidator {
|
||||
return &KustValidator{}
|
||||
}
|
||||
|
||||
// MakeAnnotationValidator returns a MapValidatorFunc using apimachinery.
|
||||
func (v *KustValidator) MakeAnnotationValidator() func(map[string]string) error {
|
||||
return func(x map[string]string) error {
|
||||
errs := apivalidation.ValidateAnnotations(x, field.NewPath("field"))
|
||||
if len(errs) > 0 {
|
||||
return errors.New(errs.ToAggregate().Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// MakeLabelValidator returns a MapValidatorFunc using apimachinery.
|
||||
func (v *KustValidator) MakeLabelValidator() func(map[string]string) error {
|
||||
return func(x map[string]string) error {
|
||||
errs := v1validation.ValidateLabels(x, field.NewPath("field"))
|
||||
if len(errs) > 0 {
|
||||
return errors.New(errs.ToAggregate().Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateNamespace validates a string is a valid namespace using apimachinery.
|
||||
func (v *KustValidator) ValidateNamespace(s string) []string {
|
||||
return validation.IsDNS1123Label(s)
|
||||
}
|
||||
@@ -19,14 +19,10 @@ package main
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"sigs.k8s.io/kustomize/pkg/commands"
|
||||
)
|
||||
|
||||
func main() {
|
||||
defer glog.Flush()
|
||||
|
||||
if err := commands.NewDefaultCommand().Execute(); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
161
pkg/accumulator/resaccumulator.go
Normal file
161
pkg/accumulator/resaccumulator.go
Normal file
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package accumulator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"sigs.k8s.io/kustomize/pkg/resid"
|
||||
"sigs.k8s.io/kustomize/pkg/resmap"
|
||||
"sigs.k8s.io/kustomize/pkg/transformers"
|
||||
"sigs.k8s.io/kustomize/pkg/transformers/config"
|
||||
"sigs.k8s.io/kustomize/pkg/types"
|
||||
)
|
||||
|
||||
// ResAccumulator accumulates resources and the rules
|
||||
// used to customize those resources.
|
||||
type ResAccumulator struct {
|
||||
resMap resmap.ResMap
|
||||
tConfig *config.TransformerConfig
|
||||
varSet types.VarSet
|
||||
}
|
||||
|
||||
func MakeEmptyAccumulator() *ResAccumulator {
|
||||
ra := &ResAccumulator{}
|
||||
ra.resMap = make(resmap.ResMap)
|
||||
ra.tConfig = &config.TransformerConfig{}
|
||||
ra.varSet = types.VarSet{}
|
||||
return ra
|
||||
}
|
||||
|
||||
// ResMap returns a copy of the internal resMap.
|
||||
func (ra *ResAccumulator) ResMap() resmap.ResMap {
|
||||
result := make(resmap.ResMap)
|
||||
for k, v := range ra.resMap {
|
||||
result[k] = v
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Vars returns a copy of underlying vars.
|
||||
func (ra *ResAccumulator) Vars() []types.Var {
|
||||
return ra.varSet.Set()
|
||||
}
|
||||
|
||||
func (ra *ResAccumulator) MergeResourcesWithErrorOnIdCollision(
|
||||
resources resmap.ResMap) (err error) {
|
||||
ra.resMap, err = resmap.MergeWithErrorOnIdCollision(
|
||||
resources, ra.resMap)
|
||||
return err
|
||||
}
|
||||
|
||||
func (ra *ResAccumulator) MergeResourcesWithOverride(
|
||||
resources resmap.ResMap) (err error) {
|
||||
ra.resMap, err = resmap.MergeWithOverride(
|
||||
ra.resMap, resources)
|
||||
return err
|
||||
}
|
||||
|
||||
func (ra *ResAccumulator) MergeConfig(
|
||||
tConfig *config.TransformerConfig) (err error) {
|
||||
ra.tConfig, err = ra.tConfig.Merge(tConfig)
|
||||
return err
|
||||
}
|
||||
|
||||
func (ra *ResAccumulator) GetTransformerConfig() *config.TransformerConfig {
|
||||
return ra.tConfig
|
||||
}
|
||||
|
||||
func (ra *ResAccumulator) MergeVars(incoming []types.Var) error {
|
||||
return ra.varSet.MergeSlice(incoming)
|
||||
}
|
||||
|
||||
func (ra *ResAccumulator) MergeAccumulator(other *ResAccumulator) (err error) {
|
||||
err = ra.MergeResourcesWithErrorOnIdCollision(other.resMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ra.MergeConfig(other.tConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ra.varSet.MergeSet(&other.varSet)
|
||||
}
|
||||
|
||||
// makeVarReplacementMap returns a map of Var names to
|
||||
// their final values. The values are strings intended
|
||||
// for substitution wherever the $(var.Name) occurs.
|
||||
func (ra *ResAccumulator) makeVarReplacementMap() (map[string]string, error) {
|
||||
result := map[string]string{}
|
||||
for _, v := range ra.Vars() {
|
||||
matched := ra.resMap.GetMatchingIds(
|
||||
resid.NewResId(v.ObjRef.GVK(), v.ObjRef.Name).GvknEquals)
|
||||
if len(matched) > 1 {
|
||||
return nil, fmt.Errorf(
|
||||
"found %d resId matches for var %s "+
|
||||
"(unable to disambiguate)",
|
||||
len(matched), v)
|
||||
}
|
||||
if len(matched) == 1 {
|
||||
s, err := ra.resMap[matched[0]].GetFieldValue(v.FieldRef.FieldPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"field specified in var '%v' "+
|
||||
"not found in corresponding resource", v)
|
||||
}
|
||||
result[v.Name] = s
|
||||
} else {
|
||||
return nil, fmt.Errorf(
|
||||
"var '%v' cannot be mapped to a field "+
|
||||
"in the set of known resources", v)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (ra *ResAccumulator) Transform(t transformers.Transformer) error {
|
||||
return t.Transform(ra.resMap)
|
||||
}
|
||||
|
||||
func (ra *ResAccumulator) ResolveVars() error {
|
||||
replacementMap, err := ra.makeVarReplacementMap()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(replacementMap) == 0 {
|
||||
return nil
|
||||
}
|
||||
t := transformers.NewRefVarTransformer(
|
||||
replacementMap, ra.tConfig.VarReference)
|
||||
err = ra.Transform(t)
|
||||
if len(t.UnusedVars()) > 0 {
|
||||
log.Printf(
|
||||
"well-defined vars that were never replaced: %s\n",
|
||||
strings.Join(t.UnusedVars(), ","))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (ra *ResAccumulator) FixBackReferences() (err error) {
|
||||
if ra.tConfig.NameReference == nil {
|
||||
return nil
|
||||
}
|
||||
return ra.Transform(transformers.NewNameReferenceTransformer(
|
||||
ra.tConfig.NameReference))
|
||||
}
|
||||
284
pkg/accumulator/resaccumulator_test.go
Normal file
284
pkg/accumulator/resaccumulator_test.go
Normal file
@@ -0,0 +1,284 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package accumulator_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/k8sdeps/kunstruct"
|
||||
. "sigs.k8s.io/kustomize/pkg/accumulator"
|
||||
"sigs.k8s.io/kustomize/pkg/gvk"
|
||||
"sigs.k8s.io/kustomize/pkg/resid"
|
||||
"sigs.k8s.io/kustomize/pkg/resmap"
|
||||
"sigs.k8s.io/kustomize/pkg/resource"
|
||||
"sigs.k8s.io/kustomize/pkg/transformers/config"
|
||||
"sigs.k8s.io/kustomize/pkg/types"
|
||||
)
|
||||
|
||||
func makeResAccumulator() (*ResAccumulator, *resource.Factory, error) {
|
||||
ra := MakeEmptyAccumulator()
|
||||
err := ra.MergeConfig(config.MakeDefaultConfig())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
rf := resource.NewFactory(
|
||||
kunstruct.NewKunstructuredFactoryImpl())
|
||||
ra.MergeResourcesWithErrorOnIdCollision(resmap.ResMap{
|
||||
resid.NewResId(
|
||||
gvk.Gvk{Group: "apps", Version: "v1", Kind: "Deployment"},
|
||||
"deploy1"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "deploy1",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"template": map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"containers": []interface{}{
|
||||
map[string]interface{}{
|
||||
"command": []interface{}{
|
||||
"myserver",
|
||||
"--somebackendService $(SERVICE_ONE)",
|
||||
"--yetAnother $(SERVICE_TWO)",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
resid.NewResId(
|
||||
gvk.Gvk{Version: "v1", Kind: "Service"},
|
||||
"backendOne"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Service",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "backendOne",
|
||||
},
|
||||
}),
|
||||
resid.NewResId(
|
||||
gvk.Gvk{Version: "v1", Kind: "Service"},
|
||||
"backendTwo"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Service",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "backendTwo",
|
||||
},
|
||||
}),
|
||||
})
|
||||
return ra, rf, nil
|
||||
}
|
||||
|
||||
func TestResolveVarsHappy(t *testing.T) {
|
||||
ra, _, err := makeResAccumulator()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
err = ra.MergeVars([]types.Var{
|
||||
{
|
||||
Name: "SERVICE_ONE",
|
||||
ObjRef: types.Target{
|
||||
Gvk: gvk.Gvk{Version: "v1", Kind: "Service"},
|
||||
Name: "backendOne"},
|
||||
},
|
||||
{
|
||||
Name: "SERVICE_TWO",
|
||||
ObjRef: types.Target{
|
||||
Gvk: gvk.Gvk{Version: "v1", Kind: "Service"},
|
||||
Name: "backendTwo"},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
err = ra.ResolveVars()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
c := getCommand(find("deploy1", ra.ResMap()))
|
||||
if c != "myserver --somebackendService backendOne --yetAnother backendTwo" {
|
||||
t.Fatalf("unexpected command: %s", c)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveVarsOneUnused(t *testing.T) {
|
||||
ra, _, err := makeResAccumulator()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
err = ra.MergeVars([]types.Var{
|
||||
{
|
||||
Name: "SERVICE_ONE",
|
||||
ObjRef: types.Target{
|
||||
Gvk: gvk.Gvk{Version: "v1", Kind: "Service"},
|
||||
Name: "backendOne"},
|
||||
},
|
||||
{
|
||||
Name: "SERVICE_UNUSED",
|
||||
ObjRef: types.Target{
|
||||
Gvk: gvk.Gvk{Version: "v1", Kind: "Service"},
|
||||
Name: "backendTwo"},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
log.SetOutput(&buf)
|
||||
defer func() {
|
||||
log.SetOutput(os.Stderr)
|
||||
}()
|
||||
err = ra.ResolveVars()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
expectLog(t, buf, "well-defined vars that were never replaced: SERVICE_UNUSED")
|
||||
c := getCommand(find("deploy1", ra.ResMap()))
|
||||
if c != "myserver --somebackendService backendOne --yetAnother $(SERVICE_TWO)" {
|
||||
t.Fatalf("unexpected command: %s", c)
|
||||
}
|
||||
}
|
||||
|
||||
func expectLog(t *testing.T, log bytes.Buffer, expect string) {
|
||||
if !strings.Contains(log.String(), expect) {
|
||||
t.Fatalf("expected log containing '%s', got '%s'", expect, log.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveVarsVarNeedsDisambiguation(t *testing.T) {
|
||||
ra, rf, err := makeResAccumulator()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
ra.MergeResourcesWithErrorOnIdCollision(resmap.ResMap{
|
||||
resid.NewResIdWithPrefixNamespace(
|
||||
gvk.Gvk{Version: "v1", Kind: "Service"},
|
||||
"backendOne", "", "fooNamespace"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Service",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "backendOne",
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
err = ra.MergeVars([]types.Var{
|
||||
{
|
||||
Name: "SERVICE_ONE",
|
||||
ObjRef: types.Target{
|
||||
Gvk: gvk.Gvk{Version: "v1", Kind: "Service"},
|
||||
Name: "backendOne",
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
err = ra.ResolveVars()
|
||||
if err == nil {
|
||||
t.Fatalf("expected error")
|
||||
}
|
||||
if !strings.Contains(
|
||||
err.Error(), "unable to disambiguate") {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveVarsGoodResIdBadField(t *testing.T) {
|
||||
ra, _, err := makeResAccumulator()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
err = ra.MergeVars([]types.Var{
|
||||
{
|
||||
Name: "SERVICE_ONE",
|
||||
ObjRef: types.Target{
|
||||
Gvk: gvk.Gvk{Version: "v1", Kind: "Service"},
|
||||
Name: "backendOne"},
|
||||
FieldRef: types.FieldSelector{FieldPath: "nope_nope_nope"},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
err = ra.ResolveVars()
|
||||
if err == nil {
|
||||
t.Fatalf("expected error")
|
||||
}
|
||||
if !strings.Contains(
|
||||
err.Error(),
|
||||
"not found in corresponding resource") {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveVarsUnmappableVar(t *testing.T) {
|
||||
ra, _, err := makeResAccumulator()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
err = ra.MergeVars([]types.Var{
|
||||
{
|
||||
Name: "SERVICE_THREE",
|
||||
ObjRef: types.Target{
|
||||
Gvk: gvk.Gvk{Version: "v1", Kind: "Service"},
|
||||
Name: "doesNotExist"},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
err = ra.ResolveVars()
|
||||
if err == nil {
|
||||
t.Fatalf("expected error")
|
||||
}
|
||||
if !strings.Contains(
|
||||
err.Error(),
|
||||
"cannot be mapped to a field in the set of known resources") {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func find(name string, resMap resmap.ResMap) *resource.Resource {
|
||||
for k, v := range resMap {
|
||||
if k.Name() == name {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Assumes arg is a deployment, returns the command of first container.
|
||||
func getCommand(r *resource.Resource) string {
|
||||
var m map[string]interface{}
|
||||
var c []interface{}
|
||||
m, _ = r.Map()["spec"].(map[string]interface{})
|
||||
m, _ = m["template"].(map[string]interface{})
|
||||
m, _ = m["spec"].(map[string]interface{})
|
||||
c, _ = m["containers"].([]interface{})
|
||||
m, _ = c[0].(map[string]interface{})
|
||||
return strings.Join(m["command"].([]string), " ")
|
||||
}
|
||||
@@ -1,330 +0,0 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package app implements state for the set of all resources being customized.
|
||||
// Should rename this - there's nothing "app"y about it.
|
||||
package app
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/golang/glog"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"sigs.k8s.io/kustomize/pkg/configmapandsecret"
|
||||
"sigs.k8s.io/kustomize/pkg/constants"
|
||||
"sigs.k8s.io/kustomize/pkg/crds"
|
||||
"sigs.k8s.io/kustomize/pkg/fs"
|
||||
interror "sigs.k8s.io/kustomize/pkg/internal/error"
|
||||
"sigs.k8s.io/kustomize/pkg/loader"
|
||||
"sigs.k8s.io/kustomize/pkg/patch"
|
||||
patchtransformer "sigs.k8s.io/kustomize/pkg/patch/transformer"
|
||||
"sigs.k8s.io/kustomize/pkg/resmap"
|
||||
"sigs.k8s.io/kustomize/pkg/resource"
|
||||
"sigs.k8s.io/kustomize/pkg/transformers"
|
||||
"sigs.k8s.io/kustomize/pkg/types"
|
||||
)
|
||||
|
||||
// Application implements the guts of the kustomize 'build' command.
|
||||
// TODO: Change name, as "application" is overloaded and somewhat
|
||||
// misleading (one can customize an RBAC policy). Perhaps "Target"
|
||||
// https://github.com/kubernetes-sigs/kustomize/blob/master/docs/glossary.md#target
|
||||
type Application struct {
|
||||
kustomization *types.Kustomization
|
||||
ldr loader.Loader
|
||||
fSys fs.FileSystem
|
||||
}
|
||||
|
||||
// NewApplication returns a new instance of Application primed with a Loader.
|
||||
func NewApplication(ldr loader.Loader, fSys fs.FileSystem) (*Application, error) {
|
||||
content, err := ldr.Load(constants.KustomizationFileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var m types.Kustomization
|
||||
err = unmarshal(content, &m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Application{kustomization: &m, ldr: ldr, fSys: fSys}, nil
|
||||
}
|
||||
|
||||
func unmarshal(y []byte, o interface{}) error {
|
||||
j, err := yaml.YAMLToJSON(y)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dec := json.NewDecoder(bytes.NewReader(j))
|
||||
dec.DisallowUnknownFields()
|
||||
return dec.Decode(o)
|
||||
}
|
||||
|
||||
// MakeCustomizedResMap creates a ResMap per kustomization instructions.
|
||||
// The Resources in the returned ResMap are fully customized.
|
||||
func (a *Application) MakeCustomizedResMap() (resmap.ResMap, error) {
|
||||
m, err := a.loadCustomizedResMap()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return a.resolveRefsToGeneratedResources(m)
|
||||
}
|
||||
|
||||
// resolveRefsToGeneratedResources fixes all name references.
|
||||
func (a *Application) resolveRefsToGeneratedResources(m resmap.ResMap) (resmap.ResMap, error) {
|
||||
err := transformers.NewNameHashTransformer().Transform(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var r []transformers.Transformer
|
||||
t, err := transformers.NewDefaultingNameReferenceTransformer()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r = append(r, t)
|
||||
|
||||
refVars, err := a.resolveRefVars(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t, err = transformers.NewRefVarTransformer(refVars)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r = append(r, t)
|
||||
|
||||
err = transformers.NewMultiTransformer(r).Transform(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// loadCustomizedResMap loads and customizes resources to build a ResMap.
|
||||
func (a *Application) loadCustomizedResMap() (resmap.ResMap, error) {
|
||||
errs := &interror.KustomizationErrors{}
|
||||
result, err := a.loadResMapFromBasesAndResources()
|
||||
if err != nil {
|
||||
errs.Append(errors.Wrap(err, "loadResMapFromBasesAndResources"))
|
||||
}
|
||||
err = crds.RegisterCRDs(a.ldr, a.kustomization.Crds)
|
||||
if err != nil {
|
||||
errs.Append(errors.Wrap(err, "RegisterCRDs"))
|
||||
}
|
||||
cms, err := resmap.NewResMapFromConfigMapArgs(
|
||||
configmapandsecret.NewConfigMapFactory(a.fSys, a.ldr),
|
||||
a.kustomization.ConfigMapGenerator)
|
||||
if err != nil {
|
||||
errs.Append(errors.Wrap(err, "NewResMapFromConfigMapArgs"))
|
||||
}
|
||||
secrets, err := resmap.NewResMapFromSecretArgs(
|
||||
configmapandsecret.NewSecretFactory(a.fSys, a.ldr.Root()),
|
||||
a.kustomization.SecretGenerator)
|
||||
if err != nil {
|
||||
errs.Append(errors.Wrap(err, "NewResMapFromSecretArgs"))
|
||||
}
|
||||
res, err := resmap.MergeWithoutOverride(cms, secrets)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Merge")
|
||||
}
|
||||
|
||||
result, err = resmap.MergeWithOverride(result, res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
a.kustomization.PatchesStrategicMerge = patch.Append(a.kustomization.PatchesStrategicMerge, a.kustomization.Patches...)
|
||||
patches, err := resmap.NewResourceSliceFromPatches(a.ldr, a.kustomization.PatchesStrategicMerge)
|
||||
if err != nil {
|
||||
errs.Append(errors.Wrap(err, "NewResourceSliceFromPatches"))
|
||||
}
|
||||
|
||||
if len(errs.Get()) > 0 {
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
var r []transformers.Transformer
|
||||
t, err := a.newTransformer(patches)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r = append(r, t)
|
||||
t, err = patchtransformer.NewPatchJson6902Factory(a.ldr).MakePatchJson6902Transformer(a.kustomization.PatchesJson6902)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r = append(r, t)
|
||||
t, err = transformers.NewImageTagTransformer(a.kustomization.ImageTags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r = append(r, t)
|
||||
|
||||
err = transformers.NewMultiTransformer(r).Transform(result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Gets Bases and Resources as advertised.
|
||||
func (a *Application) loadResMapFromBasesAndResources() (resmap.ResMap, error) {
|
||||
bases, errs := a.loadCustomizedBases()
|
||||
resources, err := resmap.NewResMapFromFiles(a.ldr, a.kustomization.Resources)
|
||||
if err != nil {
|
||||
errs.Append(errors.Wrap(err, "rawResources failed to read Resources"))
|
||||
}
|
||||
if len(errs.Get()) > 0 {
|
||||
return nil, errs
|
||||
}
|
||||
return resmap.MergeWithoutOverride(resources, bases)
|
||||
}
|
||||
|
||||
// Loop through the Bases of this kustomization recursively loading resources.
|
||||
// Combine into one ResMap, demanding unique Ids for each resource.
|
||||
func (a *Application) loadCustomizedBases() (resmap.ResMap, *interror.KustomizationErrors) {
|
||||
var list []resmap.ResMap
|
||||
errs := &interror.KustomizationErrors{}
|
||||
for _, path := range a.kustomization.Bases {
|
||||
ldr, err := a.ldr.New(path)
|
||||
if err != nil {
|
||||
errs.Append(errors.Wrap(err, "couldn't make ldr for "+path))
|
||||
continue
|
||||
}
|
||||
app, err := NewApplication(ldr, a.fSys)
|
||||
if err != nil {
|
||||
errs.Append(errors.Wrap(err, "couldn't make app for "+path))
|
||||
continue
|
||||
}
|
||||
resMap, err := app.loadCustomizedResMap()
|
||||
if err != nil {
|
||||
errs.Append(errors.Wrap(err, "SemiResources"))
|
||||
continue
|
||||
}
|
||||
ldr.Cleanup()
|
||||
list = append(list, resMap)
|
||||
}
|
||||
result, err := resmap.MergeWithoutOverride(list...)
|
||||
if err != nil {
|
||||
errs.Append(errors.Wrap(err, "Merge failed"))
|
||||
}
|
||||
return result, errs
|
||||
}
|
||||
|
||||
func (a *Application) loadBasesAsFlatList() ([]*Application, error) {
|
||||
var result []*Application
|
||||
errs := &interror.KustomizationErrors{}
|
||||
for _, path := range a.kustomization.Bases {
|
||||
ldr, err := a.ldr.New(path)
|
||||
if err != nil {
|
||||
errs.Append(err)
|
||||
continue
|
||||
}
|
||||
a, err := NewApplication(ldr, a.fSys)
|
||||
if err != nil {
|
||||
errs.Append(err)
|
||||
continue
|
||||
}
|
||||
result = append(result, a)
|
||||
}
|
||||
if len(errs.Get()) > 0 {
|
||||
return nil, errs
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// newTransformer makes a Transformer that does everything except resolve generated names.
|
||||
func (a *Application) newTransformer(patches []*resource.Resource) (transformers.Transformer, error) {
|
||||
var r []transformers.Transformer
|
||||
t, err := transformers.NewPatchTransformer(patches)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r = append(r, t)
|
||||
r = append(r, transformers.NewNamespaceTransformer(string(a.kustomization.Namespace)))
|
||||
t, err = transformers.NewDefaultingNamePrefixTransformer(string(a.kustomization.NamePrefix))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r = append(r, t)
|
||||
t, err = transformers.NewDefaultingLabelsMapTransformer(a.kustomization.CommonLabels)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r = append(r, t)
|
||||
t, err = transformers.NewDefaultingAnnotationsMapTransformer(a.kustomization.CommonAnnotations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r = append(r, t)
|
||||
return transformers.NewMultiTransformer(r), nil
|
||||
}
|
||||
|
||||
func (a *Application) resolveRefVars(m resmap.ResMap) (map[string]string, error) {
|
||||
result := map[string]string{}
|
||||
vars, err := a.getAllVars()
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
for _, v := range vars {
|
||||
id := resource.NewResId(v.ObjRef.GroupVersionKind(), v.ObjRef.Name)
|
||||
if r, found := m.DemandOneMatchForId(id); found {
|
||||
s, err := r.GetFieldValue(v.FieldRef.FieldPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to resolve referred var: %+v", v)
|
||||
}
|
||||
result[v.Name] = s
|
||||
} else {
|
||||
glog.Infof("couldn't resolve v: %v", v)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// getAllVars returns all the "environment" style Var instances defined in the app.
|
||||
func (a *Application) getAllVars() ([]types.Var, error) {
|
||||
var result []types.Var
|
||||
errs := &interror.KustomizationErrors{}
|
||||
|
||||
bases, err := a.loadBasesAsFlatList()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: computing vars and resources for bases can be combined
|
||||
for _, b := range bases {
|
||||
vars, err := b.getAllVars()
|
||||
if err != nil {
|
||||
errs.Append(err)
|
||||
continue
|
||||
}
|
||||
b.ldr.Cleanup()
|
||||
result = append(result, vars...)
|
||||
}
|
||||
for _, v := range a.kustomization.Vars {
|
||||
v.Defaulting()
|
||||
result = append(result, v)
|
||||
}
|
||||
if len(errs.Get()) > 0 {
|
||||
return nil, errs
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
@@ -1,263 +0,0 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"sigs.k8s.io/kustomize/pkg/constants"
|
||||
"sigs.k8s.io/kustomize/pkg/fs"
|
||||
"sigs.k8s.io/kustomize/pkg/internal/loadertest"
|
||||
"sigs.k8s.io/kustomize/pkg/loader"
|
||||
"sigs.k8s.io/kustomize/pkg/resmap"
|
||||
"sigs.k8s.io/kustomize/pkg/resource"
|
||||
)
|
||||
|
||||
const (
|
||||
kustomizationContent1 = `
|
||||
namePrefix: foo-
|
||||
namespace: ns1
|
||||
commonLabels:
|
||||
app: nginx
|
||||
commonAnnotations:
|
||||
note: This is a test annotation
|
||||
resources:
|
||||
- deployment.yaml
|
||||
- namespace.yaml
|
||||
configMapGenerator:
|
||||
- name: literalConfigMap
|
||||
literals:
|
||||
- DB_USERNAME=admin
|
||||
- DB_PASSWORD=somepw
|
||||
secretGenerator:
|
||||
- name: secret
|
||||
commands:
|
||||
DB_USERNAME: "printf admin"
|
||||
DB_PASSWORD: "printf somepw"
|
||||
type: Opaque
|
||||
patchesJson6902:
|
||||
- target:
|
||||
group: apps
|
||||
version: v1
|
||||
kind: Deployment
|
||||
name: dply1
|
||||
path: jsonpatch.json
|
||||
`
|
||||
kustomizationContent2 = `
|
||||
secretGenerator:
|
||||
- name: secret
|
||||
timeoutSeconds: 1
|
||||
commands:
|
||||
USER: "sleep 2"
|
||||
type: Opaque
|
||||
`
|
||||
deploymentContent = `apiVersion: apps/v1
|
||||
metadata:
|
||||
name: dply1
|
||||
kind: Deployment
|
||||
`
|
||||
namespaceContent = `apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: ns1
|
||||
`
|
||||
jsonpatchContent = `[
|
||||
{"op": "add", "path": "/spec/replica", "value": "3"}
|
||||
]`
|
||||
)
|
||||
|
||||
func makeLoader1(t *testing.T) loader.Loader {
|
||||
ldr := loadertest.NewFakeLoader("/testpath")
|
||||
err := ldr.AddFile("/testpath/"+constants.KustomizationFileName, []byte(kustomizationContent1))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to setup fake ldr.")
|
||||
}
|
||||
err = ldr.AddFile("/testpath/deployment.yaml", []byte(deploymentContent))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to setup fake ldr.")
|
||||
}
|
||||
err = ldr.AddFile("/testpath/namespace.yaml", []byte(namespaceContent))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to setup fake ldr.")
|
||||
}
|
||||
err = ldr.AddFile("/testpath/jsonpatch.json", []byte(jsonpatchContent))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to setup fake ldr.")
|
||||
}
|
||||
return ldr
|
||||
}
|
||||
|
||||
var deploy = schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}
|
||||
var cmap = schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}
|
||||
var secret = schema.GroupVersionKind{Version: "v1", Kind: "Secret"}
|
||||
var ns = schema.GroupVersionKind{Version: "v1", Kind: "Namespace"}
|
||||
|
||||
func TestResources1(t *testing.T) {
|
||||
expected := resmap.ResMap{
|
||||
resource.NewResIdWithPrefixNamespace(deploy, "dply1", "foo-", "ns1"): resource.NewResourceFromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "foo-dply1",
|
||||
"namespace": "ns1",
|
||||
"labels": map[string]interface{}{
|
||||
"app": "nginx",
|
||||
},
|
||||
"annotations": map[string]interface{}{
|
||||
"note": "This is a test annotation",
|
||||
},
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"replica": "3",
|
||||
"selector": map[string]interface{}{
|
||||
"matchLabels": map[string]interface{}{
|
||||
"app": "nginx",
|
||||
},
|
||||
},
|
||||
"template": map[string]interface{}{
|
||||
"metadata": map[string]interface{}{
|
||||
"annotations": map[string]interface{}{
|
||||
"note": "This is a test annotation",
|
||||
},
|
||||
"labels": map[string]interface{}{
|
||||
"app": "nginx",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
resource.NewResIdWithPrefixNamespace(cmap, "literalConfigMap", "foo-", "ns1"): resource.NewResourceFromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "foo-literalConfigMap-mc92bgcbh5",
|
||||
"namespace": "ns1",
|
||||
"labels": map[string]interface{}{
|
||||
"app": "nginx",
|
||||
},
|
||||
"annotations": map[string]interface{}{
|
||||
"note": "This is a test annotation",
|
||||
},
|
||||
"creationTimestamp": nil,
|
||||
},
|
||||
"data": map[string]interface{}{
|
||||
"DB_USERNAME": "admin",
|
||||
"DB_PASSWORD": "somepw",
|
||||
},
|
||||
}).SetBehavior(resource.BehaviorCreate),
|
||||
resource.NewResIdWithPrefixNamespace(secret, "secret", "foo-", "ns1"): resource.NewResourceFromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Secret",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "foo-secret-877fcfhgt5",
|
||||
"namespace": "ns1",
|
||||
"labels": map[string]interface{}{
|
||||
"app": "nginx",
|
||||
},
|
||||
"annotations": map[string]interface{}{
|
||||
"note": "This is a test annotation",
|
||||
},
|
||||
"creationTimestamp": nil,
|
||||
},
|
||||
"type": string(corev1.SecretTypeOpaque),
|
||||
"data": map[string]interface{}{
|
||||
"DB_USERNAME": base64.StdEncoding.EncodeToString([]byte("admin")),
|
||||
"DB_PASSWORD": base64.StdEncoding.EncodeToString([]byte("somepw")),
|
||||
},
|
||||
}).SetBehavior(resource.BehaviorCreate),
|
||||
resource.NewResIdWithPrefixNamespace(ns, "ns1", "foo-", ""): resource.NewResourceFromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Namespace",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "foo-ns1",
|
||||
"labels": map[string]interface{}{
|
||||
"app": "nginx",
|
||||
},
|
||||
"annotations": map[string]interface{}{
|
||||
"note": "This is a test annotation",
|
||||
},
|
||||
},
|
||||
}),
|
||||
}
|
||||
l := makeLoader1(t)
|
||||
fakeFs := fs.MakeFakeFS()
|
||||
fakeFs.Mkdir("/")
|
||||
app, err := NewApplication(l, fakeFs)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected construction error %v", err)
|
||||
}
|
||||
actual, err := app.MakeCustomizedResMap()
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Resources error %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
err = expected.ErrorIfNotEqual(actual)
|
||||
t.Fatalf("unexpected inequality: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceNotFound(t *testing.T) {
|
||||
l := loadertest.NewFakeLoader("/testpath")
|
||||
err := l.AddFile("/testpath/"+constants.KustomizationFileName, []byte(kustomizationContent1))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to setup fake ldr.")
|
||||
}
|
||||
fakeFs := fs.MakeFakeFS()
|
||||
fakeFs.Mkdir("/")
|
||||
app, err := NewApplication(l, fakeFs)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected construction error %v", err)
|
||||
}
|
||||
_, err = app.MakeCustomizedResMap()
|
||||
if err == nil {
|
||||
t.Fatalf("Didn't get the expected error for an unknown resource")
|
||||
}
|
||||
if !strings.Contains(err.Error(), `cannot read file "/testpath/deployment.yaml"`) {
|
||||
t.Fatalf("Unpexpected error message %q", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecretTimeout(t *testing.T) {
|
||||
l := loadertest.NewFakeLoader("/testpath")
|
||||
err := l.AddFile("/testpath/"+constants.KustomizationFileName, []byte(kustomizationContent2))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to setup fake ldr.")
|
||||
}
|
||||
fakeFs := fs.MakeFakeFS()
|
||||
fakeFs.Mkdir("/")
|
||||
app, err := NewApplication(l, fakeFs)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected construction error %v", err)
|
||||
}
|
||||
_, err = app.MakeCustomizedResMap()
|
||||
if err == nil {
|
||||
t.Fatalf("Didn't get the expected error for an unknown resource")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "killed") {
|
||||
t.Fatalf("Unpexpected error message %q", err)
|
||||
}
|
||||
}
|
||||
@@ -1,175 +0,0 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"sigs.k8s.io/kustomize/pkg/constants"
|
||||
"sigs.k8s.io/kustomize/pkg/fs"
|
||||
"sigs.k8s.io/kustomize/pkg/types"
|
||||
"sigs.k8s.io/kustomize/pkg/validators"
|
||||
)
|
||||
|
||||
// kindOfAdd is the kind of metadata being added: label or annotation
|
||||
type kindOfAdd int
|
||||
|
||||
const (
|
||||
annotation kindOfAdd = iota
|
||||
label
|
||||
)
|
||||
|
||||
func (k kindOfAdd) String() string {
|
||||
kinds := [...]string{
|
||||
"annotation",
|
||||
"label",
|
||||
}
|
||||
if k < 0 || k > 1 {
|
||||
return "Unknown metadatakind"
|
||||
}
|
||||
return kinds[k]
|
||||
}
|
||||
|
||||
type addMetadataOptions struct {
|
||||
metadata map[string]string
|
||||
mapValidator validators.MapValidatorFunc
|
||||
kind kindOfAdd
|
||||
}
|
||||
|
||||
// newCmdAddAnnotation adds one or more commonAnnotations to the kustomization file.
|
||||
func newCmdAddAnnotation(fSys fs.FileSystem, v validators.MapValidatorFunc) *cobra.Command {
|
||||
var o addMetadataOptions
|
||||
o.kind = annotation
|
||||
o.mapValidator = v
|
||||
cmd := &cobra.Command{
|
||||
Use: "annotation",
|
||||
Short: "Adds one or more commonAnnotations to " + constants.KustomizationFileName,
|
||||
Example: `
|
||||
add annotation {annotationKey1:annotationValue1},{annotationKey2:annotationValue2}`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return o.runE(args, fSys, o.addAnnotations)
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
// newCmdAddLabel adds one or more commonLabels to the kustomization file.
|
||||
func newCmdAddLabel(fSys fs.FileSystem, v validators.MapValidatorFunc) *cobra.Command {
|
||||
var o addMetadataOptions
|
||||
o.kind = label
|
||||
o.mapValidator = v
|
||||
cmd := &cobra.Command{
|
||||
Use: "label",
|
||||
Short: "Adds one or more commonLabels to " + constants.KustomizationFileName,
|
||||
Example: `
|
||||
add label {labelKey1:labelValue1},{labelKey2:labelValue2}`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return o.runE(args, fSys, o.addLabels)
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o *addMetadataOptions) runE(
|
||||
args []string, fSys fs.FileSystem, adder func(*types.Kustomization) error) error {
|
||||
err := o.validateAndParse(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
kf, err := newKustomizationFile(constants.KustomizationFileName, fSys)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m, err := kf.read()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = adder(m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return kf.write(m)
|
||||
}
|
||||
|
||||
// validateAndParse validates `add` commands and parses them into o.metadata
|
||||
func (o *addMetadataOptions) validateAndParse(args []string) error {
|
||||
if len(args) < 1 {
|
||||
return fmt.Errorf("must specify %s", o.kind)
|
||||
}
|
||||
if len(args) > 1 {
|
||||
return fmt.Errorf("%ss must be comma-separated, with no spaces", o.kind)
|
||||
}
|
||||
m, err := o.convertToMap(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = o.mapValidator(m); err != nil {
|
||||
return err
|
||||
}
|
||||
o.metadata = m
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *addMetadataOptions) convertToMap(arg string) (map[string]string, error) {
|
||||
result := make(map[string]string)
|
||||
inputs := strings.Split(arg, ",")
|
||||
for _, input := range inputs {
|
||||
kv := strings.Split(input, ":")
|
||||
if len(kv[0]) < 1 {
|
||||
return nil, o.makeError(input, "empty key")
|
||||
}
|
||||
if len(kv) > 2 {
|
||||
return nil, o.makeError(input, "too many colons")
|
||||
}
|
||||
if len(kv) > 1 {
|
||||
result[kv[0]] = kv[1]
|
||||
} else {
|
||||
result[kv[0]] = ""
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (o *addMetadataOptions) addAnnotations(m *types.Kustomization) error {
|
||||
if m.CommonAnnotations == nil {
|
||||
m.CommonAnnotations = make(map[string]string)
|
||||
}
|
||||
return o.writeToMap(m.CommonAnnotations, annotation)
|
||||
}
|
||||
|
||||
func (o *addMetadataOptions) addLabels(m *types.Kustomization) error {
|
||||
if m.CommonLabels == nil {
|
||||
m.CommonLabels = make(map[string]string)
|
||||
}
|
||||
return o.writeToMap(m.CommonLabels, label)
|
||||
}
|
||||
|
||||
func (o *addMetadataOptions) writeToMap(m map[string]string, kind kindOfAdd) error {
|
||||
for k, v := range o.metadata {
|
||||
if _, ok := m[k]; ok {
|
||||
return fmt.Errorf("%s %s already in kustomization file", kind, k)
|
||||
}
|
||||
m[k] = v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *addMetadataOptions) makeError(input string, message string) error {
|
||||
return fmt.Errorf("invalid %s: %s (%s)", o.kind, input, message)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user