mirror of
https://github.com/helmfile/helmfile.git
synced 2026-10-22 14:32:27 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5650661a88 | ||
|
|
6b31b66473 | ||
|
|
c17317100b | ||
|
|
734957238d | ||
|
|
10d640b211 | ||
|
|
d276cd2902 | ||
|
|
5e8a502b41 | ||
|
|
bfb94314be | ||
|
|
198b6e173a | ||
|
|
d34cfaafa1 | ||
|
|
a817997ed8 | ||
|
|
011066f653 | ||
|
|
063ba86447 | ||
|
|
2d9f83c1de | ||
|
|
5cdec2dd51 | ||
|
|
ccc69399e5 | ||
|
|
400488d3ae | ||
|
|
87eaa851e7 | ||
|
|
c21958557c | ||
|
|
939ae409c3 | ||
|
|
95c56d87fc | ||
|
|
522392c08c | ||
|
|
a5c4bea54a | ||
|
|
d56dfeb5e4 | ||
|
|
6607d12cac | ||
|
|
68d6b2d885 | ||
|
|
2c2bfcf58a | ||
|
|
489a3ae133 | ||
|
|
dfe835a9c4 | ||
|
|
d0ef31e10b | ||
|
|
77d3aee6f7 | ||
|
|
f629ee1ae6 | ||
|
|
9d74fa3513 | ||
|
|
ebc56f3aff | ||
|
|
99e85bb672 | ||
|
|
cb50fa932c | ||
|
|
a8574ba0cf | ||
|
|
37c213210e | ||
|
|
65eca3337e | ||
|
|
2d98bba1a1 | ||
|
|
d443e7424e | ||
|
|
8f2d97a1b3 | ||
|
|
e930deffbc | ||
|
|
0e89ceb0f0 | ||
|
|
8c130a4fa7 | ||
|
|
fc027d1538 | ||
|
|
b359efef8b | ||
|
|
4ce8676588 | ||
|
|
59f32512fd | ||
|
|
652628c5ee | ||
|
|
9aadde6173 | ||
|
|
04fc38f233 | ||
|
|
f355b297be | ||
|
|
d263b99b44 | ||
|
|
5190d0b62b | ||
|
|
daa77b24c0 | ||
|
|
1ccdf83cd5 | ||
|
|
d164a4f0bb | ||
|
|
518ab98c1c | ||
|
|
abbdb6950b | ||
|
|
8bea5aee3e | ||
|
|
bc6317122b | ||
|
|
652b609b35 | ||
|
|
b44aaf90f7 | ||
|
|
ec60ac815b | ||
|
|
60e024ee3b | ||
|
|
543629f335 | ||
|
|
cff674b7e9 | ||
|
|
1664edd0b9 | ||
|
|
f3a24dd99a | ||
|
|
a724ab73f6 | ||
|
|
44b473ebe8 | ||
|
|
c3a9d45b55 | ||
|
|
fc634c9ee3 | ||
|
|
c4eb62388b | ||
|
|
1f134d931f | ||
|
|
8d96bbb0e4 | ||
|
|
db5c2a52d6 | ||
|
|
fdfa520f1b | ||
|
|
982795acb3 | ||
|
|
4688cf0132 | ||
|
|
490bb5d147 | ||
|
|
6b19577987 | ||
|
|
d8cb740dcf | ||
|
|
6664f01596 | ||
|
|
50fd0d786e | ||
|
|
c83ba69f3e | ||
|
|
7860bddedc | ||
|
|
8144638bab | ||
|
|
eb810c6c0c | ||
|
|
d8d0bf830a | ||
|
|
8ec51c2826 | ||
|
|
8ce539f4de | ||
|
|
36c91c5427 | ||
|
|
9cc0cff07f | ||
|
|
7410316cf9 | ||
|
|
17b03c22ec | ||
|
|
0a0640affd | ||
|
|
ecc8988f10 | ||
|
|
5da1200a8b | ||
|
|
524003d80b | ||
|
|
7860ac988a | ||
|
|
a93035a3e5 | ||
|
|
89d9a9b3a2 | ||
|
|
773b0d6fc4 | ||
|
|
04729b027a | ||
|
|
a3e8c0426c | ||
|
|
9f702280ae | ||
|
|
9b0eeaad94 | ||
|
|
d0915641ca | ||
|
|
c2e7804479 | ||
|
|
608bb0b525 | ||
|
|
197fdcdabd | ||
|
|
c744046f88 | ||
|
|
73134d8a70 | ||
|
|
0a953731b0 | ||
|
|
1f0f7ec8d6 | ||
|
|
4cc07daced | ||
|
|
7be64f29cf | ||
|
|
bdc6982172 | ||
|
|
2a18134986 | ||
|
|
2f2a74d28f | ||
|
|
195cf774a3 | ||
|
|
ad7fcbb0e9 | ||
|
|
a6df2f8148 | ||
|
|
e173a7b64f | ||
|
|
3f4673aa95 | ||
|
|
94381c1e17 | ||
|
|
75aab769c8 | ||
|
|
70a1b3b513 | ||
|
|
398c812e49 | ||
|
|
f7b3fb4432 | ||
|
|
28ba777d5f | ||
|
|
b76f1d37d7 | ||
|
|
ba2b68fd18 | ||
|
|
354f9097cf | ||
|
|
1f6ef3edc1 | ||
|
|
1c77bc5cfb | ||
|
|
9b1312aaee | ||
|
|
02dc32aa2b | ||
|
|
23aa8ce46f | ||
|
|
04679b4b90 | ||
|
|
5a29137572 | ||
|
|
63bbcf8930 | ||
|
|
5262145f6e | ||
|
|
01aee9337f | ||
|
|
447481ae83 | ||
|
|
211852cd38 | ||
|
|
60621ff318 | ||
|
|
181c2f4509 | ||
|
|
44c26e6639 | ||
|
|
36996df980 | ||
|
|
bd84b51fba | ||
|
|
54d987dd75 | ||
|
|
5c5dc42ef5 | ||
|
|
7844145ee3 | ||
|
|
ce7f38ee96 | ||
|
|
766c1c951f | ||
|
|
85f577e551 | ||
|
|
a05ccab6c3 | ||
|
|
1ba83ae722 | ||
|
|
bdbfc9106d | ||
|
|
8d2aec249d | ||
|
|
235917c11c | ||
|
|
445c74c92b | ||
|
|
6b69b31754 | ||
|
|
f09ec18aa9 | ||
|
|
143f85b4f1 | ||
|
|
667c3723f0 | ||
|
|
844f6e9583 | ||
|
|
0f44cfacc4 | ||
|
|
2594dc1524 | ||
|
|
c9db278df7 | ||
|
|
8c870dc444 | ||
|
|
2324188695 | ||
|
|
cd92b4185e | ||
|
|
8f59a1c18a | ||
|
|
712ee3a0a2 | ||
|
|
34c5adb1da | ||
|
|
3c907e2c2b | ||
|
|
1513035718 | ||
|
|
831410c814 | ||
|
|
b0bb03a0ea | ||
|
|
faf3444761 | ||
|
|
ce6f8fc9ec | ||
|
|
bc3528ae09 | ||
|
|
120ff7cce6 | ||
|
|
82004b53f9 | ||
|
|
6dcde20d7a | ||
|
|
008e92de37 | ||
|
|
5f2b6994a6 | ||
|
|
e919b4b1d2 | ||
|
|
3ac0cee50a | ||
|
|
414f899c28 | ||
|
|
2cf6501d1f | ||
|
|
2e7261426a | ||
|
|
9704180862 | ||
|
|
4c103073bc | ||
|
|
d54104d6ec | ||
|
|
dcfc31eca6 | ||
|
|
2702161e74 | ||
|
|
4a5928e269 | ||
|
|
7d05ffd098 | ||
|
|
d4b1b078cf | ||
|
|
f8a297bbac | ||
|
|
9d28e28086 | ||
|
|
83868d8db4 | ||
|
|
dba05f1fdf | ||
|
|
6be3c11119 | ||
|
|
39d0371d15 | ||
|
|
bc255f3e51 | ||
|
|
a409b450cd | ||
|
|
f2be4861d5 | ||
|
|
420f5b2079 | ||
|
|
94cc07cdf2 | ||
|
|
38fffad737 | ||
|
|
62cbeb661d | ||
|
|
61233badd5 | ||
|
|
da2ce574b0 | ||
|
|
ed228c9c7e | ||
|
|
82f0d88d56 | ||
|
|
48c1e34a07 | ||
|
|
bdaafdff67 | ||
|
|
ffce09a35f | ||
|
|
97ce31d7c8 | ||
|
|
b77ccd844e | ||
|
|
62f0d722cd | ||
|
|
b7da0965f1 | ||
|
|
ef115ba653 | ||
|
|
85bfe64b90 | ||
|
|
caee32f7be | ||
|
|
a2d5d82cb6 | ||
|
|
3e2285ea72 | ||
|
|
f7f858cc52 | ||
|
|
95065f8d28 | ||
|
|
2ff0969e3b | ||
|
|
3a5d9efb20 | ||
|
|
111a248523 | ||
|
|
c2ea0f0355 | ||
|
|
ff107103da | ||
|
|
e001f6f05d | ||
|
|
c762310a5f | ||
|
|
31cd729fd4 | ||
|
|
31da58fa3c | ||
|
|
20d321ca16 | ||
|
|
7c98dbb20c | ||
|
|
0c205771ce | ||
|
|
f483a09879 | ||
|
|
6a36f34c7d | ||
|
|
ad257554c9 | ||
|
|
d1c31e8c17 | ||
|
|
21c28ca6d0 | ||
|
|
8f7796b260 | ||
|
|
144964002e | ||
|
|
8f0993a081 | ||
|
|
e06abe4620 | ||
|
|
a8101e44c7 | ||
|
|
422852dd9a | ||
|
|
b5d54d6921 | ||
|
|
bffa048dc6 | ||
|
|
e6b263340d | ||
|
|
e92c71f9d8 | ||
|
|
65cc3741d2 | ||
|
|
69d77507ee | ||
|
|
5f699656df | ||
|
|
c0a1ec46dc | ||
|
|
f15bdbbb0c | ||
|
|
3165551f9a | ||
|
|
68728ef352 | ||
|
|
a42c14e640 | ||
|
|
b347eec6f2 | ||
|
|
5367fbe549 | ||
|
|
f6fd754386 | ||
|
|
14ba7cd156 | ||
|
|
d169786770 | ||
|
|
bef8aaceee | ||
|
|
636f2f130c | ||
|
|
6862c362b4 | ||
|
|
22f0d7b8a0 | ||
|
|
de1aa61693 | ||
|
|
e9fadfc868 | ||
|
|
5f05bb01ca | ||
|
|
0e04546880 | ||
|
|
06cfdfc891 | ||
|
|
2a7669f8fa | ||
|
|
1328d24452 | ||
|
|
da60e90bc0 | ||
|
|
85f050d2e5 | ||
|
|
6408ff1319 | ||
|
|
a36f07cda3 | ||
|
|
7cf010bb15 | ||
|
|
d94f740f45 | ||
|
|
466661c223 | ||
|
|
0a1b6c0907 | ||
|
|
7f3076a054 | ||
|
|
3c151b8d45 | ||
|
|
1f4af59c5f | ||
|
|
e4d36e44fb | ||
|
|
c90cace43b | ||
|
|
6aeb6b38ba | ||
|
|
24810a45ae | ||
|
|
bb13ef68d7 | ||
|
|
385c3e80ef | ||
|
|
dc40ccde2e | ||
|
|
793050cc18 | ||
|
|
9e673ca902 | ||
|
|
5b1606df75 | ||
|
|
3c0456c577 | ||
|
|
5b88006e86 | ||
|
|
1e9cce7d36 | ||
|
|
f0b76e9e26 | ||
|
|
fcdd852153 | ||
|
|
ea56061609 | ||
|
|
ca7942c751 | ||
|
|
eb2419aa15 | ||
|
|
c6e8afd3d0 | ||
|
|
32477e6cf2 | ||
|
|
db81f18095 | ||
|
|
1a3c11dffd | ||
|
|
4e5987d833 | ||
|
|
9019cec4ac | ||
|
|
a1c8926a86 | ||
|
|
fa8dcc2c8f | ||
|
|
0341027bc0 | ||
|
|
7b40cefdda | ||
|
|
e8a7e23d4d | ||
|
|
18eee1fa47 | ||
|
|
7d88370310 | ||
|
|
2663ac4f1b | ||
|
|
e1ca846772 | ||
|
|
b8cf0f156e | ||
|
|
d4e4d10fa1 | ||
|
|
24cefc7fd5 | ||
|
|
8408b021f0 | ||
|
|
c828d22a5c | ||
|
|
0fbcb07bad | ||
|
|
3ff98c02a1 | ||
|
|
99d5eaf25b | ||
|
|
ade8e81cc1 | ||
|
|
9fe54c72d7 | ||
|
|
76bf9952b9 | ||
|
|
bf323b4021 | ||
|
|
98ca036d99 | ||
|
|
deca47087f | ||
|
|
7eb23296e1 | ||
|
|
ca9bb9bcd2 | ||
|
|
68995bd34b | ||
|
|
9cd2c99286 | ||
|
|
f37289e3e1 | ||
|
|
d8ec42c48f | ||
|
|
9ef075a249 | ||
|
|
ac0bdb8052 | ||
|
|
700d708b29 | ||
|
|
af52c960f4 | ||
|
|
4dd73d4efb | ||
|
|
1838ec0a11 | ||
|
|
ebf0f370a1 | ||
|
|
3b890056e3 | ||
|
|
cb780be6bd | ||
|
|
f3788249e4 | ||
|
|
8b0ad72e77 | ||
|
|
d9ee3afd6f | ||
|
|
a56303b5bf | ||
|
|
822b7b2a9b | ||
|
|
f3984d5be5 | ||
|
|
8b326fb1c3 | ||
|
|
56e83dc8a8 | ||
|
|
18d7d7561d | ||
|
|
50736aaefe | ||
|
|
de567ac0f7 | ||
|
|
ab12e5ef2d | ||
|
|
86072ca200 | ||
|
|
9284d1764e | ||
|
|
896d16566f | ||
|
|
cbace3d7af | ||
|
|
43b52f6040 | ||
|
|
b89c24cd8a | ||
|
|
107c2c3458 | ||
|
|
d201c1d2ea | ||
|
|
7434872c6b | ||
|
|
ece0387fc6 | ||
|
|
630919e8f2 | ||
|
|
e0d7b98075 | ||
|
|
01f896ced6 | ||
|
|
cc33e7b7d8 | ||
|
|
a626664f46 | ||
|
|
5d5d61e56f | ||
|
|
316f7ddf5d | ||
|
|
3984659133 | ||
|
|
429e724104 | ||
|
|
7972a8921c | ||
|
|
d28f16253f | ||
|
|
b616a8b690 | ||
|
|
86deeb377d | ||
|
|
be9e88d3b5 | ||
|
|
14e277f683 | ||
|
|
c26bc2e26e | ||
|
|
c9b5f95890 | ||
|
|
4553efdcf1 | ||
|
|
684be0af50 | ||
|
|
824f7ba2b2 | ||
|
|
e44a915d40 | ||
|
|
295d54d838 | ||
|
|
1d882697d9 | ||
|
|
3986f81ff8 | ||
|
|
211b8efebb | ||
|
|
8d798e69f1 | ||
|
|
bb056a4ffc | ||
|
|
89950f3794 | ||
|
|
94b7a6c128 | ||
|
|
cf94a4edb3 | ||
|
|
8c4a122be4 | ||
|
|
75aa0e5a21 | ||
|
|
8690d63401 | ||
|
|
18942e2a2c | ||
|
|
f309dc3332 | ||
|
|
6c1b6f855a | ||
|
|
bbf790247b | ||
|
|
86bfb5689a | ||
|
|
13bac519d0 | ||
|
|
2cb4bdba1a | ||
|
|
eabf360fe8 | ||
|
|
5d0a5671f2 | ||
|
|
6a37ae9650 | ||
|
|
7488d7513e | ||
|
|
8beb69d08e | ||
|
|
005ce66afa | ||
|
|
5ac1e1fb98 | ||
|
|
913a22e9aa | ||
|
|
329ec766fe | ||
|
|
a405a52609 | ||
|
|
9af8f1286a | ||
|
|
68d7c5f6a4 | ||
|
|
4e9b99d10e | ||
|
|
59ffa687df | ||
|
|
70d2dd653b | ||
|
|
cf62499d4a | ||
|
|
72579d9386 | ||
|
|
70fd920384 | ||
|
|
6dc1736f15 | ||
|
|
7417da6d98 | ||
|
|
a97ff87fa1 | ||
|
|
a4dee77430 | ||
|
|
d27765148e | ||
|
|
098684c136 | ||
|
|
5e0368451e | ||
|
|
85ade797ab | ||
|
|
35453566df | ||
|
|
cdea0ff080 | ||
|
|
3a5d4cb62f | ||
|
|
ac1edafe9a | ||
|
|
93b1ac2b19 | ||
|
|
48ae800f79 | ||
|
|
eb0415b139 | ||
|
|
9884e4fdb1 | ||
|
|
a4b914abc7 | ||
|
|
7141003106 | ||
|
|
b3f4b78cd9 | ||
|
|
122cb25b6a | ||
|
|
866f424578 | ||
|
|
f00a8194e0 | ||
|
|
ac23def893 | ||
|
|
cc13492623 | ||
|
|
1d61d7d3e7 | ||
|
|
95c444a6cd | ||
|
|
4e49ed9a1f | ||
|
|
9e3f0a3ba3 | ||
|
|
008f4906aa | ||
|
|
65fae9c52c | ||
|
|
a93661d382 | ||
|
|
de6a9aeebf | ||
|
|
c1eb7f0623 | ||
|
|
74c1f9e9a0 | ||
|
|
f0662f2ebe | ||
|
|
fba8b63eac | ||
|
|
603d305194 | ||
|
|
96174e4229 | ||
|
|
9179cfb55f | ||
|
|
31d3d3fad7 | ||
|
|
1e5d309e68 | ||
|
|
9b0293d9ea | ||
|
|
ae5e93d71d | ||
|
|
e106d105ce | ||
|
|
a81c8fa4e1 | ||
|
|
ce3b81e2a1 | ||
|
|
a5b7c2b08d | ||
|
|
bf3ce285ec | ||
|
|
e0e49813f2 | ||
|
|
367b5bbc9b | ||
|
|
5bd5f72170 | ||
|
|
c9cc2556be | ||
|
|
c85a085e4d | ||
|
|
9671bfb2ab | ||
|
|
505fff245c | ||
|
|
b4b0763cdf | ||
|
|
219671b54c | ||
|
|
fae23fe380 | ||
|
|
91aea33350 | ||
|
|
26e0a4a482 | ||
|
|
3f48b295d3 | ||
|
|
1996304880 | ||
|
|
3bf4744049 | ||
|
|
7cc23f1ee8 | ||
|
|
fc306ec3d1 | ||
|
|
3e60f72d1f | ||
|
|
092c4ed671 | ||
|
|
4fa305c38a | ||
|
|
724c8eea09 | ||
|
|
2230022ad0 | ||
|
|
7183bc5044 | ||
|
|
a6f1d7c8cf | ||
|
|
21b2248d19 | ||
|
|
e642094ead | ||
|
|
1da88b829c | ||
|
|
45283bfcde | ||
|
|
ec1661c7ac | ||
|
|
d4160ebc59 | ||
|
|
fb8a7f0bf4 | ||
|
|
4220f2e3a9 | ||
|
|
7a63f8b031 | ||
|
|
261b311af6 | ||
|
|
0948bbae1c | ||
|
|
4071f6aa99 | ||
|
|
e5e8309ea6 | ||
|
|
ef812d1227 | ||
|
|
9abcc1f413 | ||
|
|
e182e9e775 | ||
|
|
c897a10dce | ||
|
|
fef447ddec | ||
|
|
bb20fcaf5b | ||
|
|
9032395818 | ||
|
|
214d06e5cd | ||
|
|
6ea940e50f | ||
|
|
bf9eeba544 | ||
|
|
173767d2c5 | ||
|
|
83a7245d0f | ||
|
|
58721ab1fc | ||
|
|
6e5a726ae5 | ||
|
|
414db47428 | ||
|
|
577f54af7a | ||
|
|
789af92c09 | ||
|
|
b6f3972381 | ||
|
|
aaf2126f91 | ||
|
|
7c34e2e1b7 | ||
|
|
b986b3de9f | ||
|
|
871f2a9ed3 | ||
|
|
8024949d48 | ||
|
|
e73fb21a84 | ||
|
|
f8597c564e | ||
|
|
736edf89fe | ||
|
|
9580c8863d | ||
|
|
73b7243f94 | ||
|
|
86eff8e2ed | ||
|
|
6ca79ef7f1 | ||
|
|
a92c574a98 | ||
|
|
0cfef76f0d | ||
|
|
0c57129aab | ||
|
|
f16001e61a | ||
|
|
070ed0dd47 | ||
|
|
9011d2dd15 | ||
|
|
c34c7c87a1 | ||
|
|
23b6429f33 | ||
|
|
33c66a6f46 | ||
|
|
70b874a596 | ||
|
|
80457d964c | ||
|
|
a993b2af3f | ||
|
|
c32689e779 | ||
|
|
8a2fe28bbf | ||
|
|
2e58c01ced | ||
|
|
26b4be3b5d | ||
|
|
30f53b914e | ||
|
|
3b27243ec6 | ||
|
|
d9143f55ce | ||
|
|
7e2462ed62 | ||
|
|
281fe9bf59 | ||
|
|
e67203ac9d | ||
|
|
eddb9eec45 | ||
|
|
d10a64ccf6 | ||
|
|
eace00f3db | ||
|
|
eb3484d4a8 | ||
|
|
dec10eb9d9 | ||
|
|
3978767ca7 | ||
|
|
62747378ca | ||
|
|
10c3eb61a5 | ||
|
|
d83e9214e6 | ||
|
|
2dff652862 | ||
|
|
af497ae73a | ||
|
|
d5f37b0e35 | ||
|
|
4959c5bdde | ||
|
|
3f14cb1d00 | ||
|
|
35bd7c1349 | ||
|
|
dccaa6ac4f | ||
|
|
f7ef07ff94 | ||
|
|
a320fccdd2 | ||
|
|
ad4039e2bc | ||
|
|
a942f12d36 | ||
|
|
490fa01247 | ||
|
|
7bc8ae585a | ||
|
|
a4c2a341bc | ||
|
|
ba5bea17d7 | ||
|
|
1258c9ed10 | ||
|
|
aed75091fa | ||
|
|
d2498d10cc | ||
|
|
7d74ebb16e | ||
|
|
21b9cc027c | ||
|
|
29d40d9d9c | ||
|
|
f93647be60 | ||
|
|
78de70349a | ||
|
|
341d81a3f3 | ||
|
|
f384fa8819 | ||
|
|
62653032eb | ||
|
|
548e66a118 | ||
|
|
e06e7f6d7f | ||
|
|
1adcbc152c | ||
|
|
d018b581a9 | ||
|
|
fda29a639f | ||
|
|
761b12e120 | ||
|
|
34a431f5f2 | ||
|
|
faef77d143 | ||
|
|
cc6edc18b6 | ||
|
|
e4f57718e1 | ||
|
|
218d7a6f0a | ||
|
|
eeb2eee099 | ||
|
|
e2cb7fe176 | ||
|
|
85671c4dc1 | ||
|
|
04d364a467 | ||
|
|
aef233489f | ||
|
|
45c983a0bb | ||
|
|
1bcf6a7bb1 | ||
|
|
33f733026b | ||
|
|
e0a618d0cc | ||
|
|
df6ec3a82e | ||
|
|
e1d8cea8cc | ||
|
|
617121e761 | ||
|
|
b9a69696ac | ||
|
|
8304d49cc3 | ||
|
|
c6920363d1 | ||
|
|
d26f83c460 | ||
|
|
fc3f468a81 | ||
|
|
88f17e6c9a | ||
|
|
8b15272cbc | ||
|
|
eee5f834c6 | ||
|
|
feb2bc9589 | ||
|
|
f8f50c8bcd | ||
|
|
a0d4047cc7 | ||
|
|
9e9567ed7d | ||
|
|
b895f75198 | ||
|
|
21ab18b906 | ||
|
|
42ad7beb6e | ||
|
|
a4542265ca | ||
|
|
dad58533b5 | ||
|
|
86c9a1ae3d | ||
|
|
f84ab66ba0 | ||
|
|
47e13fcdad | ||
|
|
7af57add9d | ||
|
|
d0841c72aa | ||
|
|
b5480123a2 | ||
|
|
f89234e5dc | ||
|
|
12e103d510 | ||
|
|
ec648c4011 | ||
|
|
c7b23a67cb | ||
|
|
3cae3f4f89 | ||
|
|
88127d641b | ||
|
|
9c0c021dc9 | ||
|
|
7743f317d1 | ||
|
|
f1a250cab3 | ||
|
|
89a00ca3e3 | ||
|
|
b672af6c06 | ||
|
|
a4555f34ff | ||
|
|
570a2c22e9 | ||
|
|
97e0ca73ca | ||
|
|
8e50dbc46d | ||
|
|
8fb418e3c9 | ||
|
|
f49d181b41 | ||
|
|
f7eb4a9e3e | ||
|
|
3f1d01a688 | ||
|
|
e8212276d5 | ||
|
|
03cbf5f9f4 | ||
|
|
a793797404 | ||
|
|
eed489efc1 | ||
|
|
5cb82ec3d1 | ||
|
|
4247b97eb1 | ||
|
|
4480ec2cdc | ||
|
|
7cc9778a13 | ||
|
|
8e5c779626 | ||
|
|
b2798315b1 | ||
|
|
a1dfcb3480 | ||
|
|
02058cc1f5 | ||
|
|
4a52ce0ac2 | ||
|
|
e0244bc341 | ||
|
|
01b5b79a01 | ||
|
|
7f70138b5f | ||
|
|
ed27147cac | ||
|
|
137afa3f35 | ||
|
|
6c258af4b6 | ||
|
|
31c68607ac | ||
|
|
4bafeab2fe | ||
|
|
4567a643a5 | ||
|
|
81cc843710 | ||
|
|
303ef9cd80 | ||
|
|
1aa87538a7 | ||
|
|
11ab0cb8c8 | ||
|
|
e561e9bc06 | ||
|
|
3267484f47 | ||
|
|
498a976efb | ||
|
|
f6fac3ff32 | ||
|
|
19ba9d2c4d | ||
|
|
1e41817c63 | ||
|
|
d4a7b3bfff | ||
|
|
be5af8e3b7 | ||
|
|
ed436ba68b | ||
|
|
45be24da53 | ||
|
|
4716320d99 | ||
|
|
9b621b3d21 | ||
|
|
a8784a17ed | ||
|
|
cabaf15a1a | ||
|
|
04c379b284 | ||
|
|
742391357a | ||
|
|
f65d1e6b05 | ||
|
|
185bc82992 | ||
|
|
b1928e585d | ||
|
|
dbc40f68b8 | ||
|
|
0105ed4df3 | ||
|
|
1d4d67fd56 | ||
|
|
700a89f3ff | ||
|
|
2f04831817 | ||
|
|
9a2df3c0a2 | ||
|
|
baafe702b2 | ||
|
|
242e601898 | ||
|
|
cf02442591 | ||
|
|
766b03047c | ||
|
|
19927fc147 | ||
|
|
061644c5d0 | ||
|
|
1d70130ab9 | ||
|
|
debd3c0eea | ||
|
|
87e5903705 | ||
|
|
9e9a90f8ef | ||
|
|
c069fbf268 | ||
|
|
346e318fd0 | ||
|
|
753de35ee0 | ||
|
|
16f19613fb | ||
|
|
8bdf377be0 | ||
|
|
9e58082342 | ||
|
|
4d01eddd3d | ||
|
|
91aa0acddb | ||
|
|
1735fb5bed | ||
|
|
fe8a176db5 | ||
|
|
94f99536c6 | ||
|
|
578c559640 | ||
|
|
b101f9ff8d | ||
|
|
170e29a558 | ||
|
|
554cbc19f5 | ||
|
|
d34dc7bb64 | ||
|
|
1986cb37aa | ||
|
|
9efb7afb47 | ||
|
|
3d7b4287b3 | ||
|
|
ca6dd7a60f | ||
|
|
9354c38a0a | ||
|
|
c0668874e3 | ||
|
|
77e6268bcb | ||
|
|
9a0ce53608 | ||
|
|
787e6c80d1 | ||
|
|
724b4b36f3 | ||
|
|
2792ef75d1 | ||
|
|
f57b510d01 | ||
|
|
7845cccd23 | ||
|
|
e1cabc82e3 | ||
|
|
0f91f2c9cf | ||
|
|
8594944f63 | ||
|
|
ae438c8e9e | ||
|
|
ad2f8e87e6 | ||
|
|
ccd81de77e | ||
|
|
d8247e6543 | ||
|
|
46b17e2cdb | ||
|
|
c623730374 | ||
|
|
72e71608b1 | ||
|
|
0d4adfedab | ||
|
|
1a109f7400 | ||
|
|
97bab23e0c | ||
|
|
c881544255 | ||
|
|
92ba347d6f | ||
|
|
f502a0a779 | ||
|
|
77241607fa | ||
|
|
f38958d6d7 | ||
|
|
212e6acf23 | ||
|
|
efa404e275 | ||
|
|
794ab9fc66 | ||
|
|
48bd5c0979 | ||
|
|
e39a0b3639 | ||
|
|
58954109fb | ||
|
|
cb7fa6bf63 | ||
|
|
44fb68b7e0 | ||
|
|
ded0f1049a | ||
|
|
08db073958 | ||
|
|
de8644a504 | ||
|
|
f28ad5af23 | ||
|
|
77ee239db2 | ||
|
|
d9e15754b7 | ||
|
|
dabd7ad99f | ||
|
|
204f78c8ff | ||
|
|
59b91a45a0 | ||
|
|
5a3bd7d649 | ||
|
|
ce6a621414 | ||
|
|
20f6c68108 | ||
|
|
3367822e89 | ||
|
|
8702639510 | ||
|
|
41cbc25c35 | ||
|
|
83189dbad3 | ||
|
|
28ade19419 | ||
|
|
378ae4ad72 | ||
|
|
30e0356ad6 | ||
|
|
eabda4cf28 | ||
|
|
5617b593c8 | ||
|
|
5d43b30a7c | ||
|
|
d6db4b53e7 | ||
|
|
368dd13e16 | ||
|
|
ae942c5288 | ||
|
|
a111e89b27 | ||
|
|
589b26aaad | ||
|
|
1d7608f42c | ||
|
|
efad26641d | ||
|
|
81b04fcb8b | ||
|
|
74cd2bc147 | ||
|
|
2ff06a8abd | ||
|
|
d703e17239 | ||
|
|
3bfcc1ea42 | ||
|
|
c65bdff62c | ||
|
|
f614e8b1bf | ||
|
|
85accf7330 | ||
|
|
a161796dc4 | ||
|
|
261367e7e9 | ||
|
|
5cd0afcfa0 | ||
|
|
1748cbfcea | ||
|
|
200cae2a68 | ||
|
|
b1b7831a90 | ||
|
|
53c6d2f988 | ||
|
|
faa74962e6 | ||
|
|
28cf300ef2 | ||
|
|
e9d1c8534c | ||
|
|
aafe74a3e1 | ||
|
|
db9d31ac06 | ||
|
|
4a9d753441 | ||
|
|
4efaa656f9 | ||
|
|
59f4043fc5 | ||
|
|
d04cd1b1a7 | ||
|
|
781c4e9ebe | ||
|
|
0c9934c357 | ||
|
|
2983f5cd28 | ||
|
|
7610d8d3c6 | ||
|
|
5cd853b773 | ||
|
|
f1c07879ad | ||
|
|
d1a2435ce1 | ||
|
|
45b8eb6942 | ||
|
|
17a969ce83 | ||
|
|
4f02196dad | ||
|
|
85f19b6abd | ||
|
|
42138d88bf | ||
|
|
2ef09d45ee | ||
|
|
9b2a770dbe | ||
|
|
6085730a91 | ||
|
|
94a8658635 | ||
|
|
2618cfb38b | ||
|
|
fcf9a7273f | ||
|
|
5ce755e4d8 | ||
|
|
b7d6639b25 | ||
|
|
95759958b5 | ||
|
|
8ed8a9eab3 | ||
|
|
14a83e7cbe | ||
|
|
bf9f36b2bb | ||
|
|
3215eaf710 | ||
|
|
927bd30fa0 | ||
|
|
247ae9108c | ||
|
|
97f0e59cfd | ||
|
|
56405e7445 | ||
|
|
42c97cd919 | ||
|
|
65317e96f6 | ||
|
|
453b498ccb | ||
|
|
19cbea49f7 | ||
|
|
bd3838c059 | ||
|
|
f24b61f100 | ||
|
|
257c1f62d2 | ||
|
|
4e1ecb5890 |
@@ -1,234 +0,0 @@
|
||||
version: 2
|
||||
|
||||
jobs:
|
||||
|
||||
build:
|
||||
docker:
|
||||
- image: circleci/golang:1.14.2
|
||||
environment:
|
||||
GOPATH: /go
|
||||
working_directory: /home/circleci/workspace/helmfile
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
keys:
|
||||
- go-mod-cache-v1-{{ checksum "./go.sum" }}
|
||||
- go-mod-cache-v1-
|
||||
- run: go env
|
||||
- run: make build
|
||||
- run: make build-test-tools
|
||||
- save_cache:
|
||||
key: go-mod-cache-v1-{{ checksum "./go.sum" }}
|
||||
paths:
|
||||
- /go/pkg/mod
|
||||
- persist_to_workspace:
|
||||
root: ./
|
||||
paths:
|
||||
- .
|
||||
image:
|
||||
docker:
|
||||
- image: circleci/golang:1.14.2
|
||||
working_directory: /home/circleci/workspace/helmfile
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
keys:
|
||||
- go-mod-cache-v1-{{ checksum "./go.sum" }}
|
||||
- go-mod-cache-v1-
|
||||
- setup_remote_docker
|
||||
- run:
|
||||
command: |
|
||||
make tools
|
||||
go mod vendor
|
||||
ORG=roboll BUILD_URL="$CIRCLE_BUILD_URL" make image
|
||||
|
||||
image_helm3:
|
||||
docker:
|
||||
- image: circleci/golang:1.14.2
|
||||
working_directory: /home/circleci/workspace/helmfile
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
keys:
|
||||
- go-mod-cache-v1-{{ checksum "./go.sum" }}
|
||||
- go-mod-cache-v1-
|
||||
- setup_remote_docker
|
||||
- run:
|
||||
command: |
|
||||
make tools
|
||||
go mod vendor
|
||||
ORG=roboll BUILD_URL="$CIRCLE_BUILD_URL" make image/helm3
|
||||
|
||||
test:
|
||||
environment:
|
||||
GOPATH: /go
|
||||
docker:
|
||||
- image: circleci/golang:1.14.2
|
||||
working_directory: /home/circleci/workspace/helmfile
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
keys:
|
||||
- go-mod-cache-v1-{{ checksum "./go.sum" }}
|
||||
- go-mod-cache-v1-
|
||||
- run: make check
|
||||
- run: make pristine
|
||||
- run: make test
|
||||
|
||||
# thanks to https://raw.githubusercontent.com/weaveworks/launcher/master/.circleci/config.yml
|
||||
integration_tests:
|
||||
machine:
|
||||
image: circleci/classic:201808-01
|
||||
environment:
|
||||
CHANGE_MINIKUBE_NONE_USER: true
|
||||
MINIKUBE_WANTUPDATENOTIFICATION: false
|
||||
MINIKUBE_WANTREPORTERRORPROMPT: false
|
||||
steps:
|
||||
- checkout
|
||||
- run: mkdir ~/build
|
||||
- attach_workspace:
|
||||
at: ~/build
|
||||
- run:
|
||||
command: |
|
||||
cp ~/build/helmfile ~/project/helmfile
|
||||
cp ~/build/diff-yamls ~/project/diff-yamls
|
||||
- run:
|
||||
name: Install helm
|
||||
environment:
|
||||
HELM_VERSION: v2.17.0
|
||||
command: |
|
||||
HELM_FILENAME="helm-${HELM_VERSION}-linux-amd64.tar.gz"
|
||||
curl -Lo ${HELM_FILENAME} "https://kubernetes-helm.storage.googleapis.com/${HELM_FILENAME}"
|
||||
tar zxf ${HELM_FILENAME} linux-amd64/helm
|
||||
chmod +x linux-amd64/helm
|
||||
sudo mv linux-amd64/helm /usr/local/bin/
|
||||
- run:
|
||||
name: Install kustomize
|
||||
environment:
|
||||
KUSTOMIZE_VERSION: v3.6.1
|
||||
command: |
|
||||
KUSTOMIZE_FILENAME="kustomize_${KUSTOMIZE_VERSION}_linux_amd64.tar.gz"
|
||||
curl -Lo ${KUSTOMIZE_FILENAME} "https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2F${KUSTOMIZE_VERSION}/${KUSTOMIZE_FILENAME}"
|
||||
tar zxf ${KUSTOMIZE_FILENAME} kustomize
|
||||
chmod +x kustomize
|
||||
sudo mv kustomize /usr/local/bin/
|
||||
- run:
|
||||
name: Deploy minikube
|
||||
environment:
|
||||
CHANGE_MINIKUBE_NONE_USER: true
|
||||
K8S_VERSION: v1.12.3
|
||||
MINIKUBE_VERSION: v0.30.0
|
||||
command: |
|
||||
curl -Lo kubectl https://storage.googleapis.com/kubernetes-release/release/${K8S_VERSION}/bin/linux/amd64/kubectl
|
||||
chmod +x kubectl && sudo mv kubectl /usr/local/bin/
|
||||
curl -Lo minikube https://storage.googleapis.com/minikube/releases/${MINIKUBE_VERSION}/minikube-linux-amd64
|
||||
chmod +x minikube && sudo mv minikube /usr/local/bin/
|
||||
sudo -E minikube start --vm-driver=none --kubernetes-version=${K8S_VERSION}
|
||||
sudo -E minikube update-context
|
||||
- run:
|
||||
name: Wait for nodes to become ready
|
||||
command: JSONPATH='{range .items[*]}{@.metadata.name}:{range @.status.conditions[*]}{@.type}={@.status};{end}{end}'; until kubectl get nodes -o jsonpath="$JSONPATH" 2>&1 | grep -q "Ready=True"; do sleep 1; done
|
||||
- run:
|
||||
name: Execute integration tests
|
||||
command: |
|
||||
export TERM=xterm
|
||||
make integration
|
||||
|
||||
integration_tests_helm3:
|
||||
machine:
|
||||
image: circleci/classic:201808-01
|
||||
environment:
|
||||
CHANGE_MINIKUBE_NONE_USER: true
|
||||
MINIKUBE_WANTUPDATENOTIFICATION: false
|
||||
MINIKUBE_WANTREPORTERRORPROMPT: false
|
||||
steps:
|
||||
- checkout
|
||||
- run: mkdir ~/build
|
||||
- attach_workspace:
|
||||
at: ~/build
|
||||
- run:
|
||||
command: |
|
||||
cp ~/build/helmfile ~/project/helmfile
|
||||
cp ~/build/diff-yamls ~/project/diff-yamls
|
||||
- run:
|
||||
name: Install helm
|
||||
environment:
|
||||
HELM_VERSION: v3.4.2
|
||||
command: |
|
||||
HELM_FILENAME="helm-${HELM_VERSION}-linux-amd64.tar.gz"
|
||||
curl -Lo ${HELM_FILENAME} "https://get.helm.sh/${HELM_FILENAME}"
|
||||
tar zxf ${HELM_FILENAME} linux-amd64/helm
|
||||
chmod +x linux-amd64/helm
|
||||
sudo mv linux-amd64/helm /usr/local/bin/
|
||||
- run:
|
||||
name: Install kustomize
|
||||
environment:
|
||||
KUSTOMIZE_VERSION: v3.6.1
|
||||
command: |
|
||||
KUSTOMIZE_FILENAME="kustomize_${KUSTOMIZE_VERSION}_linux_amd64.tar.gz"
|
||||
curl -Lo ${KUSTOMIZE_FILENAME} "https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2F${KUSTOMIZE_VERSION}/${KUSTOMIZE_FILENAME}"
|
||||
tar zxf ${KUSTOMIZE_FILENAME} kustomize
|
||||
chmod +x kustomize
|
||||
sudo mv kustomize /usr/local/bin/
|
||||
- run:
|
||||
name: Deploy minikube
|
||||
environment:
|
||||
CHANGE_MINIKUBE_NONE_USER: true
|
||||
K8S_VERSION: v1.12.3
|
||||
MINIKUBE_VERSION: v0.30.0
|
||||
command: |
|
||||
curl -Lo kubectl https://storage.googleapis.com/kubernetes-release/release/${K8S_VERSION}/bin/linux/amd64/kubectl
|
||||
chmod +x kubectl && sudo mv kubectl /usr/local/bin/
|
||||
curl -Lo minikube https://storage.googleapis.com/minikube/releases/${MINIKUBE_VERSION}/minikube-linux-amd64
|
||||
chmod +x minikube && sudo mv minikube /usr/local/bin/
|
||||
sudo -E minikube start --vm-driver=none --kubernetes-version=${K8S_VERSION}
|
||||
sudo -E minikube update-context
|
||||
- run:
|
||||
name: Wait for nodes to become ready
|
||||
command: JSONPATH='{range .items[*]}{@.metadata.name}:{range @.status.conditions[*]}{@.type}={@.status};{end}{end}'; until kubectl get nodes -o jsonpath="$JSONPATH" 2>&1 | grep -q "Ready=True"; do sleep 1; done
|
||||
- run:
|
||||
name: Execute integration tests
|
||||
command: |
|
||||
export TERM=xterm
|
||||
HELMFILE_HELM3=1 make integration
|
||||
|
||||
# GITHUB_TOKEN env var must be setup in circleci console
|
||||
|
||||
release:
|
||||
docker:
|
||||
- image: circleci/golang:1.14.2
|
||||
working_directory: /home/circleci/workspace/helmfile
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
keys:
|
||||
- go-mod-cache-v1-{{ checksum "./go.sum" }}
|
||||
- go-mod-cache-v1-
|
||||
- setup_remote_docker
|
||||
- run:
|
||||
command: |
|
||||
docker login -u="$DOCKER_USER" -p="$DOCKER_PASS" quay.io
|
||||
make tools
|
||||
go mod vendor
|
||||
ORG=roboll BUILD_URL="$CIRCLE_BUILD_URL" make push push/helm3 release
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
build_and_test:
|
||||
jobs:
|
||||
- build
|
||||
- image
|
||||
- image_helm3
|
||||
- test
|
||||
- integration_tests:
|
||||
requires:
|
||||
- build
|
||||
- integration_tests_helm3:
|
||||
requires:
|
||||
- build
|
||||
- release:
|
||||
filters:
|
||||
branches:
|
||||
ignore: /.*/
|
||||
tags:
|
||||
only: /v.*/
|
||||
@@ -0,0 +1 @@
|
||||
Dockerfile*
|
||||
@@ -0,0 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: mumoshu
|
||||
@@ -0,0 +1,81 @@
|
||||
name: Bug
|
||||
description: File a bug report
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Before opening a bug report, please search for the behaviour in the existing issues.
|
||||
|
||||
Go ahead to https://github.com/helmfile/helmfile/search?q=&type= and fill in the error message you received from Helmfile or the keywords you think appropriate.
|
||||
- type: markdown
|
||||
id: thankyou
|
||||
attributes:
|
||||
value: |
|
||||
Thank you for taking your time to file a bug report! To confirm it's a bug, we need some information to reproduce it.
|
||||
- type: input
|
||||
id: os
|
||||
attributes:
|
||||
label: Operating system
|
||||
description: "Which operating system do you use? Please provide the version as well."
|
||||
placeholder: "ex. Ubuntu 20.04.4 LTS"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: helmfile
|
||||
attributes:
|
||||
label: Helmfile Version
|
||||
description: "Please provide the version number of Helmfile you used. If it isn't the latest, please upgrade first."
|
||||
placeholder: "Run `helmfile version` to print it."
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: helm
|
||||
attributes:
|
||||
label: Helm Version
|
||||
description: "Please provide the version number of Helm you used. If it isn't the latest, please upgrade first."
|
||||
placeholder: "Run `helm version` to print it."
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: bug-description
|
||||
attributes:
|
||||
label: Bug description
|
||||
description: What happened?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: helmfile-yaml
|
||||
attributes:
|
||||
label: Example helmfile.yaml
|
||||
description: "Please provide an example helmfile.yaml that can be used to reproduce the issue locally."
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: helmfile-error
|
||||
attributes:
|
||||
label: "Error message you've seen (if any)"
|
||||
description: "Please provide the error message emitted by Helmfile."
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: repo
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
description: Please provide the URL to a GitHub repository that contains a helmfile.yaml, other companion files, and a README.md with the steps to reproduce the bug.
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: helmfile_working_ver
|
||||
attributes:
|
||||
label: Working Helmfile Version
|
||||
description: "Please provide the highest version number of Helmfile that doesn't result in the above problem."
|
||||
placeholder: "Run `helmfile version` to print it."
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: discussion
|
||||
attributes:
|
||||
label: Relevant discussion
|
||||
description: Please provide the URL to a relevant GitHub Discussion.
|
||||
validations:
|
||||
required: false
|
||||
@@ -0,0 +1,14 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Sponsor Helmfile Maintainers
|
||||
about: If your business relies on the continued maintainance of Helmfile, please consider sponsoring maintainers.
|
||||
url: https://github.com/helmfile/helmfile/tree/main/CODEOWNERS
|
||||
- name: Ideas and Feature Requests
|
||||
about: Wanna request a feature? Create a discussion and collect :+1:s first.
|
||||
url: https://github.com/helmfile/helmfile/discussions/new?category=ideas
|
||||
- name: Questions and User Support
|
||||
about: Need support using Helmfile? We use Discussions as the place to provide community support.
|
||||
url: https://github.com/helmfile/helmfile/discussions/new?category=questions
|
||||
- name: Need Paid Support?
|
||||
about: Consider contracting with any of the Helmfile maintainers.
|
||||
url: https://github.com/helmfile/helmfile/tree/main/CODEOWNERS
|
||||
@@ -0,0 +1,11 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
@@ -0,0 +1,18 @@
|
||||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 14
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 7
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- in progress
|
||||
- pinned
|
||||
- security
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: wontfix
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. Thank you
|
||||
for your contributions.
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: false
|
||||
@@ -0,0 +1,68 @@
|
||||
HELM_VERSION ?= v3.7.2
|
||||
KUSTOMIZE_VERSION ?= v4.5.7
|
||||
K8S_VERSION ?= v1.13.12
|
||||
MINIKUBE_VERSION ?= v0.30.0
|
||||
SOPS_VERSION ?= v3.6.1
|
||||
|
||||
# ---
|
||||
CHANGE_MINIKUBE_NONE_USER ?= true
|
||||
MINIKUBE_WANTUPDATENOTIFICATION ?= false
|
||||
MINIKUBE_WANTREPORTERRORPROMPT ?= false
|
||||
|
||||
VAULT_ADDR := http://127.0.0.1:8200
|
||||
VAULT_TOKEN := toor
|
||||
|
||||
tmp := $(shell mktemp -d)
|
||||
HELM_FILENAME := helm-${HELM_VERSION}-linux-amd64.tar.gz
|
||||
KUSTOMIZE_FILENAME := kustomize_${KUSTOMIZE_VERSION}_linux_amd64.tar.gz
|
||||
|
||||
|
||||
all: vault sops helm kustomize minikube/destroy minikube
|
||||
|
||||
helm:
|
||||
curl -sSLo $(tmp)/${HELM_FILENAME} "https://get.helm.sh/${HELM_FILENAME}"
|
||||
tar zxf $(tmp)/${HELM_FILENAME} --directory ${tmp} linux-amd64/helm
|
||||
chmod +x ${tmp}/linux-amd64/helm
|
||||
sudo mv ${tmp}/linux-amd64/helm /usr/local/bin/
|
||||
.PHONY: helm
|
||||
|
||||
kustomize:
|
||||
curl -sSLo $(tmp)/${KUSTOMIZE_FILENAME} "https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2F${KUSTOMIZE_VERSION}/${KUSTOMIZE_FILENAME}"
|
||||
tar zxf $(tmp)/${KUSTOMIZE_FILENAME} --directory ${tmp} kustomize
|
||||
chmod +x ${tmp}/kustomize
|
||||
sudo mv ${tmp}/kustomize /usr/local/bin/
|
||||
.PHONY: kustomize
|
||||
|
||||
minikube/destroy:
|
||||
sudo -E minikube delete || true
|
||||
sudo -E rm -rf /etc/kubernetes || true
|
||||
sudo -E rm -rf $$HOME/.minikube/* || true
|
||||
.PHONY: minikube/destroy
|
||||
.EXPORT_ALL_VARIABLES: minikube/destroy
|
||||
|
||||
minikube:
|
||||
curl -sSLo ${tmp}/kubectl https://dl.k8s.io/release/${K8S_VERSION}/bin/linux/amd64/kubectl
|
||||
chmod +x ${tmp}/kubectl && sudo mv ${tmp}/kubectl /usr/local/bin/
|
||||
curl -sSLo ${tmp}/minikube https://storage.googleapis.com/minikube/releases/${MINIKUBE_VERSION}/minikube-linux-amd64
|
||||
chmod +x ${tmp}/minikube && sudo mv ${tmp}/minikube /usr/local/bin/
|
||||
sudo -E minikube delete || true
|
||||
sudo -E rm -rf /etc/kubernetes || true
|
||||
sudo -E rm -rf $$HOME/.minikube/* || true
|
||||
sudo -E minikube start --vm-driver=none --kubernetes-version=${K8S_VERSION}
|
||||
sudo -E minikube update-context
|
||||
kubectl wait node/minikube --for=condition=Ready
|
||||
.PHONY: minikube
|
||||
.EXPORT_ALL_VARIABLES: minikube
|
||||
|
||||
vault:
|
||||
docker kill $$(docker ps -a --filter "name=vault" -q) || true
|
||||
docker run -d -p8200:8200 --rm --name vault vault:1.2.0 server -dev -dev-root-token-id=toor
|
||||
docker run --rm --network="host" -e VAULT_ADDR=$$VAULT_ADDR -e VAULT_TOKEN=$$VAULT_TOKEN vault:1.2.0 secrets enable -path=sops transit
|
||||
docker run --rm --network="host" -e VAULT_ADDR=$$VAULT_ADDR -e VAULT_TOKEN=$$VAULT_TOKEN vault:1.2.0 write sops/keys/key type=rsa-4096
|
||||
.PHONY: vault
|
||||
|
||||
sops:
|
||||
curl -sSLo $(tmp)/sops "https://github.com/mozilla/sops/releases/download/${SOPS_VERSION}/sops-${SOPS_VERSION}.linux"
|
||||
chmod +x $(tmp)/sops
|
||||
sudo mv ${tmp}/sops /usr/local/bin/
|
||||
.PHONY: sops
|
||||
@@ -0,0 +1,171 @@
|
||||
name: Go
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
paths-ignore: [ '**.md', '**/docs/**' ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
paths-ignore: [ '**.md', '**/docs/**' ]
|
||||
|
||||
jobs:
|
||||
|
||||
tests:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Cache libraries
|
||||
uses: actions/cache@v2
|
||||
env:
|
||||
cache-name: cache-go
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.20'
|
||||
- name: Env
|
||||
run: go env
|
||||
- name: Build
|
||||
run: make build build-test-tools
|
||||
- name: Test
|
||||
run: make check test
|
||||
|
||||
- name: Prepare tar to upload built binaries
|
||||
run: tar -cvf built-binaries.tar helmfile diff-yamls dyff
|
||||
- name: Upload built binaries
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: built-binaries-${{ github.run_id }}
|
||||
path: built-binaries.tar
|
||||
retention-days: 1
|
||||
- name: Display built binaries
|
||||
run: ls -l helmfile diff-yamls dyff
|
||||
|
||||
integration_tests:
|
||||
needs: tests
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
# Helm maintains the latest minor version only and therefore each Helmfile version supports 2 Helm minor versions.
|
||||
# That's why we cover only 2 Helm minor versions in this matrix.
|
||||
# See https://github.com/helmfile/helmfile/pull/286#issuecomment-1250161182 for more context.
|
||||
- helm-version: v3.10.3
|
||||
kustomize-version: v4.4.1
|
||||
plugin-secrets-version: 3.15.0
|
||||
plugin-diff-version: 3.5.0
|
||||
extra-helmfile-flags:
|
||||
v1mode:
|
||||
- helm-version: v3.10.3
|
||||
kustomize-version: v4.5.7
|
||||
# We assume that the helm-secrets plugin is supposed to
|
||||
# work with the two most recent helm minor versions.
|
||||
# Once it turned out to be not practically true,
|
||||
# we will mark this combination as failable,
|
||||
# and instruct users to upgrade helm and helm-secrets at once.
|
||||
plugin-secrets-version: 4.0.0
|
||||
plugin-diff-version: 3.6.0
|
||||
extra-helmfile-flags:
|
||||
v1mode:
|
||||
- helm-version: v3.11.2
|
||||
kustomize-version: v4.4.1
|
||||
plugin-secrets-version: 3.15.0
|
||||
plugin-diff-version: 3.5.0
|
||||
extra-helmfile-flags:
|
||||
v1mode:
|
||||
- helm-version: v3.11.2
|
||||
kustomize-version: v4.5.7
|
||||
plugin-secrets-version: 4.0.0
|
||||
plugin-diff-version: 3.6.0
|
||||
extra-helmfile-flags:
|
||||
v1mode:
|
||||
# Helmfile v1
|
||||
- helm-version: v3.11.2
|
||||
kustomize-version: v4.5.7
|
||||
plugin-secrets-version: 4.0.0
|
||||
plugin-diff-version: 3.6.0
|
||||
extra-helmfile-flags:
|
||||
v1mode: "true"
|
||||
# In case you need to test some optional helmfile features,
|
||||
# enable it via extra-helmfile-flags below.
|
||||
- helm-version: v3.11.2
|
||||
kustomize-version: v4.5.7
|
||||
plugin-secrets-version: 4.0.0
|
||||
plugin-diff-version: 3.6.0
|
||||
extra-helmfile-flags: "--enable-live-output"
|
||||
v1mode:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Cache libraries
|
||||
uses: actions/cache@v2
|
||||
env:
|
||||
cache-name: cache-go
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.20'
|
||||
- name: Env
|
||||
run: go env
|
||||
|
||||
- name: Download built binaries
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: built-binaries-${{ github.run_id }}
|
||||
- name: Extract tar to get built binaries
|
||||
run: tar -xvf built-binaries.tar
|
||||
- name: Display built binaries
|
||||
run: ls -l helmfile diff-yamls dyff
|
||||
|
||||
- name: Install test dependencies
|
||||
env:
|
||||
HELM_VERSION: ${{ matrix.helm-version }}
|
||||
KUSTOMIZE_VERSION: ${{ matrix.kustomize-version }}
|
||||
run: make -C .github/workflows helm vault sops kustomize
|
||||
- name: Start minikube
|
||||
uses: medyagh/setup-minikube@master
|
||||
- name: Execute integration tests
|
||||
env:
|
||||
HELM_SECRETS_VERSION: ${{ matrix.plugin-secrets-version }}
|
||||
HELM_DIFF_VERSION: ${{ matrix.plugin-diff-version }}
|
||||
HELMFILE_HELM3: 1
|
||||
TERM: xterm
|
||||
EXTRA_HELMFILE_FLAGS: ${{ matrix.extra-helmfile-flags }}
|
||||
HELMFILE_V1MODE: ${{ matrix.v1mode }}
|
||||
run: make integration
|
||||
e2e_tests:
|
||||
needs: tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Install package
|
||||
run: |
|
||||
sudo apt-get -y install expect
|
||||
- name: Download built binaries
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: built-binaries-${{ github.run_id }}
|
||||
- name: Extract tar to get built binaries
|
||||
run: tar -xvf built-binaries.tar
|
||||
- name: Display built binaries
|
||||
run: ls -l helmfile diff-yamls dyff
|
||||
- name: Run helmfile init
|
||||
env:
|
||||
TERM: xterm
|
||||
run: bash test/e2e/helmfile-init/init_linux.sh
|
||||
@@ -0,0 +1,83 @@
|
||||
name: Images
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- "image/**"
|
||||
- "*image"
|
||||
- "image*"
|
||||
tags:
|
||||
- 'v*'
|
||||
paths-ignore:
|
||||
- .github/workflows/ci.yml
|
||||
- .github/workflows/lock.yml
|
||||
- ".github/ISSUE_TEMPLATE/**"
|
||||
- "docs/**"
|
||||
- "hack/**"
|
||||
- "**.md"
|
||||
- ".gitignore"
|
||||
- "Makefile"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
strategy:
|
||||
matrix:
|
||||
image:
|
||||
- dockerfile: Dockerfile
|
||||
suffix: ""
|
||||
- dockerfile: Dockerfile.debian-stable-slim
|
||||
suffix: "-debian-stable-slim"
|
||||
- dockerfile: Dockerfile.ubuntu
|
||||
suffix: "-ubuntu"
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
version: latest
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ghcr.io/${{ github.repository }}${{ matrix.image.suffix }}
|
||||
flavor: latest=false
|
||||
tags: |
|
||||
type=raw,value=canary,enable=${{ github.ref == format('refs/heads/{0}', 'main') }}
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{raw}}
|
||||
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build / Push
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
file: ${{ matrix.image.dockerfile }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
@@ -0,0 +1,30 @@
|
||||
name: Lint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
paths-ignore: [ '**.md', '**/docs/**' ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
paths-ignore: [ '**.md', '**/docs/**' ]
|
||||
|
||||
env:
|
||||
GO_VERSION: 1.20
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.20'
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Golangci lint
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
version: v1.51.0
|
||||
@@ -0,0 +1,30 @@
|
||||
# Copyright 2022 The Helmfile 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.
|
||||
|
||||
name: 'Lock closed'
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
|
||||
jobs:
|
||||
lock:
|
||||
runs-on: 'ubuntu-latest'
|
||||
steps:
|
||||
- uses: 'dessant/lock-threads@v2'
|
||||
with:
|
||||
github-token: '${{ github.token }}'
|
||||
issue-lock-inactive-days: 1
|
||||
issue-lock-reason: 'resolved'
|
||||
pr-lock-inactive-days: 1
|
||||
pr-lock-reason: 'resolved'
|
||||
@@ -0,0 +1,29 @@
|
||||
name: Publish v0.x Binaries
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "!*"
|
||||
tags:
|
||||
- "v0*"
|
||||
|
||||
jobs:
|
||||
goreleaser:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.20'
|
||||
-
|
||||
name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v1
|
||||
with:
|
||||
version: latest
|
||||
args: release --rm-dist
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -0,0 +1,33 @@
|
||||
name: Publish v1.x Binaries
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "!*"
|
||||
tags:
|
||||
- "v1*"
|
||||
|
||||
env:
|
||||
# This is referenced from .goreleaser.yml
|
||||
HELMFILE_V1MODE: "true"
|
||||
|
||||
jobs:
|
||||
goreleaser:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.20'
|
||||
-
|
||||
name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v1
|
||||
with:
|
||||
version: latest
|
||||
args: release --rm-dist
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -0,0 +1,26 @@
|
||||
name: Test Binary Builds
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
goreleaser:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.20'
|
||||
-
|
||||
name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v1
|
||||
with:
|
||||
version: latest
|
||||
args: release --rm-dist --snapshot
|
||||
+10
-3
@@ -1,6 +1,13 @@
|
||||
dist/
|
||||
.idea/
|
||||
helmfile
|
||||
helmfile.lock
|
||||
test/integration/tmp
|
||||
/helmfile
|
||||
/helmfile.lock
|
||||
/diff-yamls
|
||||
/dyff
|
||||
test/integration/tmp$
|
||||
vendor/
|
||||
*.log
|
||||
.vagrant/
|
||||
*.lock
|
||||
test/integration/.gnupg/
|
||||
.vscode/launch.json
|
||||
|
||||
+360
@@ -0,0 +1,360 @@
|
||||
# This file contains all available configuration options
|
||||
# with their default values.
|
||||
|
||||
# options for analysis running
|
||||
run:
|
||||
# default concurrency is a available CPU number
|
||||
# concurrency: 4
|
||||
|
||||
# timeout for analysis, e.g. 30s, 5m, default is 1m
|
||||
timeout: 30m
|
||||
|
||||
# exit code when at least one issue was found, default is 1
|
||||
issues-exit-code: 1
|
||||
|
||||
# include test files or not, default is true
|
||||
tests: true
|
||||
|
||||
# list of build tags, all linters use it. Default is empty list.
|
||||
# build-tags:
|
||||
# - mytag
|
||||
|
||||
# which dirs to skip: issues from them won't be reported;
|
||||
# can use regexp here: generated.*, regexp is applied on full path;
|
||||
# default value is empty list, but default dirs are skipped independently
|
||||
# from this option's value (see skip-dirs-use-default).
|
||||
# skip-dirs:
|
||||
# - src/external_libs
|
||||
# - autogenerated_by_my_lib
|
||||
|
||||
# default is true. Enables skipping of directories:
|
||||
# vendor$, third_party$, testdata$, examples$, Godeps$, builtin$
|
||||
skip-dirs-use-default: true
|
||||
|
||||
# which files to skip: they will be analyzed, but issues from them
|
||||
# won't be reported. Default value is empty list, but there is
|
||||
# no need to include all autogenerated files, we confidently recognize
|
||||
# autogenerated files. If it's not please let us know.
|
||||
# skip-files:
|
||||
# - ".*\\.my\\.go$"
|
||||
# - lib/bad.go
|
||||
|
||||
# by default isn't set. If set we pass it to "go list -mod={option}". From "go help modules":
|
||||
# If invoked with -mod=readonly, the go command is disallowed from the implicit
|
||||
# automatic updating of go.mod described above. Instead, it fails when any changes
|
||||
# to go.mod are needed. This setting is most useful to check that go.mod does
|
||||
# not need updates, such as in a continuous integration and testing system.
|
||||
# If invoked with -mod=vendor, the go command assumes that the vendor
|
||||
# directory holds the correct copies of dependencies and ignores
|
||||
# the dependency descriptions in go.mod.
|
||||
# modules-download-mode: readonly|release|vendor
|
||||
|
||||
# output configuration options
|
||||
output:
|
||||
# colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number"
|
||||
format: line-number
|
||||
|
||||
# print lines of code with issue, default is true
|
||||
print-issued-lines: true
|
||||
|
||||
# print linter name in the end of issue text, default is true
|
||||
print-linter-name: true
|
||||
|
||||
# all available settings of specific linters
|
||||
linters-settings:
|
||||
gci:
|
||||
sections:
|
||||
- standard
|
||||
- default
|
||||
- prefix(github.com/helmfile/helmfile)
|
||||
|
||||
errcheck:
|
||||
# report about not checking of errors in type assetions: `a := b.(MyStruct)`;
|
||||
# default is false: such cases aren't reported by default.
|
||||
check-type-assertions: false
|
||||
|
||||
# report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`;
|
||||
# default is false: such cases aren't reported by default.
|
||||
check-blank: false
|
||||
|
||||
# [deprecated] comma-separated list of pairs of the form pkg:regex
|
||||
# the regex is used to ignore names within pkg. (default "fmt:.*").
|
||||
# see https://github.com/kisielk/errcheck#the-deprecated-method for details
|
||||
# ignore: fmt:.*
|
||||
|
||||
# path to a file containing a list of functions to exclude from checking
|
||||
# see https://github.com/kisielk/errcheck#excluding-functions for details
|
||||
# exclude: /path/to/file.txt
|
||||
|
||||
# Disable error checking, as errorcheck detects more errors and is more configurable.
|
||||
gosec:
|
||||
exclude:
|
||||
- "G104"
|
||||
|
||||
govet:
|
||||
# report about shadowed variables
|
||||
check-shadowing: false
|
||||
|
||||
# settings per analyzer
|
||||
settings:
|
||||
printf: # analyzer name, run `go tool vet help` to see all analyzers
|
||||
funcs: # run `go tool vet help printf` to see available settings for `printf` analyzer
|
||||
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof
|
||||
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf
|
||||
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf
|
||||
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf
|
||||
|
||||
# enable or disable analyzers by name
|
||||
# enable:
|
||||
# - atomicalign
|
||||
# enable-all: false
|
||||
# disable:
|
||||
# - shadow
|
||||
# disable-all: false
|
||||
golint:
|
||||
# minimal confidence for issues, default is 0.8
|
||||
min-confidence: 0.8
|
||||
gofmt:
|
||||
# simplify code: gofmt with `-s` option, true by default
|
||||
simplify: true
|
||||
goimports:
|
||||
# put imports beginning with prefix after 3rd-party packages;
|
||||
# it's a comma-separated list of prefixes
|
||||
# local-prefixes: github.com/org/project
|
||||
gocyclo:
|
||||
# minimal code complexity to report, 30 by default (but we recommend 10-20)
|
||||
min-complexity: 30
|
||||
gocognit:
|
||||
# minimal code complexity to report, 30 by default (but we recommend 10-20)
|
||||
min-complexity: 100
|
||||
maligned:
|
||||
# print struct with more effective memory layout or not, false by default
|
||||
suggest-new: true
|
||||
dupl:
|
||||
# tokens count to trigger issue, 150 by default
|
||||
threshold: 100
|
||||
goconst:
|
||||
# minimal length of string constant, 3 by default
|
||||
min-len: 3
|
||||
# minimal occurrences count to trigger, 3 by default
|
||||
min-occurrences: 8
|
||||
# depguard:
|
||||
# list-type: blacklist
|
||||
# include-go-root: false
|
||||
# packages:
|
||||
# - github.com/sirupsen/logrus
|
||||
# packages-with-error-messages:
|
||||
# # specify an error message to output when a blacklisted package is used
|
||||
# github.com/sirupsen/logrus: "logging is allowed only by logutils.Log"
|
||||
misspell:
|
||||
# Correct spellings using locale preferences for US or UK.
|
||||
# Default is to use a neutral variety of English.
|
||||
# Setting locale to US will correct the British spelling of 'colour' to 'color'.
|
||||
locale: US
|
||||
ignore-words:
|
||||
- GitLab
|
||||
lll:
|
||||
# max line length, lines longer will be reported. Default is 120.
|
||||
# '\t' is counted as 1 character by default, and can be changed with the tab-width option
|
||||
line-length: 120
|
||||
# tab width in spaces. Default to 1.
|
||||
tab-width: 1
|
||||
unused:
|
||||
# treat code as a program (not a library) and report unused exported identifiers; default is false.
|
||||
# XXX: if you enable this setting, unused will report a lot of false-positives in text editors:
|
||||
# if it's called for subdir of a project it can't find funcs usages. All text editor integrations
|
||||
# with golangci-lint call it on a directory with the changed file.
|
||||
check-exported: false
|
||||
unparam:
|
||||
# Inspect exported functions, default is false. Set to true if no external program/library imports your code.
|
||||
# XXX: if you enable this setting, unparam will report a lot of false-positives in text editors:
|
||||
# if it's called for subdir of a project it can't find external interfaces. All text editor integrations
|
||||
# with golangci-lint call it on a directory with the changed file.
|
||||
check-exported: false
|
||||
nakedret:
|
||||
# make an issue if func has more lines of code than this setting and it has naked returns; default is 30
|
||||
max-func-lines: 30
|
||||
prealloc:
|
||||
# XXX: we don't recommend using this linter before doing performance profiling.
|
||||
# For most programs usage of prealloc will be a premature optimization.
|
||||
|
||||
# Report preallocation suggestions only on simple loops that have no returns/breaks/continues/gotos in them.
|
||||
# True by default.
|
||||
simple: true
|
||||
range-loops: true # Report preallocation suggestions on range loops, true by default
|
||||
for-loops: false # Report preallocation suggestions on for loops, false by default
|
||||
gocritic:
|
||||
# Which checks should be enabled; can't be combined with 'disabled-checks';
|
||||
# See https://go-critic.github.io/overview#checks-overview
|
||||
# To check which checks are enabled run `GL_DEBUG=gocritic golangci-lint run`
|
||||
# By default list of stable checks is used.
|
||||
# enabled-checks:
|
||||
# - rangeValCopy
|
||||
|
||||
# Which checks should be disabled; can't be combined with 'enabled-checks'; default is empty
|
||||
# disabled-checks:
|
||||
# - regexpMust
|
||||
|
||||
# Enable multiple checks by tags, run `GL_DEBUG=gocritic golangci-lint run` to see all tags and checks.
|
||||
# Empty list by default. See https://github.com/go-critic/go-critic#usage -> section "Tags".
|
||||
# enabled-tags:
|
||||
# - performance
|
||||
|
||||
settings: # settings passed to gocritic
|
||||
captLocal: # must be valid enabled check name
|
||||
paramsOnly: true
|
||||
# rangeValCopy:
|
||||
# sizeThreshold: 32
|
||||
godox:
|
||||
# report any comments starting with keywords, this is useful for TODO or FIXME comments that
|
||||
# might be left in the code accidentally and should be resolved before merging
|
||||
keywords: # default keywords are TODO, BUG, and FIXME, these can be overwritten by this setting
|
||||
- TODO
|
||||
- BUG
|
||||
- FIXME
|
||||
- NOTE
|
||||
- OPTIMIZE # marks code that should be optimized before merging
|
||||
- HACK # marks hack-arounds that should be removed before merging
|
||||
dogsled:
|
||||
# checks assignments with too many blank identifiers; default is 2
|
||||
max-blank-identifiers: 2
|
||||
|
||||
whitespace:
|
||||
multi-if: false # Enforces newlines (or comments) after every multi-line if statement
|
||||
multi-func: false # Enforces newlines (or comments) after every multi-line function signature
|
||||
wsl:
|
||||
# If true append is only allowed to be cuddled if appending value is
|
||||
# matching variables, fields or types on line above. Default is true.
|
||||
strict-append: true
|
||||
# Allow calls and assignments to be cuddled as long as the lines have any
|
||||
# matching variables, fields or types. Default is true.
|
||||
allow-assign-and-call: true
|
||||
# Allow multiline assignments to be cuddled. Default is true.
|
||||
allow-multiline-assign: true
|
||||
# Allow declarations (var) to be cuddled.
|
||||
allow-cuddle-declarations: false
|
||||
# Allow trailing comments in ending of blocks
|
||||
allow-trailing-comment: false
|
||||
# Force newlines in end of case at this limit (0 = never).
|
||||
force-case-trailing-whitespace: 0
|
||||
revive:
|
||||
ignore-generated-header: true
|
||||
severity: warning
|
||||
funlen:
|
||||
# Checks the number of lines in a function.
|
||||
# If lower than 0, disable the check.
|
||||
# Default: 60
|
||||
lines: 280
|
||||
# Checks the number of statements in a function.
|
||||
# If lower than 0, disable the check.
|
||||
# Default: 40
|
||||
statements: 140
|
||||
|
||||
linters:
|
||||
# please, do not use `enable-all`: it's deprecated and will be removed soon.
|
||||
# inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint
|
||||
disable-all: true
|
||||
enable:
|
||||
- bodyclose
|
||||
- depguard
|
||||
- usestdlibvars
|
||||
- reassign
|
||||
- errcheck
|
||||
- funlen
|
||||
- gocognit
|
||||
- goconst
|
||||
- gofmt
|
||||
- goimports
|
||||
- revive
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- misspell
|
||||
- nakedret
|
||||
- exportloopref
|
||||
- staticcheck
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unparam
|
||||
- unused
|
||||
- whitespace
|
||||
- gci
|
||||
# - gocritic
|
||||
# - godox
|
||||
# - gosec
|
||||
# - interfacer
|
||||
# - stylecheck
|
||||
# - dogsled
|
||||
# - dupl
|
||||
# don't enable:
|
||||
# - deadcode
|
||||
# - gochecknoglobals
|
||||
# - gochecknoinits
|
||||
# - gocyclo
|
||||
# - lll
|
||||
# - maligned
|
||||
# - prealloc
|
||||
# - varcheck
|
||||
|
||||
issues:
|
||||
# List of regexps of issue texts to exclude, empty list by default.
|
||||
# But independently from this option we use default exclude patterns,
|
||||
# it can be disabled by `exclude-use-default: false`. To list all
|
||||
# excluded by default patterns execute `golangci-lint run --help`
|
||||
# exclude:
|
||||
# - abcdef
|
||||
|
||||
# Excluding configuration per-path, per-linter, per-text and per-source
|
||||
exclude-rules:
|
||||
# Exclude some linters from running on tests files.
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- gocyclo
|
||||
- errcheck
|
||||
- dupl
|
||||
- gosec
|
||||
- funlen
|
||||
|
||||
# Exclude known linters from partially hard-vendored code,
|
||||
# which is impossible to exclude via "nolint" comments.
|
||||
# - path: internal/hmac/
|
||||
# text: "weak cryptographic primitive"
|
||||
# linters:
|
||||
# - gosec
|
||||
|
||||
# Exclude some staticcheck messages
|
||||
# - linters:
|
||||
# - staticcheck
|
||||
# text: "SA9003:"
|
||||
|
||||
# Exclude lll issues for long lines with go:generate
|
||||
- linters:
|
||||
- lll
|
||||
source: "^//go:generate "
|
||||
|
||||
# Independently from option `exclude` we use default exclude patterns,
|
||||
# it can be disabled by this option. To list all
|
||||
# excluded by default patterns execute `golangci-lint run --help`.
|
||||
# Default value for this option is true.
|
||||
exclude-use-default: false
|
||||
|
||||
# Maximum issues count per one linter. Set to 0 to disable. Default is 50.
|
||||
max-issues-per-linter: 0
|
||||
|
||||
# Maximum count of issues with the same text. Set to 0 to disable. Default is 3.
|
||||
max-same-issues: 0
|
||||
|
||||
# Show only new issues: if there are unstaged changes or untracked files,
|
||||
# only those changes are analyzed, else only changes in HEAD~ are analyzed.
|
||||
# It's a super-useful option for integration of golangci-lint into existing
|
||||
# large codebase. It's not practical to fix all existing issues at the moment
|
||||
# of integration: much better don't allow issues in new code.
|
||||
# Default is false.
|
||||
new: false
|
||||
|
||||
# Show only new issues created after git revision `REV`
|
||||
# This should be passed as flag during individual CI jobs.
|
||||
# new-from-rev: REV
|
||||
|
||||
# Show only new issues created in git patch with set file path.
|
||||
# new-from-patch: path/to/patch/file
|
||||
@@ -0,0 +1,27 @@
|
||||
project_name: helmfile
|
||||
env:
|
||||
# We default to non-v1 mode (=helmfile v0.x behavior) when HELMFILE_V1MODE is not set
|
||||
- HELMFILE_V1MODE={{ if index .Env "HELMFILE_V1MODE" }}{{ .Env.HELMFILE_V1MODE }}{{ else }}false{{ end }}
|
||||
builds:
|
||||
- id: helmfile
|
||||
main: .
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
ldflags:
|
||||
- -s -w
|
||||
- -X go.szostok.io/version.version={{.Version}}
|
||||
- -X go.szostok.io/version.buildDate={{.Date}}
|
||||
- -X go.szostok.io/version.commit={{.FullCommit}}
|
||||
- -X go.szostok.io/version.commitDate={{.CommitDate}}
|
||||
- -X go.szostok.io/version.dirtyBuild=false
|
||||
- -X github.com/helmfile/helmfile/pkg/runtime.v1Mode={{.Env.HELMFILE_V1MODE}}
|
||||
goos:
|
||||
- darwin
|
||||
- linux
|
||||
- windows
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
- "386"
|
||||
changelog:
|
||||
use: github-native
|
||||
@@ -0,0 +1,9 @@
|
||||
version: 2
|
||||
|
||||
mkdocs:
|
||||
configuration: mkdocs.yml
|
||||
fail_on_warning: false
|
||||
|
||||
python:
|
||||
install:
|
||||
- requirements: docs/requirements.txt
|
||||
@@ -0,0 +1,2 @@
|
||||
# Helmfile Maintainers
|
||||
* @mumoshu @itscaro @yxxhero @xiaomudk
|
||||
+3
-3
@@ -4,16 +4,16 @@ By contributing to `helmfile`, you accept and agree to the following DCO and lic
|
||||
conditions for your present and future Contributions submitted to the `helmfile` project.
|
||||
|
||||
[DCO](https://developercertificate.org/)
|
||||
[License](https://github.com/roboll/helmfile/blob/master/LICENSE)
|
||||
[License](https://github.com/helmfile/helmfile/blob/master/LICENSE)
|
||||
|
||||
# Developing helmfile
|
||||
|
||||
Locate your `GOPATH`, usually `~/go`, and run:
|
||||
|
||||
```console
|
||||
$ go get github.com/roboll/helmfile
|
||||
$ go get github.com/helmfile/helmfile
|
||||
|
||||
$ cd $GOPATH/src/github.com/roboll/helmfile
|
||||
$ cd $GOPATH/src/github.com/helmfile/helmfile
|
||||
|
||||
$ git checkout -b your-shiny-new-feature origin/master
|
||||
|
||||
|
||||
+71
-26
@@ -1,49 +1,94 @@
|
||||
FROM golang:1.14.2-alpine3.11 as builder
|
||||
FROM golang:1.20-alpine as builder
|
||||
|
||||
RUN apk add --no-cache make git
|
||||
WORKDIR /workspace/helmfile
|
||||
|
||||
COPY go.mod go.sum /workspace/helmfile/
|
||||
RUN go mod download
|
||||
|
||||
COPY . /workspace/helmfile
|
||||
RUN make static-linux
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
FROM alpine:3.11
|
||||
FROM alpine:3.16
|
||||
|
||||
RUN apk add --no-cache ca-certificates git bash curl jq
|
||||
LABEL org.opencontainers.image.source https://github.com/helmfile/helmfile
|
||||
|
||||
ARG HELM_VERSION="v2.17.0"
|
||||
ARG HELM_LOCATION="https://kubernetes-helm.storage.googleapis.com"
|
||||
RUN apk add --no-cache ca-certificates git bash curl jq openssh-client gnupg
|
||||
|
||||
# Set Helm home variables so that also non-root users can use plugins etc.
|
||||
ARG HOME="/helm"
|
||||
ENV HOME="${HOME}"
|
||||
ARG HELM_CACHE_HOME="${HOME}/.cache/helm"
|
||||
ENV HELM_CACHE_HOME="${HELM_CACHE_HOME}"
|
||||
ARG HELM_CONFIG_HOME="${HOME}/.config/helm"
|
||||
ENV HELM_CONFIG_HOME="${HELM_CONFIG_HOME}"
|
||||
ARG HELM_DATA_HOME="${HOME}/.local/share/helm"
|
||||
ENV HELM_DATA_HOME="${HELM_DATA_HOME}"
|
||||
|
||||
ARG HELM_VERSION="v3.11.2"
|
||||
ENV HELM_VERSION="${HELM_VERSION}"
|
||||
ARG HELM_SHA256="781d826daec584f9d50a01f0f7dadfd25a3312217a14aa2fbb85107b014ac8ca"
|
||||
ARG HELM_LOCATION="https://get.helm.sh"
|
||||
ARG HELM_FILENAME="helm-${HELM_VERSION}-linux-amd64.tar.gz"
|
||||
ARG HELM_SHA256="f3bec3c7c55f6a9eb9e6586b8c503f370af92fe987fcbf741f37707606d70296"
|
||||
RUN set -x && \
|
||||
wget ${HELM_LOCATION}/${HELM_FILENAME} && \
|
||||
curl --retry 5 --retry-connrefused -LO "${HELM_LOCATION}/${HELM_FILENAME}" && \
|
||||
echo Verifying ${HELM_FILENAME}... && \
|
||||
sha256sum ${HELM_FILENAME} | grep -q "${HELM_SHA256}" && \
|
||||
echo "${HELM_SHA256} ${HELM_FILENAME}" | sha256sum -c && \
|
||||
echo Extracting ${HELM_FILENAME}... && \
|
||||
tar zxvf ${HELM_FILENAME} && mv /linux-amd64/helm /usr/local/bin/ && \
|
||||
mv /linux-amd64/tiller /usr/local/bin/ && \
|
||||
rm ${HELM_FILENAME} && rm -r /linux-amd64
|
||||
tar xvf "${HELM_FILENAME}" -C /usr/local/bin --strip-components 1 linux-amd64/helm && \
|
||||
rm "${HELM_FILENAME}" && \
|
||||
[ "$(helm version --template '{{.Version}}')" = "${HELM_VERSION}" ]
|
||||
|
||||
# using the install documentation found at https://kubernetes.io/docs/tasks/tools/install-kubectl/
|
||||
# for now but in a future version of alpine (in the testing version at the time of writing)
|
||||
# we should be able to install using apk add.
|
||||
# the sha256 sum can be found at https://storage.googleapis.com/kubernetes-release/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl.sha256
|
||||
# maybe a good idea to automate in the future?
|
||||
ENV KUBECTL_VERSION="v1.18.9"
|
||||
ENV KUBECTL_SHA256="6a68756a2d3d04b4d0f52b00de6493ba2c1fcb28b32f3e4a0e99b3d9f6c4e8ed"
|
||||
RUN set -x & \
|
||||
curl --retry 5 --retry-connrefused -LO "https://storage.googleapis.com/kubernetes-release/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl" && \
|
||||
sha256sum kubectl | grep ${KUBECTL_SHA256} && \
|
||||
ENV KUBECTL_VERSION="v1.25.2"
|
||||
ARG KUBECTL_SHA256="8639f2b9c33d38910d706171ce3d25be9b19fc139d0e3d4627f38ce84f9040eb"
|
||||
RUN set -x && \
|
||||
curl --retry 5 --retry-connrefused -LO "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl" && \
|
||||
echo "${KUBECTL_SHA256} kubectl" | sha256sum -c && \
|
||||
chmod +x kubectl && \
|
||||
mv kubectl /usr/local/bin/kubectl
|
||||
mv kubectl /usr/local/bin/kubectl && \
|
||||
[ "$(kubectl version -o json | jq -r '.clientVersion.gitVersion')" = "${KUBECTL_VERSION}" ]
|
||||
|
||||
RUN ["helm", "init", "--client-only", "--stable-repo-url", "https://charts.helm.sh/stable"]
|
||||
RUN helm plugin install https://github.com/databus23/helm-diff && \
|
||||
helm plugin install https://github.com/futuresimple/helm-secrets && \
|
||||
helm plugin install https://github.com/hypnoglow/helm-s3.git && \
|
||||
helm plugin install https://github.com/aslafy-z/helm-git.git && \
|
||||
helm plugin install https://github.com/rimusz/helm-tiller
|
||||
ENV KUSTOMIZE_VERSION="v4.5.7"
|
||||
ARG KUSTOMIZE_SHA256="701e3c4bfa14e4c520d481fdf7131f902531bfc002cb5062dcf31263a09c70c9"
|
||||
ARG KUSTOMIZE_FILENAME="kustomize_${KUSTOMIZE_VERSION}_linux_amd64.tar.gz"
|
||||
RUN set -x && \
|
||||
curl --retry 5 --retry-connrefused -LO "https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize/${KUSTOMIZE_VERSION}/${KUSTOMIZE_FILENAME}" && \
|
||||
echo "${KUSTOMIZE_SHA256} ${KUSTOMIZE_FILENAME}" | sha256sum -c && \
|
||||
tar xvf "${KUSTOMIZE_FILENAME}" -C /usr/local/bin && \
|
||||
rm "${KUSTOMIZE_FILENAME}" && \
|
||||
kustomize version --short | grep "kustomize/${KUSTOMIZE_VERSION}"
|
||||
|
||||
ENV SOPS_VERSION="v3.7.3"
|
||||
ARG SOPS_FILENAME="sops-${SOPS_VERSION}.linux.amd64"
|
||||
RUN set -x && \
|
||||
curl --retry 5 --retry-connrefused -LO "https://github.com/mozilla/sops/releases/download/${SOPS_VERSION}/${SOPS_FILENAME}" && \
|
||||
chmod +x "${SOPS_FILENAME}" && \
|
||||
mv "${SOPS_FILENAME}" /usr/local/bin/sops && \
|
||||
sops --version | grep -E "^sops ${SOPS_VERSION#v}"
|
||||
|
||||
ENV AGE_VERSION="v1.0.0"
|
||||
ARG AGE_FILENAME="age-${AGE_VERSION}-linux-amd64.tar.gz"
|
||||
RUN set -x && \
|
||||
curl --retry 5 --retry-connrefused -LO "https://github.com/FiloSottile/age/releases/download/${AGE_VERSION}/${AGE_FILENAME}" && \
|
||||
tar xvf "${AGE_FILENAME}" -C /usr/local/bin --strip-components 1 age/age age/age-keygen && \
|
||||
rm "${AGE_FILENAME}" && \
|
||||
[ "$(age --version)" = "${AGE_VERSION}" ] && \
|
||||
[ "$(age-keygen --version)" = "${AGE_VERSION}" ]
|
||||
|
||||
RUN helm plugin install https://github.com/databus23/helm-diff --version v3.6.0 && \
|
||||
helm plugin install https://github.com/jkroepke/helm-secrets --version v4.1.1 && \
|
||||
helm plugin install https://github.com/hypnoglow/helm-s3.git --version v0.14.0 && \
|
||||
helm plugin install https://github.com/aslafy-z/helm-git.git --version v0.12.0 && \
|
||||
rm -rf ${HELM_CACHE_HOME}/plugins
|
||||
|
||||
# Allow users other than root to use helm plugins located in root home
|
||||
RUN chmod 751 ${HOME}
|
||||
|
||||
COPY --from=builder /workspace/helmfile/dist/helmfile_linux_amd64 /usr/local/bin/helmfile
|
||||
|
||||
CMD ["/usr/local/bin/helmfile", "--help"]
|
||||
CMD ["/usr/local/bin/helmfile"]
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
FROM golang:1.20-alpine as builder
|
||||
|
||||
RUN apk add --no-cache make git
|
||||
WORKDIR /workspace/helmfile
|
||||
|
||||
COPY go.mod go.sum /workspace/helmfile/
|
||||
RUN go mod download
|
||||
|
||||
COPY . /workspace/helmfile
|
||||
RUN make static-linux
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
FROM debian:stable-slim
|
||||
|
||||
LABEL org.opencontainers.image.source https://github.com/helmfile/helmfile
|
||||
|
||||
RUN apt update -qq && \
|
||||
apt install --no-install-recommends -y \
|
||||
ca-certificates \
|
||||
gnupg \
|
||||
git bash curl jq wget openssh-client && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Set Helm home variables so that also non-root users can use plugins etc.
|
||||
ARG HOME="/helm"
|
||||
ENV HOME="${HOME}"
|
||||
ARG HELM_CACHE_HOME="${HOME}/.cache/helm"
|
||||
ENV HELM_CACHE_HOME="${HELM_CACHE_HOME}"
|
||||
ARG HELM_CONFIG_HOME="${HOME}/.config/helm"
|
||||
ENV HELM_CONFIG_HOME="${HELM_CONFIG_HOME}"
|
||||
ARG HELM_DATA_HOME="${HOME}/.local/share/helm"
|
||||
ENV HELM_DATA_HOME="${HELM_DATA_HOME}"
|
||||
|
||||
ARG HELM_VERSION="v3.11.2"
|
||||
ENV HELM_VERSION="${HELM_VERSION}"
|
||||
ARG HELM_SHA256="781d826daec584f9d50a01f0f7dadfd25a3312217a14aa2fbb85107b014ac8ca"
|
||||
ARG HELM_LOCATION="https://get.helm.sh"
|
||||
ARG HELM_FILENAME="helm-${HELM_VERSION}-linux-amd64.tar.gz"
|
||||
RUN set -x && \
|
||||
curl --retry 5 --retry-connrefused -LO "${HELM_LOCATION}/${HELM_FILENAME}" && \
|
||||
echo Verifying ${HELM_FILENAME}... && \
|
||||
echo "${HELM_SHA256} ${HELM_FILENAME}" | sha256sum -c && \
|
||||
echo Extracting ${HELM_FILENAME}... && \
|
||||
tar xvf "${HELM_FILENAME}" -C /usr/local/bin --strip-components 1 linux-amd64/helm && \
|
||||
rm "${HELM_FILENAME}" && \
|
||||
[ "$(helm version --template '{{.Version}}')" = "${HELM_VERSION}" ]
|
||||
|
||||
# using the install documentation found at https://kubernetes.io/docs/tasks/tools/install-kubectl/
|
||||
# for now but in a future version of alpine (in the testing version at the time of writing)
|
||||
# we should be able to install using apk add.
|
||||
ENV KUBECTL_VERSION="v1.25.2"
|
||||
ARG KUBECTL_SHA256="8639f2b9c33d38910d706171ce3d25be9b19fc139d0e3d4627f38ce84f9040eb"
|
||||
RUN set -x && \
|
||||
curl --retry 5 --retry-connrefused -LO "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl" && \
|
||||
echo "${KUBECTL_SHA256} kubectl" | sha256sum -c && \
|
||||
chmod +x kubectl && \
|
||||
mv kubectl /usr/local/bin/kubectl && \
|
||||
[ "$(kubectl version -o json | jq -r '.clientVersion.gitVersion')" = "${KUBECTL_VERSION}" ]
|
||||
|
||||
ENV KUSTOMIZE_VERSION="v4.5.7"
|
||||
ARG KUSTOMIZE_SHA256="701e3c4bfa14e4c520d481fdf7131f902531bfc002cb5062dcf31263a09c70c9"
|
||||
ARG KUSTOMIZE_FILENAME="kustomize_${KUSTOMIZE_VERSION}_linux_amd64.tar.gz"
|
||||
RUN set -x && \
|
||||
curl --retry 5 --retry-connrefused -LO "https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize/${KUSTOMIZE_VERSION}/${KUSTOMIZE_FILENAME}" && \
|
||||
echo "${KUSTOMIZE_SHA256} ${KUSTOMIZE_FILENAME}" | sha256sum -c && \
|
||||
tar xvf "${KUSTOMIZE_FILENAME}" -C /usr/local/bin && \
|
||||
rm "${KUSTOMIZE_FILENAME}" && \
|
||||
kustomize version --short | grep "kustomize/${KUSTOMIZE_VERSION}"
|
||||
|
||||
ENV SOPS_VERSION="v3.7.3"
|
||||
ARG SOPS_FILENAME="sops-${SOPS_VERSION}.linux.amd64"
|
||||
RUN set -x && \
|
||||
curl --retry 5 --retry-connrefused -LO "https://github.com/mozilla/sops/releases/download/${SOPS_VERSION}/${SOPS_FILENAME}" && \
|
||||
chmod +x "${SOPS_FILENAME}" && \
|
||||
mv "${SOPS_FILENAME}" /usr/local/bin/sops && \
|
||||
sops --version | grep -E "^sops ${SOPS_VERSION#v}"
|
||||
|
||||
ENV AGE_VERSION="v1.0.0"
|
||||
ARG AGE_FILENAME="age-${AGE_VERSION}-linux-amd64.tar.gz"
|
||||
RUN set -x && \
|
||||
curl --retry 5 --retry-connrefused -LO "https://github.com/FiloSottile/age/releases/download/${AGE_VERSION}/${AGE_FILENAME}" && \
|
||||
tar xvf "${AGE_FILENAME}" -C /usr/local/bin --strip-components 1 age/age age/age-keygen && \
|
||||
rm "${AGE_FILENAME}" && \
|
||||
[ "$(age --version)" = "${AGE_VERSION}" ] && \
|
||||
[ "$(age-keygen --version)" = "${AGE_VERSION}" ]
|
||||
|
||||
RUN helm plugin install https://github.com/databus23/helm-diff --version v3.6.0 && \
|
||||
helm plugin install https://github.com/jkroepke/helm-secrets --version v4.1.1 && \
|
||||
helm plugin install https://github.com/hypnoglow/helm-s3.git --version v0.14.0 && \
|
||||
helm plugin install https://github.com/aslafy-z/helm-git.git --version v0.12.0 && \
|
||||
rm -rf ${HELM_CACHE_HOME}/plugins
|
||||
|
||||
# Allow users other than root to use helm plugins located in root home
|
||||
RUN chmod 751 ${HOME}
|
||||
|
||||
COPY --from=builder /workspace/helmfile/dist/helmfile_linux_amd64 /usr/local/bin/helmfile
|
||||
|
||||
CMD ["/usr/local/bin/helmfile"]
|
||||
@@ -1,55 +0,0 @@
|
||||
FROM golang:1.14.2-alpine3.11 as builder
|
||||
|
||||
RUN apk add --no-cache make git
|
||||
WORKDIR /workspace/helmfile
|
||||
COPY . /workspace/helmfile
|
||||
RUN make static-linux
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
FROM alpine:3.11
|
||||
|
||||
RUN apk add --no-cache ca-certificates git bash curl jq
|
||||
|
||||
ARG HELM_VERSION="v3.5.0"
|
||||
ARG HELM_LOCATION="https://get.helm.sh"
|
||||
ARG HELM_FILENAME="helm-${HELM_VERSION}-linux-amd64.tar.gz"
|
||||
ARG HELM_SHA256="3fff0354d5fba4c73ebd5db59a59db72f8a5bbe1117a0b355b0c2983e98db95b"
|
||||
RUN set -x && \
|
||||
wget ${HELM_LOCATION}/${HELM_FILENAME} && \
|
||||
echo Verifying ${HELM_FILENAME}... && \
|
||||
sha256sum ${HELM_FILENAME} | grep -q "${HELM_SHA256}" && \
|
||||
echo Extracting ${HELM_FILENAME}... && \
|
||||
tar zxvf ${HELM_FILENAME} && mv /linux-amd64/helm /usr/local/bin/ && \
|
||||
rm ${HELM_FILENAME} && rm -r /linux-amd64
|
||||
|
||||
# using the install documentation found at https://kubernetes.io/docs/tasks/tools/install-kubectl/
|
||||
# for now but in a future version of alpine (in the testing version at the time of writing)
|
||||
# we should be able to install using apk add.
|
||||
# the sha256 sum can be found at https://storage.googleapis.com/kubernetes-release/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl.sha256
|
||||
# maybe a good idea to automate in the future?
|
||||
ENV KUBECTL_VERSION="v1.18.9"
|
||||
ENV KUBECTL_SHA256="6a68756a2d3d04b4d0f52b00de6493ba2c1fcb28b32f3e4a0e99b3d9f6c4e8ed"
|
||||
RUN set -x && \
|
||||
curl --retry 5 --retry-connrefused -LO "https://storage.googleapis.com/kubernetes-release/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl" && \
|
||||
sha256sum kubectl | grep ${KUBECTL_SHA256} && \
|
||||
chmod +x kubectl && \
|
||||
mv kubectl /usr/local/bin/kubectl
|
||||
|
||||
ENV KUSTOMIZE_VERSION="v3.8.8"
|
||||
ENV KUSTOMIZE_SHA256="175938206f23956ec18dac3da0816ea5b5b485a8493a839da278faac82e3c303"
|
||||
RUN set -x && \
|
||||
curl --retry 5 --retry-connrefused -LO https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize/${KUSTOMIZE_VERSION}/kustomize_${KUSTOMIZE_VERSION}_linux_amd64.tar.gz && \
|
||||
sha256sum kustomize_${KUSTOMIZE_VERSION}_linux_amd64.tar.gz | grep ${KUSTOMIZE_SHA256} && \
|
||||
tar zxf kustomize_${KUSTOMIZE_VERSION}_linux_amd64.tar.gz && \
|
||||
rm kustomize_${KUSTOMIZE_VERSION}_linux_amd64.tar.gz && \
|
||||
mv kustomize /usr/local/bin/kustomize
|
||||
|
||||
RUN helm plugin install https://github.com/databus23/helm-diff --version v3.1.3 && \
|
||||
helm plugin install https://github.com/futuresimple/helm-secrets && \
|
||||
helm plugin install https://github.com/hypnoglow/helm-s3.git && \
|
||||
helm plugin install https://github.com/aslafy-z/helm-git.git
|
||||
|
||||
COPY --from=builder /workspace/helmfile/dist/helmfile_linux_amd64 /usr/local/bin/helmfile
|
||||
|
||||
CMD ["/usr/local/bin/helmfile"]
|
||||
@@ -0,0 +1,99 @@
|
||||
FROM golang:1.20-alpine as builder
|
||||
|
||||
RUN apk add --no-cache make git
|
||||
WORKDIR /workspace/helmfile
|
||||
|
||||
COPY go.mod go.sum /workspace/helmfile/
|
||||
RUN go mod download
|
||||
|
||||
COPY . /workspace/helmfile
|
||||
RUN make static-linux
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
FROM ubuntu:20.04
|
||||
|
||||
LABEL org.opencontainers.image.source https://github.com/helmfile/helmfile
|
||||
|
||||
RUN apt update -qq && \
|
||||
apt install --no-install-recommends -y \
|
||||
ca-certificates \
|
||||
gnupg \
|
||||
git bash curl jq wget openssh-client && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Set Helm home variables so that also non-root users can use plugins etc.
|
||||
ARG HOME="/helm"
|
||||
ENV HOME="${HOME}"
|
||||
ARG HELM_CACHE_HOME="${HOME}/.cache/helm"
|
||||
ENV HELM_CACHE_HOME="${HELM_CACHE_HOME}"
|
||||
ARG HELM_CONFIG_HOME="${HOME}/.config/helm"
|
||||
ENV HELM_CONFIG_HOME="${HELM_CONFIG_HOME}"
|
||||
ARG HELM_DATA_HOME="${HOME}/.local/share/helm"
|
||||
ENV HELM_DATA_HOME="${HELM_DATA_HOME}"
|
||||
|
||||
ARG HELM_VERSION="v3.11.2"
|
||||
ENV HELM_VERSION="${HELM_VERSION}"
|
||||
ARG HELM_SHA256="781d826daec584f9d50a01f0f7dadfd25a3312217a14aa2fbb85107b014ac8ca"
|
||||
ARG HELM_LOCATION="https://get.helm.sh"
|
||||
ARG HELM_FILENAME="helm-${HELM_VERSION}-linux-amd64.tar.gz"
|
||||
RUN set -x && \
|
||||
curl --retry 5 --retry-connrefused -LO "${HELM_LOCATION}/${HELM_FILENAME}" && \
|
||||
echo Verifying ${HELM_FILENAME}... && \
|
||||
echo "${HELM_SHA256} ${HELM_FILENAME}" | sha256sum -c && \
|
||||
echo Extracting ${HELM_FILENAME}... && \
|
||||
tar xvf "${HELM_FILENAME}" -C /usr/local/bin --strip-components 1 linux-amd64/helm && \
|
||||
rm "${HELM_FILENAME}" && \
|
||||
[ "$(helm version --template '{{.Version}}')" = "${HELM_VERSION}" ]
|
||||
|
||||
# using the install documentation found at https://kubernetes.io/docs/tasks/tools/install-kubectl/
|
||||
# for now but in a future version of alpine (in the testing version at the time of writing)
|
||||
# we should be able to install using apk add.
|
||||
ENV KUBECTL_VERSION="v1.25.2"
|
||||
ARG KUBECTL_SHA256="8639f2b9c33d38910d706171ce3d25be9b19fc139d0e3d4627f38ce84f9040eb"
|
||||
RUN set -x && \
|
||||
curl --retry 5 --retry-connrefused -LO "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl" && \
|
||||
echo "${KUBECTL_SHA256} kubectl" | sha256sum -c && \
|
||||
chmod +x kubectl && \
|
||||
mv kubectl /usr/local/bin/kubectl && \
|
||||
[ "$(kubectl version -o json | jq -r '.clientVersion.gitVersion')" = "${KUBECTL_VERSION}" ]
|
||||
|
||||
ENV KUSTOMIZE_VERSION="v4.5.7"
|
||||
ARG KUSTOMIZE_SHA256="701e3c4bfa14e4c520d481fdf7131f902531bfc002cb5062dcf31263a09c70c9"
|
||||
ARG KUSTOMIZE_FILENAME="kustomize_${KUSTOMIZE_VERSION}_linux_amd64.tar.gz"
|
||||
RUN set -x && \
|
||||
curl --retry 5 --retry-connrefused -LO "https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize/${KUSTOMIZE_VERSION}/${KUSTOMIZE_FILENAME}" && \
|
||||
echo "${KUSTOMIZE_SHA256} ${KUSTOMIZE_FILENAME}" | sha256sum -c && \
|
||||
tar xvf "${KUSTOMIZE_FILENAME}" -C /usr/local/bin && \
|
||||
rm "${KUSTOMIZE_FILENAME}" && \
|
||||
kustomize version --short | grep "kustomize/${KUSTOMIZE_VERSION}"
|
||||
|
||||
ENV SOPS_VERSION="v3.7.3"
|
||||
ARG SOPS_FILENAME="sops-${SOPS_VERSION}.linux.amd64"
|
||||
RUN set -x && \
|
||||
curl --retry 5 --retry-connrefused -LO "https://github.com/mozilla/sops/releases/download/${SOPS_VERSION}/${SOPS_FILENAME}" && \
|
||||
chmod +x "${SOPS_FILENAME}" && \
|
||||
mv "${SOPS_FILENAME}" /usr/local/bin/sops && \
|
||||
sops --version | grep -E "^sops ${SOPS_VERSION#v}"
|
||||
|
||||
ENV AGE_VERSION="v1.0.0"
|
||||
ARG AGE_FILENAME="age-${AGE_VERSION}-linux-amd64.tar.gz"
|
||||
RUN set -x && \
|
||||
curl --retry 5 --retry-connrefused -LO "https://github.com/FiloSottile/age/releases/download/${AGE_VERSION}/${AGE_FILENAME}" && \
|
||||
tar xvf "${AGE_FILENAME}" -C /usr/local/bin --strip-components 1 age/age age/age-keygen && \
|
||||
rm "${AGE_FILENAME}" && \
|
||||
[ "$(age --version)" = "${AGE_VERSION}" ] && \
|
||||
[ "$(age-keygen --version)" = "${AGE_VERSION}" ]
|
||||
|
||||
RUN helm plugin install https://github.com/databus23/helm-diff --version v3.6.0 && \
|
||||
helm plugin install https://github.com/jkroepke/helm-secrets --version v4.1.1 && \
|
||||
helm plugin install https://github.com/hypnoglow/helm-s3.git --version v0.14.0 && \
|
||||
helm plugin install https://github.com/aslafy-z/helm-git.git --version v0.12.0 && \
|
||||
rm -rf ${HELM_CACHE_HOME}/plugins
|
||||
|
||||
# Allow users other than root to use helm plugins located in root home
|
||||
RUN chmod 751 ${HOME}
|
||||
|
||||
COPY --from=builder /workspace/helmfile/dist/helmfile_linux_amd64 /usr/local/bin/helmfile
|
||||
|
||||
CMD ["/usr/local/bin/helmfile"]
|
||||
@@ -1,16 +1,41 @@
|
||||
ORG ?= $(shell basename $(realpath ..))
|
||||
PKGS := $(shell go list ./... | grep -v /vendor/)
|
||||
|
||||
TAG ?= $(shell git describe --tags --abbrev=0 HEAD)
|
||||
LAST = $(shell git describe --tags --abbrev=0 HEAD^)
|
||||
BODY = "`git log ${LAST}..HEAD --oneline --decorate` `printf '\n\#\#\# [Build Info](${BUILD_URL})'`"
|
||||
DATE_FMT = +"%Y-%m-%dT%H:%M:%S%z"
|
||||
ifdef SOURCE_DATE_EPOCH
|
||||
BUILD_DATE ?= $(shell date -u -d "@$(SOURCE_DATE_EPOCH)" "$(DATE_FMT)" 2>/dev/null || date -u -r "$(SOURCE_DATE_EPOCH)" "$(DATE_FMT)" 2>/dev/null || date -u "$(DATE_FMT)")
|
||||
else
|
||||
BUILD_DATE ?= $(shell date "$(DATE_FMT)")
|
||||
endif
|
||||
|
||||
|
||||
# The ldflags for the Go build process to set the version related data
|
||||
GO_BUILD_VERSION_LDFLAGS=\
|
||||
-X go.szostok.io/version.version=$(TAG) \
|
||||
-X go.szostok.io/version.buildDate=$(BUILD_DATE) \
|
||||
-X go.szostok.io/version.commit=$(shell git rev-parse --short HEAD) \
|
||||
-X go.szostok.io/version.commitDate=$(shell git log -1 --date=format:"%Y-%m-%dT%H:%M:%S%z" --format=%cd) \
|
||||
-X go.szostok.io/version.dirtyBuild=false
|
||||
|
||||
|
||||
build:
|
||||
go build -ldflags '-X github.com/roboll/helmfile/pkg/app/version.Version=${TAG}' ${TARGETS}
|
||||
go build -ldflags="$(GO_BUILD_VERSION_LDFLAGS)" ${TARGETS}
|
||||
.PHONY: build
|
||||
|
||||
build-v1:
|
||||
go build -ldflags="$(GO_BUILD_VERSION_LDFLAGS) -X github.com/helmfile/helmfile/pkg/runtime.v1Mode=true" ${TARGETS}
|
||||
.PHONY: build-v1
|
||||
|
||||
generate:
|
||||
go generate ${PKGS}
|
||||
.PHONY: generate
|
||||
|
||||
fmt:
|
||||
go fmt ${PKGS}
|
||||
gci write --skip-generated -s standard -s default -s 'prefix(github.com/helmfile/helmfile)' .
|
||||
.PHONY: fmt
|
||||
|
||||
check:
|
||||
@@ -18,27 +43,38 @@ check:
|
||||
.PHONY: check
|
||||
|
||||
build-test-tools:
|
||||
go build test/diff-yamls.go
|
||||
go build test/diff-yamls/diff-yamls.go
|
||||
curl --progress-bar --location https://github.com/homeport/dyff/releases/download/v1.5.6/dyff_1.5.6_linux_amd64.tar.gz | tar -xzf - -C `pwd` dyff
|
||||
.PHONY: build-test-tools
|
||||
|
||||
test:
|
||||
go test -v ${PKGS} -cover -race -p=1
|
||||
@which helm &> /dev/null || (echo "helm binary not found. Please see: https://helm.sh/docs/intro/install/" && exit 1)
|
||||
go build -o helmfile .
|
||||
go test -v ${PKGS} -coverprofile cover.out -race -p=1
|
||||
go tool cover -func cover.out
|
||||
.PHONY: test
|
||||
|
||||
integration:
|
||||
bash test/integration/run.sh
|
||||
.PHONY: integration
|
||||
|
||||
integration/vagrant:
|
||||
$(MAKE) build GOOS=linux GOARCH=amd64
|
||||
$(MAKE) build-test-tools GOOS=linux GOARCH=amd64
|
||||
vagrant up
|
||||
vagrant ssh -c 'HELMFILE_HELM3=1 make -C /vagrant integration'
|
||||
.PHONY: integration/vagrant
|
||||
|
||||
cross:
|
||||
env CGO_ENABLED=0 gox -os 'windows darwin linux' -arch '386 amd64 arm64' -osarch '!darwin/arm64' -output "dist/{{.Dir}}_{{.OS}}_{{.Arch}}" -ldflags '-X github.com/roboll/helmfile/pkg/app/version.Version=${TAG}' ${TARGETS}
|
||||
env CGO_ENABLED=0 gox -parallel 4 -os 'windows darwin linux' -arch '386 amd64 arm64' -osarch '!darwin/386' -output "dist/{{.Dir}}_{{.OS}}_{{.Arch}}" -ldflags="$(GO_BUILD_VERSION_LDFLAGS)" ${TARGETS}
|
||||
.PHONY: cross
|
||||
|
||||
static-linux:
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOFLAGS=-mod=vendor go build -o "dist/helmfile_linux_amd64" -ldflags '-X github.com/roboll/helmfile/pkg/app/version.Version=${TAG}' ${TARGETS}
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOFLAGS=-mod=readonly go build -o "dist/helmfile_linux_amd64" -ldflags="$(GO_BUILD_VERSION_LDFLAGS)" ${TARGETS}
|
||||
.PHONY: static-linux
|
||||
|
||||
install:
|
||||
env CGO_ENABLED=0 go install -ldflags '-X github.com/roboll/helmfile/pkg/app/version.Version=${TAG}' ${TARGETS}
|
||||
env CGO_ENABLED=0 go install -ldflags="$(GO_BUILD_VERSION_LDFLAGS)" ${TARGETS}
|
||||
.PHONY: install
|
||||
|
||||
clean:
|
||||
@@ -51,7 +87,7 @@ pristine: generate fmt
|
||||
.PHONY: pristine
|
||||
|
||||
release: pristine cross
|
||||
@ghr -b ${BODY} -t ${GITHUB_TOKEN} -u ${ORG} -recreate ${TAG} dist
|
||||
@ghr -b ${BODY} -t ${GITHUB_TOKEN} -u ${ORG} ${TAG} dist
|
||||
.PHONY: release
|
||||
|
||||
image:
|
||||
@@ -63,21 +99,16 @@ run: image
|
||||
push: image
|
||||
docker push quay.io/${ORG}/helmfile:${TAG}
|
||||
|
||||
image/debian:
|
||||
docker build -f Dockerfile.debian -t quay.io/${ORG}/helmfile:${TAG}-stable-slim .
|
||||
|
||||
image/helm3:
|
||||
docker build -f Dockerfile.helm3 -t quay.io/${ORG}/helmfile:helm3-${TAG} .
|
||||
|
||||
push/helm3: image/helm3
|
||||
docker push quay.io/${ORG}/helmfile:helm3-${TAG}
|
||||
push/debian: image/debian
|
||||
docker push quay.io/${ORG}/helmfile:${TAG}-stable-slim
|
||||
|
||||
tools:
|
||||
go get -u github.com/tcnksm/ghr github.com/mitchellh/gox
|
||||
.PHONY: tools
|
||||
|
||||
TAG = $(shell git describe --tags --abbrev=0 HEAD)
|
||||
LAST = $(shell git describe --tags --abbrev=0 HEAD^)
|
||||
BODY = "`git log ${LAST}..HEAD --oneline --decorate` `printf '\n\#\#\# [Build Info](${BUILD_URL})'`"
|
||||
|
||||
release/minor:
|
||||
git checkout master
|
||||
git pull --rebase origin master
|
||||
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
<!-- markdownlint-configure-file {
|
||||
"MD013": {
|
||||
"code_blocks": false,
|
||||
"tables": false
|
||||
},
|
||||
"MD033": false,
|
||||
"MD041": false
|
||||
} -->
|
||||
|
||||
<div align="center" markdown="1">
|
||||
|
||||
# Helmfile
|
||||
|
||||
[](https://github.com/helmfile/helmfile/actions/workflows/ci.yaml?query=branch%3Amain)
|
||||
[](https://github.com/helmfile/helmfile/pkgs/container/helmfile)
|
||||
[](https://goreportcard.com/report/github.com/helmfile/helmfile)
|
||||
[](https://slack.sweetops.com)
|
||||
[](https://helmfile.readthedocs.io/en/latest/)
|
||||
|
||||
声明式Helm Chart管理工具
|
||||
<br />
|
||||
|
||||
</div>
|
||||
|
||||
[English](./README.md) | [简体中文]
|
||||
|
||||
# 关于
|
||||
|
||||
Helmfile 是一个声明式Helm Chart管理工具
|
||||
|
||||
> Helmfile 项目已经从原仓库 roboll/helmfile 转移到了 helmfile/helmfile。有关更多信息,请参见 roboll/helmfile#1824
|
||||
|
||||
## 特性
|
||||
|
||||
- 通过一个YAML集中管理集群中多个Helm Chart, 类似于Docker Compose统一管理Docker
|
||||
- 对Helm Chart根据部署环境区分管理
|
||||
- Helm Chart版本控制,比如指定版本范围、锁定某一版本
|
||||
- 快速识别 Kubernetes 集群内已经部署应用与新更改之间的差异
|
||||
- Helmfile支持Go Templates语法定义Helm Chart
|
||||
- 在部署阶段支持配置hook,可以执行脚本等,实现变量远程获取,报错清理,成功提醒等
|
||||
|
||||
|
||||
## 安装
|
||||
|
||||
**方式1: 二进制安装**
|
||||
|
||||
下载 [releases](https://github.com/helmfile/helmfile/releases)
|
||||
|
||||
**方式2: 包管理工具**
|
||||
|
||||
* Archlinux: `pacman -S helmfile`
|
||||
* openSUSE: `zypper in helmfile`
|
||||
* Windows: ([scoop](https://scoop.sh/)): `scoop install helmfile`
|
||||
* macOS ([homebrew](https://brew.sh/)): `brew install helmfile`
|
||||
|
||||
**方式3: 容器**
|
||||
|
||||
详细见:[run as a container](https://helmfile.readthedocs.io/en/latest/#running-as-a-container)
|
||||
|
||||
> 安装后请运行一次 `helmfile init`。 检查[helm-diff](https://github.com/databus23/helm-diff) 等插件安装正确。
|
||||
|
||||
## 使用
|
||||
|
||||
让我们从最简单的 helmfile 开始,逐渐改进它以适应您的用例!
|
||||
|
||||
假设表示您 helm releases 的期望状态的 helmfile.yaml 看起来像这样:
|
||||
|
||||
```yaml
|
||||
repositories:
|
||||
- name: prometheus-community
|
||||
url: https://prometheus-community.github.io/helm-charts
|
||||
|
||||
releases:
|
||||
- name: prom-norbac-ubuntu
|
||||
namespace: prometheus
|
||||
chart: prometheus-community/prometheus
|
||||
set:
|
||||
- name: rbac.create
|
||||
value: false
|
||||
```
|
||||
|
||||
通过运行来同步您的Kubernetes集群状态到期望状态:
|
||||
|
||||
```console
|
||||
helmfile apply
|
||||
```
|
||||
|
||||
恭喜!您现在已经在集群内部运行了第一个Prometheus部署。
|
||||
|
||||
|
||||
## 文档
|
||||
|
||||
[Documentation](https://helmfile.readthedocs.io/)
|
||||
|
||||
|
||||
## 参与贡献
|
||||
|
||||
欢迎贡献! 让我们一起使helmfile变得更好:[贡献指南](https://helmfile.readthedocs.io/en/latest/contributing/)
|
||||
|
||||
|
||||
## 使用者
|
||||
|
||||
Helmfile 已经被许多用户在生产环境中使用:
|
||||
|
||||
* [gitlab.com](https://gitlab.com)
|
||||
* [reddit.com](https://reddit.com)
|
||||
* [Jenkins](https://jenkins.io)
|
||||
* ...
|
||||
|
||||
更多用户请参见: [Users](https://helmfile.readthedocs.io/en/latest/users/)
|
||||
|
||||
|
||||
## License
|
||||
|
||||
[MIT](https://github.com/helmfile/helmfile/blob/main/LICENSE)
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
# Security Policy
|
||||
|
||||
## Sponsoring the project
|
||||
|
||||
This project is maintained by a small team of four and therefore lacks the resource to provide security fixes in a very timely manner.
|
||||
|
||||
That said, even though we are very passionate about making Helmfile rock solid security wise, all issues are handled on the best effort basis.
|
||||
|
||||
If you have important business(es) that relies on this project, please consider sponsoring the maintainers, so that they can commit more on providing such service.
|
||||
|
||||
> *Note* that we don't currently have project-wide sponsorship enabled as we don't know how to share the amount of sponsorships with fairness.
|
||||
> Please sponsor individuals instead! Thanks for your understanding.
|
||||
|
||||
## Supported Versions
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 0.144.0 | :white_check_mark: |
|
||||
| < 0.144.0| :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
To report a security issue, please email helmfile-security@googlegroups.com with a description of the issue, the steps you took to create the issue, affected versions, and, if known, mitigations for the issue.
|
||||
|
||||
A maintainer will try to respond within 5 working days. If the issue is confirmed as a vulnerability, a Security Advisory will be opened. This project currently tries to follow a 90 day disclosure timeline.
|
||||
@@ -10,6 +10,7 @@ information to this file.
|
||||
| Organization | Workload | More Info | Location | Added |
|
||||
|---|---|---|---|---|
|
||||
| [reddit.com](https://www.reddit.com/) | production | [GitHub issue](https://github.com/roboll/helmfile/issues/96#issue-312012249), [Talk](https://www.slideshare.net/GregoryTaylor11/helm-at-reddit-from-local-dev-staging-to-production) | San Francisco, CA | April 2018 |
|
||||
| [gitlab.com](https://gitlab.com/) | production | [Gitlab Project](https://gitlab.com/gitlab-com/gl-infra/k8s-workloads/gitlab-com/-/tree/master) | * | August 2022 |
|
||||
| [ricardo.ch](https://www.ricardo.ch/) | production | We're deploying our complete application platform using Helmfile. | Zug, Switzerland | April 2018 |
|
||||
| [Hellofresh](https://engineering.hellofresh.com/) | production | We've been using helmfile in production since April 2018 for deploying all our infrastructure applications | Berlin, Germany | April 2018 |
|
||||
| [Cherre](https://cherre.com/) | production | We have no public posts about using Helmfile, but we have been using it for a long time now | New York, NY | October 2018 |
|
||||
@@ -24,7 +25,14 @@ information to this file.
|
||||
| [American Express](https://www.americanexpress.com) | proof-of-concept | Orchestration of both internal cluster workloads and local developer environments. | London, GB | January 2020 |
|
||||
| [Sportradar](https://www.sportradar.com) | production | Since mid-2019, we've been deploying our core infrastructure and several application stacks with Helmfile. | St. Gallen, Switzerland | March 2020 |
|
||||
| [PedidosYa](https://www.pedidosya.com) | production | | Montevideo, Uruguay | June 2020 |
|
||||
| [Jenkins OSS](https://jenkins.io) | production | [jenkins-infra/charts](https://github.com/jenkins-infra/charts) | * | July 2020 |
|
||||
| [Jenkins](https://jenkins.io) | production | [jenkins-infra/kubernetes-management](https://github.com/jenkins-infra/kubernetes-management) | * | July 2020 |
|
||||
| [SettleMint](https://settlemint.com) | production | The SettleMint platform allows enterprises to spin up k8s clusters and deploy production grade blockchain networks and additional services. Helmfile is in charge of deploying these networks and services on demand out of the self service management ui. | Belgium, Singapore, UAE, India | October 2020 |
|
||||
| [AutoTrader (UK)](https://www.autotrader.co.uk) | production | We've used Helmfile for 2+ years to deploy 400+ services | UK | October 2020 |
|
||||
| [Trend Micro](https://www.trendmicro.com) | production | We manage 9 k8s clusters in a mono git repository with Helmfile | Taipei, Taiwan | December 2019 |
|
||||
| [William Hill](https://www.williamhill.com) | production | We have standardised on Helmfile for managing both our application services and our Kubernetes platform components. | International | March 2021 |
|
||||
| [The Hyve](https://www.thehyve.nl) | production | We've been using Helmfile since 2019 to deploy [Radar-base applications](https://github.com/RADAR-base/RADAR-Kubernetes). [Blog Post](https://www.thehyve.nl/articles/kubernetes-added-to-radar-base) | Utrecht, Netherlands | April 2021 |
|
||||
| [IKEA US](https://www.ikea.com) | production | We started using Helmfile this year to shrink the boilerplate involved in deploying or microservices to cloud Kubernetes clusters. | Utrecht, Netherlands | April 2021 |
|
||||
| [ShareGate](https://www.sharegate.com) | production | Used to deploy and sync our kubernetes workloads in Azure, using Azure Pipelines and custom extensions to [install](https://marketplace.visualstudio.com/items?itemName=GSoft.HelmfileInstaller) and [execute](https://marketplace.visualstudio.com/items?itemName=GSoft.HelmfileRunner) the cli. | Montreal, Canada | June 2021 |
|
||||
| [subshell](https://subshell.com) | production | We're using helmfile since 2021 to deploy all our Kubernetes workloads into our clusters. We love helmfile for its simplicity and power. Thank you! | Hamburg, Germany | August 2022 |
|
||||
| [Norddeutscher Rundfunk](https://www.ndr.de) | production | Using Helmfile since 2020 to deploy workloads to several similar clusters (dev, qa, prod, test, etc.) for sites tagesschau.de and sportschau.de. Thank you so much for your awesome work! | Hamburg, Germany | August 2022 |
|
||||
<!-- TABLE_END -->
|
||||
|
||||
Vendored
+19
@@ -0,0 +1,19 @@
|
||||
Vagrant.configure("2") do |config|
|
||||
config.vm.box = "ubuntu/focal64"
|
||||
config.vm.hostname = "minikube.box"
|
||||
config.vm.provision :shell, privileged: false,
|
||||
inline: <<-EOS
|
||||
set -e
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y make docker.io
|
||||
sudo systemctl start docker
|
||||
sudo usermod -G docker $USER
|
||||
cd /vagrant/.circleci
|
||||
make all
|
||||
EOS
|
||||
|
||||
config.vm.provider "virtualbox" do |v|
|
||||
v.memory = 2048
|
||||
v.cpus = 2
|
||||
end
|
||||
end
|
||||
@@ -1,18 +0,0 @@
|
||||
#! /bin/bash
|
||||
|
||||
_helmfile_bash_autocomplete() {
|
||||
if [[ "${COMP_WORDS[0]}" != "source" ]]; then
|
||||
local cur opts base
|
||||
COMPREPLY=()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
if [[ "$cur" == "-"* ]]; then
|
||||
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} ${cur} --generate-bash-completion )
|
||||
else
|
||||
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion )
|
||||
fi
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
complete -o bashdefault -o default -o nospace -F _helmfile_bash_autocomplete helmfile
|
||||
@@ -1,23 +0,0 @@
|
||||
#compdef helmfile
|
||||
|
||||
_helmfile_zsh_autocomplete() {
|
||||
|
||||
local -a opts
|
||||
local cur
|
||||
cur=${words[-1]}
|
||||
if [[ "$cur" == "-"* ]]; then
|
||||
opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}")
|
||||
else
|
||||
opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} --generate-bash-completion)}")
|
||||
fi
|
||||
|
||||
if [[ "${opts[1]}" != "" ]]; then
|
||||
_describe 'values' opts
|
||||
else
|
||||
_files
|
||||
fi
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
compdef _helmfile_zsh_autocomplete helmfile
|
||||
@@ -0,0 +1,70 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/app"
|
||||
"github.com/helmfile/helmfile/pkg/config"
|
||||
"github.com/helmfile/helmfile/pkg/runtime"
|
||||
)
|
||||
|
||||
// NewApplyCmd returns apply subcmd
|
||||
func NewApplyCmd(globalCfg *config.GlobalImpl) *cobra.Command {
|
||||
applyOptions := &config.ApplyOptions{}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "apply",
|
||||
Short: "Apply all resources from state file only when there are changes",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
applyImpl := config.NewApplyImpl(globalCfg, applyOptions)
|
||||
|
||||
err := config.NewCLIConfigImpl(applyImpl.GlobalImpl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := applyImpl.ValidateConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a := app.New(applyImpl)
|
||||
return toCLIError(applyImpl.GlobalImpl, a.Apply(applyImpl))
|
||||
},
|
||||
}
|
||||
|
||||
f := cmd.Flags()
|
||||
f.StringArrayVar(&applyOptions.Set, "set", nil, "additional values to be merged into the helm command --set flag")
|
||||
f.StringArrayVar(&applyOptions.Values, "values", nil, "additional value files to be merged into the helm command --values flag")
|
||||
f.IntVar(&applyOptions.Concurrency, "concurrency", 0, "maximum number of concurrent helm processes to run, 0 is unlimited")
|
||||
f.BoolVar(&applyOptions.Validate, "validate", false, "validate your manifests against the Kubernetes cluster you are currently pointing at. Note that this requires access to a Kubernetes cluster to obtain information necessary for validating, like the list of available API versions")
|
||||
f.IntVar(&applyOptions.Context, "context", 0, "output NUM lines of context around changes")
|
||||
f.StringVar(&applyOptions.Output, "output", "", "output format for diff plugin")
|
||||
f.BoolVar(&applyOptions.DetailedExitcode, "detailed-exitcode", false, "return a non-zero exit code 2 instead of 0 when there were changes detected AND the changes are synced successfully")
|
||||
f.StringVar(&globalCfg.GlobalOptions.Args, "args", "", "pass args to helm exec")
|
||||
if !runtime.V1Mode {
|
||||
// TODO: Remove this function once Helmfile v0.x
|
||||
f.BoolVar(&applyOptions.RetainValuesFiles, "retain-values-files", false, "DEPRECATED: Use skip-cleanup instead")
|
||||
_ = f.MarkDeprecated("retain-values-files", "Use skip-cleanup instead")
|
||||
}
|
||||
|
||||
f.BoolVar(&applyOptions.SkipCleanup, "skip-cleanup", false, "Stop cleaning up temporary values generated by helmfile and helm-secrets. Useful for debugging. Don't use in production for security")
|
||||
f.BoolVar(&applyOptions.SkipCRDs, "skip-crds", false, "if set, no CRDs will be installed on sync. By default, CRDs are installed if not already present")
|
||||
f.BoolVar(&applyOptions.SkipNeeds, "skip-needs", true, `do not automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when --selector/-l flag is not provided. Defaults to true when --include-needs or --include-transitive-needs is not provided`)
|
||||
f.BoolVar(&applyOptions.IncludeNeeds, "include-needs", false, `automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when --selector/-l flag is not provided`)
|
||||
f.BoolVar(&applyOptions.IncludeTransitiveNeeds, "include-transitive-needs", false, `like --include-needs, but also includes transitive needs (needs of needs). Does nothing when --selector/-l flag is not provided. Overrides exclusions of other selectors and conditions.`)
|
||||
f.BoolVar(&applyOptions.SkipDiffOnInstall, "skip-diff-on-install", false, "Skips running helm-diff on releases being newly installed on this apply. Useful when the release manifests are too huge to be reviewed, or it's too time-consuming to diff at all")
|
||||
f.BoolVar(&applyOptions.IncludeTests, "include-tests", false, "enable the diffing of the helm test hooks")
|
||||
f.StringArrayVar(&applyOptions.Suppress, "suppress", nil, "suppress specified Kubernetes objects in the diff output. Can be provided multiple times. For example: --suppress KeycloakClient --suppress VaultSecret")
|
||||
f.BoolVar(&applyOptions.SuppressSecrets, "suppress-secrets", false, "suppress secrets in the diff output. highly recommended to specify on CI/CD use-cases")
|
||||
f.BoolVar(&applyOptions.ShowSecrets, "show-secrets", false, "do not redact secret values in the diff output. should be used for debug purpose only")
|
||||
f.BoolVar(&applyOptions.NoHooks, "no-hooks", false, "do not diff changes made by hooks.")
|
||||
f.BoolVar(&applyOptions.SuppressDiff, "suppress-diff", false, "suppress diff in the output. Usable in new installs")
|
||||
f.BoolVar(&applyOptions.SkipDeps, "skip-deps", false, `skip running "helm repo update" and "helm dependency build"`)
|
||||
f.BoolVar(&applyOptions.Wait, "wait", false, `Override helmDefaults.wait setting "helm upgrade --install --wait"`)
|
||||
f.BoolVar(&applyOptions.WaitForJobs, "wait-for-jobs", false, `Override helmDefaults.waitForJobs setting "helm upgrade --install --wait-for-jobs"`)
|
||||
f.BoolVar(&applyOptions.ReuseValues, "reuse-values", false, `Override helmDefaults.reuseValues "helm upgrade --install --reuse-values"`)
|
||||
f.BoolVar(&applyOptions.ResetValues, "reset-values", false, `Override helmDefaults.reuseValues "helm upgrade --install --reset-values"`)
|
||||
f.StringVar(&applyOptions.PostRenderer, "post-renderer", "", `pass --post-renderer to "helm template" or "helm upgrade --install"`)
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/app"
|
||||
"github.com/helmfile/helmfile/pkg/config"
|
||||
)
|
||||
|
||||
// NewBuildCmd returns build subcmd
|
||||
func NewBuildCmd(globalCfg *config.GlobalImpl) *cobra.Command {
|
||||
buildOptions := config.NewBuildOptions()
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "build",
|
||||
Short: "Build all resources from state file",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
buildImpl := config.NewBuildImpl(globalCfg, buildOptions)
|
||||
err := config.NewCLIConfigImpl(buildImpl.GlobalImpl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := buildImpl.ValidateConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a := app.New(buildImpl)
|
||||
return toCLIError(buildImpl.GlobalImpl, a.PrintState(buildImpl))
|
||||
},
|
||||
}
|
||||
|
||||
f := cmd.Flags()
|
||||
f.BoolVar(&buildOptions.EmbedValues, "embed-values", false, "Read all the values files for every release and embed into the output helmfile.yaml")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/app"
|
||||
"github.com/helmfile/helmfile/pkg/config"
|
||||
)
|
||||
|
||||
func NewCacheInfoSubcommand(cacheImpl *config.CacheImpl) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "info",
|
||||
Short: "cache info",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := config.NewCLIConfigImpl(cacheImpl.GlobalImpl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := cacheImpl.ValidateConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a := app.New(cacheImpl)
|
||||
return toCLIError(cacheImpl.GlobalImpl, a.ShowCacheDir(cacheImpl))
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func NewCacheCleanupSubcommand(cacheImpl *config.CacheImpl) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "cleanup",
|
||||
Short: "clean up cache directory",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := config.NewCLIConfigImpl(cacheImpl.GlobalImpl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := cacheImpl.ValidateConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a := app.New(cacheImpl)
|
||||
return toCLIError(cacheImpl.GlobalImpl, a.CleanCacheDir(cacheImpl))
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
// NewCacheCmd returns cache subcmd
|
||||
func NewCacheCmd(globalCfg *config.GlobalImpl) *cobra.Command {
|
||||
cacheOptions := config.NewCacheOptions()
|
||||
cacheImpl := config.NewCacheImpl(globalCfg, cacheOptions)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "cache",
|
||||
Short: "Cache management",
|
||||
}
|
||||
|
||||
cmd.AddCommand(
|
||||
NewCacheCleanupSubcommand(cacheImpl),
|
||||
NewCacheInfoSubcommand(cacheImpl),
|
||||
)
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// TODO: Remove this function once Helmfile v0.x
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/app"
|
||||
"github.com/helmfile/helmfile/pkg/config"
|
||||
)
|
||||
|
||||
// NewChartsCmd returns charts subcmd
|
||||
func NewChartsCmd(globalCfg *config.GlobalImpl) *cobra.Command {
|
||||
chartsOptions := config.NewChartsOptions()
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "charts",
|
||||
Short: "DEPRECATED: sync releases from state file (helm upgrade --install)",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
chartsImpl := config.NewChartsImpl(globalCfg, chartsOptions)
|
||||
err := config.NewCLIConfigImpl(chartsImpl.GlobalImpl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := chartsImpl.ValidateConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a := app.New(chartsImpl)
|
||||
return toCLIError(chartsImpl.GlobalImpl, a.DeprecatedSyncCharts(chartsImpl))
|
||||
},
|
||||
}
|
||||
|
||||
f := cmd.Flags()
|
||||
f.StringVar(&globalCfg.GlobalOptions.Args, "args", "", "pass args to helm exec")
|
||||
f.StringArrayVar(&chartsOptions.Set, "set", nil, "additional values to be merged into the helm command --set flag")
|
||||
f.StringArrayVar(&chartsOptions.Values, "values", nil, "additional value files to be merged into the helm command --values flag")
|
||||
f.IntVar(&chartsOptions.Concurrency, "concurrency", 0, "maximum number of concurrent helm processes to run, 0 is unlimited")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// TODO: Remove this function once Helmfile v0.x
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/app"
|
||||
"github.com/helmfile/helmfile/pkg/config"
|
||||
)
|
||||
|
||||
// NewDeleteCmd returns delete subcmd
|
||||
func NewDeleteCmd(globalCfg *config.GlobalImpl) *cobra.Command {
|
||||
deleteOptions := config.NewDeleteOptions()
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "delete",
|
||||
Short: "DEPRECATED: delete releases from state file (helm delete)",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
deleteImpl := config.NewDeleteImpl(globalCfg, deleteOptions)
|
||||
err := config.NewCLIConfigImpl(deleteImpl.GlobalImpl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := deleteImpl.ValidateConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a := app.New(deleteImpl)
|
||||
return toCLIError(deleteImpl.GlobalImpl, a.Delete(deleteImpl))
|
||||
},
|
||||
}
|
||||
|
||||
f := cmd.Flags()
|
||||
f.StringVar(&globalCfg.GlobalOptions.Args, "args", "", "pass args to helm exec")
|
||||
f.IntVar(&deleteOptions.Concurrency, "concurrency", 0, "maximum number of concurrent helm processes to run, 0 is unlimited")
|
||||
f.BoolVar(&deleteOptions.Purge, "purge", false, "purge releases i.e. free release names and histories")
|
||||
f.BoolVar(&deleteOptions.SkipDeps, "skip-deps", false, `skip running "helm repo update" and "helm dependency build"`)
|
||||
f.BoolVar(&deleteOptions.SkipCharts, "skip-charts", false, "don't prepare charts when deleting releases")
|
||||
|
||||
return cmd
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/app"
|
||||
"github.com/helmfile/helmfile/pkg/config"
|
||||
)
|
||||
|
||||
// NewDepsCmd returns deps subcmd
|
||||
func NewDepsCmd(globalCfg *config.GlobalImpl) *cobra.Command {
|
||||
depsOptions := config.NewDepsOptions()
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "deps",
|
||||
Short: "Update charts based on their requirements",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
depsImpl := config.NewDepsImpl(globalCfg, depsOptions)
|
||||
err := config.NewCLIConfigImpl(depsImpl.GlobalImpl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := depsImpl.ValidateConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a := app.New(depsImpl)
|
||||
return toCLIError(depsImpl.GlobalImpl, a.Deps(depsImpl))
|
||||
},
|
||||
}
|
||||
|
||||
f := cmd.Flags()
|
||||
f.StringVar(&globalCfg.GlobalOptions.Args, "args", "", "pass args to helm exec")
|
||||
f.BoolVar(&depsOptions.SkipRepos, "skip-repos", false, `skip running "helm repo update" and "helm dependency build"`)
|
||||
f.IntVar(&depsOptions.Concurrency, "concurrency", 0, "maximum number of concurrent helm processes to run, 0 is unlimited")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/app"
|
||||
"github.com/helmfile/helmfile/pkg/config"
|
||||
)
|
||||
|
||||
// NewDestroyCmd returns destroy subcmd
|
||||
func NewDestroyCmd(globalCfg *config.GlobalImpl) *cobra.Command {
|
||||
destroyOptions := config.NewDestroyOptions()
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "destroy",
|
||||
Short: "Destroys and then purges releases",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
destroyImpl := config.NewDestroyImpl(globalCfg, destroyOptions)
|
||||
err := config.NewCLIConfigImpl(destroyImpl.GlobalImpl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := destroyImpl.ValidateConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a := app.New(destroyImpl)
|
||||
return toCLIError(destroyImpl.GlobalImpl, a.Destroy(destroyImpl))
|
||||
},
|
||||
}
|
||||
|
||||
f := cmd.Flags()
|
||||
f.StringVar(&globalCfg.GlobalOptions.Args, "args", "", "pass args to helm exec")
|
||||
f.IntVar(&destroyOptions.Concurrency, "concurrency", 0, "maximum number of concurrent helm processes to run, 0 is unlimited")
|
||||
f.BoolVar(&destroyOptions.SkipDeps, "skip-deps", false, `skip running "helm repo update" and "helm dependency build"`)
|
||||
f.BoolVar(&destroyOptions.SkipCharts, "skip-charts", false, "don't prepare charts when destroying releases")
|
||||
|
||||
return cmd
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/app"
|
||||
"github.com/helmfile/helmfile/pkg/config"
|
||||
)
|
||||
|
||||
// NewDiffCmd returns diff subcmd
|
||||
func NewDiffCmd(globalCfg *config.GlobalImpl) *cobra.Command {
|
||||
diffOptions := config.NewDiffOptions()
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "diff",
|
||||
Short: "Diff releases defined in state file",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
diffImpl := config.NewDiffImpl(globalCfg, diffOptions)
|
||||
err := config.NewCLIConfigImpl(diffImpl.GlobalImpl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := diffImpl.ValidateConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a := app.New(diffImpl)
|
||||
return toCLIError(diffImpl.GlobalImpl, a.Diff(diffImpl))
|
||||
},
|
||||
}
|
||||
|
||||
f := cmd.Flags()
|
||||
f.StringVar(&globalCfg.GlobalOptions.Args, "args", "", "pass args to helm diff")
|
||||
f.StringArrayVar(&diffOptions.Set, "set", nil, "additional values to be merged into the helm command --set flag")
|
||||
f.StringArrayVar(&diffOptions.Values, "values", nil, "additional value files to be merged into the helm command --values flag")
|
||||
f.IntVar(&diffOptions.Concurrency, "concurrency", 0, "maximum number of concurrent helm processes to run, 0 is unlimited")
|
||||
f.BoolVar(&diffOptions.Validate, "validate", false, "validate your manifests against the Kubernetes cluster you are currently pointing at. Note that this requires access to a Kubernetes cluster to obtain information necessary for validating, like the diff of available API versions")
|
||||
f.BoolVar(&diffOptions.SkipNeeds, "skip-needs", true, `do not automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when --selector/-l flag is not provided. Defaults to true when --include-needs or --include-transitive-needs is not provided`)
|
||||
f.BoolVar(&diffOptions.IncludeTests, "include-tests", false, "enable the diffing of the helm test hooks")
|
||||
f.BoolVar(&diffOptions.IncludeNeeds, "include-needs", false, `automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when --selector/-l flag is not provided`)
|
||||
f.BoolVar(&diffOptions.IncludeTransitiveNeeds, "include-transitive-needs", false, `like --include-needs, but also includes transitive needs (needs of needs). Does nothing when --selector/-l flag is not provided. Overrides exclusions of other selectors and conditions.`)
|
||||
f.BoolVar(&diffOptions.SkipDeps, "skip-deps", false, `skip running "helm repo update" and "helm dependency build"`)
|
||||
f.BoolVar(&diffOptions.SkipDiffOnInstall, "skip-diff-on-install", false, "Skips running helm-diff on releases being newly installed on this apply. Useful when the release manifests are too huge to be reviewed, or it's too time-consuming to diff at all")
|
||||
f.BoolVar(&diffOptions.ShowSecrets, "show-secrets", false, "do not redact secret values in the output. should be used for debug purpose only")
|
||||
f.BoolVar(&diffOptions.NoHooks, "no-hooks", false, "do not diff changes made by hooks.")
|
||||
f.BoolVar(&diffOptions.DetailedExitcode, "detailed-exitcode", false, "return a detailed exit code")
|
||||
f.IntVar(&diffOptions.Context, "context", 0, "output NUM lines of context around changes")
|
||||
f.StringVar(&diffOptions.Output, "output", "", "output format for diff plugin")
|
||||
f.BoolVar(&diffOptions.SuppressSecrets, "suppress-secrets", false, "suppress secrets in the output. highly recommended to specify on CI/CD use-cases")
|
||||
f.StringArrayVar(&diffOptions.Suppress, "suppress", nil, "suppress specified Kubernetes objects in the output. Can be provided multiple times. For example: --suppress KeycloakClient --suppress VaultSecret")
|
||||
f.BoolVar(&diffOptions.ReuseValues, "reuse-values", false, `Override helmDefaults.reuseValues "helm diff upgrade --install --reuse-values"`)
|
||||
f.BoolVar(&diffOptions.ResetValues, "reset-values", false, `Override helmDefaults.reuseValues "helm diff upgrade --install --reset-values"`)
|
||||
f.StringVar(&diffOptions.PostRenderer, "post-renderer", "", `pass --post-renderer to "helm template" or "helm upgrade --install"`)
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/app"
|
||||
"github.com/helmfile/helmfile/pkg/config"
|
||||
"github.com/helmfile/helmfile/pkg/state"
|
||||
)
|
||||
|
||||
// NewFetchCmd returns fetch subcmd
|
||||
func NewFetchCmd(globalCfg *config.GlobalImpl) *cobra.Command {
|
||||
fetchOptions := config.NewFetchOptions()
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "fetch",
|
||||
Short: "Fetch charts from state file",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
fetchImpl := config.NewFetchImpl(globalCfg, fetchOptions)
|
||||
err := config.NewCLIConfigImpl(fetchImpl.GlobalImpl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := fetchImpl.ValidateConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a := app.New(fetchImpl)
|
||||
return toCLIError(fetchImpl.GlobalImpl, a.Fetch(fetchImpl))
|
||||
},
|
||||
}
|
||||
|
||||
f := cmd.Flags()
|
||||
f.IntVar(&fetchOptions.Concurrency, "concurrency", 0, "maximum number of concurrent helm processes to run, 0 is unlimited")
|
||||
f.BoolVar(&fetchOptions.SkipDeps, "skip-deps", false, `skip running "helm repo update" and "helm dependency build"`)
|
||||
f.StringVar(&fetchOptions.OutputDir, "output-dir", "", "directory to store charts (default: temporary directory which is deleted when the command terminates)")
|
||||
f.StringVar(&fetchOptions.OutputDirTemplate, "output-dir-template", state.DefaultFetchOutputDirTemplate, "go text template for generating the output directory")
|
||||
|
||||
return cmd
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/app"
|
||||
"github.com/helmfile/helmfile/pkg/config"
|
||||
)
|
||||
|
||||
// NewInitCmd helmfile checks and installs deps
|
||||
func NewInitCmd(globalCfg *config.GlobalImpl) *cobra.Command {
|
||||
options := config.NewInitOptions()
|
||||
cmd := &cobra.Command{
|
||||
Use: "init",
|
||||
Short: "Initialize the helmfile, includes version checking and installation of helm and plug-ins",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
initImpl := config.NewInitImpl(globalCfg, options)
|
||||
err := config.NewCLIConfigImpl(initImpl.GlobalImpl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := initImpl.ValidateConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a := app.New(initImpl)
|
||||
return toCLIError(initImpl.GlobalImpl, a.Init(initImpl))
|
||||
},
|
||||
}
|
||||
f := cmd.Flags()
|
||||
f.BoolVar(&options.Force, "force", false, "Do not prompt, install dependencies required by helmfile")
|
||||
|
||||
return cmd
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/app"
|
||||
"github.com/helmfile/helmfile/pkg/config"
|
||||
)
|
||||
|
||||
// NewLintCmd returns lint subcmd
|
||||
func NewLintCmd(globalCfg *config.GlobalImpl) *cobra.Command {
|
||||
lintOptions := config.NewLintOptions()
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "lint",
|
||||
Short: "Lint charts from state file (helm lint)",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
lintImpl := config.NewLintImpl(globalCfg, lintOptions)
|
||||
err := config.NewCLIConfigImpl(lintImpl.GlobalImpl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := lintImpl.ValidateConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a := app.New(lintImpl)
|
||||
return toCLIError(lintImpl.GlobalImpl, a.Lint(lintImpl))
|
||||
},
|
||||
}
|
||||
|
||||
f := cmd.Flags()
|
||||
f.IntVar(&lintOptions.Concurrency, "concurrency", 0, "maximum number of concurrent helm processes to run, 0 is unlimited")
|
||||
f.BoolVar(&lintOptions.SkipDeps, "skip-deps", false, `skip running "helm repo update" and "helm dependency build"`)
|
||||
f.StringVar(&globalCfg.GlobalOptions.Args, "args", "", "pass args to helm exec")
|
||||
f.StringArrayVar(&lintOptions.Set, "set", nil, "additional values to be merged into the helm command --set flag")
|
||||
f.StringArrayVar(&lintOptions.Values, "values", nil, "additional value files to be merged into the helm command --values flag")
|
||||
f.BoolVar(&lintOptions.SkipNeeds, "skip-needs", true, `do not automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when --selector/-l flag is not provided. Defaults to true when --include-needs or --include-transitive-needs is not provided`)
|
||||
f.BoolVar(&lintOptions.IncludeNeeds, "include-needs", false, `automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when --selector/-l flag is not provided`)
|
||||
f.BoolVar(&lintOptions.IncludeTransitiveNeeds, "include-transitive-needs", false, `like --include-needs, but also includes transitive needs (needs of needs). Does nothing when --selector/-l flag is not provided. Overrides exclusions of other selectors and conditions.`)
|
||||
|
||||
return cmd
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/app"
|
||||
"github.com/helmfile/helmfile/pkg/config"
|
||||
)
|
||||
|
||||
// NewListCmd returns list subcmd
|
||||
func NewListCmd(globalCfg *config.GlobalImpl) *cobra.Command {
|
||||
listOptions := config.NewListOptions()
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List releases defined in state file",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
listImpl := config.NewListImpl(globalCfg, listOptions)
|
||||
err := config.NewCLIConfigImpl(listImpl.GlobalImpl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := listImpl.ValidateConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a := app.New(listImpl)
|
||||
return toCLIError(listImpl.GlobalImpl, a.ListReleases(listImpl))
|
||||
},
|
||||
}
|
||||
|
||||
f := cmd.Flags()
|
||||
f.BoolVar(&listOptions.KeepTempDir, "keep-temp-dir", false, "Keep temporary directory")
|
||||
f.BoolVar(&listOptions.SkipCharts, "skip-charts", false, "don't prepare charts when listing releases")
|
||||
f.StringVar(&listOptions.Output, "output", "", "output releases list as a json string")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/app"
|
||||
"github.com/helmfile/helmfile/pkg/config"
|
||||
)
|
||||
|
||||
// NewReposCmd returns repos subcmd
|
||||
func NewReposCmd(globalCfg *config.GlobalImpl) *cobra.Command {
|
||||
reposOptions := config.NewReposOptions()
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "repos",
|
||||
Short: "Add chart repositories defined in state file",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
reposImpl := config.NewReposImpl(globalCfg, reposOptions)
|
||||
err := config.NewCLIConfigImpl(reposImpl.GlobalImpl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := reposImpl.ValidateConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a := app.New(reposImpl)
|
||||
return toCLIError(reposImpl.GlobalImpl, a.Repos(reposImpl))
|
||||
},
|
||||
}
|
||||
|
||||
f := cmd.Flags()
|
||||
f.StringVar(&globalCfg.GlobalOptions.Args, "args", "", "pass args to helm exec")
|
||||
|
||||
return cmd
|
||||
}
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"go.szostok.io/version/extension"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/app"
|
||||
"github.com/helmfile/helmfile/pkg/app/version"
|
||||
"github.com/helmfile/helmfile/pkg/config"
|
||||
"github.com/helmfile/helmfile/pkg/envvar"
|
||||
"github.com/helmfile/helmfile/pkg/errors"
|
||||
"github.com/helmfile/helmfile/pkg/helmexec"
|
||||
"github.com/helmfile/helmfile/pkg/runtime"
|
||||
)
|
||||
|
||||
var logger *zap.SugaredLogger
|
||||
var globalUsage = "Declaratively deploy your Kubernetes manifests, Kustomize configs, and Charts as Helm releases in one shot\n" + runtime.Info()
|
||||
|
||||
func toCLIError(g *config.GlobalImpl, err error) error {
|
||||
if err != nil {
|
||||
switch e := err.(type) {
|
||||
case *app.NoMatchingHelmfileError:
|
||||
noMatchingExitCode := 3
|
||||
if g.AllowNoMatchingRelease {
|
||||
noMatchingExitCode = 0
|
||||
}
|
||||
return errors.NewExitError(e.Error(), noMatchingExitCode)
|
||||
case *app.MultiError:
|
||||
return errors.NewExitError(e.Error(), 1)
|
||||
case *app.Error:
|
||||
return errors.NewExitError(e.Error(), e.Code())
|
||||
default:
|
||||
panic(fmt.Errorf("BUG: please file an github issue for this unhandled error: %T: %v", e, e))
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// NewRootCmd creates the root command for the CLI.
|
||||
func NewRootCmd(globalConfig *config.GlobalOptions) (*cobra.Command, error) {
|
||||
cmd := &cobra.Command{
|
||||
Use: "helmfile",
|
||||
Short: globalUsage,
|
||||
Long: globalUsage,
|
||||
Version: version.Version(),
|
||||
SilenceUsage: true,
|
||||
SilenceErrors: true,
|
||||
PersistentPreRunE: func(c *cobra.Command, args []string) error {
|
||||
// Valid levels:
|
||||
// https://github.com/uber-go/zap/blob/7e7e266a8dbce911a49554b945538c5b950196b8/zapcore/level.go#L126
|
||||
logLevel := globalConfig.LogLevel
|
||||
switch {
|
||||
case globalConfig.Debug:
|
||||
logLevel = "debug"
|
||||
case globalConfig.Quiet:
|
||||
logLevel = "warn"
|
||||
}
|
||||
logger = helmexec.NewLogger(os.Stderr, logLevel)
|
||||
globalConfig.SetLogger(logger)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
flags := cmd.PersistentFlags()
|
||||
|
||||
// Set the global options for the root command.
|
||||
setGlobalOptionsForRootCmd(flags, globalConfig)
|
||||
|
||||
flags.ParseErrorsWhitelist.UnknownFlags = true
|
||||
|
||||
globalImpl := config.NewGlobalImpl(globalConfig)
|
||||
|
||||
// when set environment HELMFILE_UPGRADE_NOTICE_DISABLED any value, skip upgrade notice.
|
||||
var versionOpts []extension.CobraOption
|
||||
if os.Getenv(envvar.UpgradeNoticeDisabled) == "" {
|
||||
versionOpts = append(versionOpts, extension.WithUpgradeNotice("helmfile", "helmfile"))
|
||||
}
|
||||
|
||||
cmd.AddCommand(
|
||||
NewInitCmd(globalImpl),
|
||||
NewApplyCmd(globalImpl),
|
||||
NewBuildCmd(globalImpl),
|
||||
NewCacheCmd(globalImpl),
|
||||
NewDepsCmd(globalImpl),
|
||||
NewDestroyCmd(globalImpl),
|
||||
NewFetchCmd(globalImpl),
|
||||
NewListCmd(globalImpl),
|
||||
NewReposCmd(globalImpl),
|
||||
NewLintCmd(globalImpl),
|
||||
NewWriteValuesCmd(globalImpl),
|
||||
NewTestCmd(globalImpl),
|
||||
NewTemplateCmd(globalImpl),
|
||||
NewSyncCmd(globalImpl),
|
||||
NewDiffCmd(globalImpl),
|
||||
NewStatusCmd(globalImpl),
|
||||
extension.NewVersionCobraCmd(
|
||||
versionOpts...,
|
||||
),
|
||||
)
|
||||
|
||||
// TODO: Remove this function once Helmfile v0.x
|
||||
if !runtime.V1Mode {
|
||||
cmd.AddCommand(
|
||||
NewChartsCmd(globalImpl),
|
||||
NewDeleteCmd(globalImpl),
|
||||
)
|
||||
}
|
||||
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
func setGlobalOptionsForRootCmd(fs *pflag.FlagSet, globalOptions *config.GlobalOptions) {
|
||||
fs.StringVarP(&globalOptions.HelmBinary, "helm-binary", "b", app.DefaultHelmBinary, "Path to the helm binary")
|
||||
fs.StringVarP(&globalOptions.File, "file", "f", "", "load config from file or directory. defaults to `helmfile.yaml` or `helmfile.yaml.gotmpl` or `helmfile.d`(means `helmfile.d/*.yaml` or `helmfile.d/*.yaml.gotmpl`) in this preference. Specify - to load the config from the standard input.")
|
||||
fs.StringVarP(&globalOptions.Environment, "environment", "e", "", `specify the environment name. defaults to "default"`)
|
||||
fs.StringArrayVar(&globalOptions.StateValuesSet, "state-values-set", nil, "set state values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2). Used to override .Values within the helmfile template (not values template).")
|
||||
fs.StringArrayVar(&globalOptions.StateValuesFile, "state-values-file", nil, "specify state values in a YAML file. Used to override .Values within the helmfile template (not values template).")
|
||||
fs.BoolVarP(&globalOptions.Quiet, "quiet", "q", false, "Silence output. Equivalent to log-level warn")
|
||||
fs.StringVar(&globalOptions.KubeContext, "kube-context", "", "Set kubectl context. Uses current context by default")
|
||||
fs.BoolVar(&globalOptions.Debug, "debug", false, "Enable verbose output for Helm and set log-level to debug, this disables --quiet/-q effect")
|
||||
fs.BoolVar(&globalOptions.Color, "color", false, "Output with color")
|
||||
fs.BoolVar(&globalOptions.NoColor, "no-color", false, "Output without color")
|
||||
fs.StringVar(&globalOptions.LogLevel, "log-level", "info", "Set log level, default info")
|
||||
fs.StringVarP(&globalOptions.Namespace, "namespace", "n", "", "Set namespace. Uses the namespace set in the context by default, and is available in templates as {{ .Namespace }}")
|
||||
fs.StringVarP(&globalOptions.Chart, "chart", "c", "", "Set chart. Uses the chart set in release by default, and is available in template as {{ .Chart }}")
|
||||
fs.StringArrayVarP(&globalOptions.Selector, "selector", "l", nil, `Only run using the releases that match labels. Labels can take the form of foo=bar or foo!=bar.
|
||||
A release must match all labels in a group in order to be used. Multiple groups can be specified at once.
|
||||
"--selector tier=frontend,tier!=proxy --selector tier=backend" will match all frontend, non-proxy releases AND all backend releases.
|
||||
The name of a release can be used as a label: "--selector name=myrelease"`)
|
||||
fs.BoolVar(&globalOptions.AllowNoMatchingRelease, "allow-no-matching-release", false, `Do not exit with an error code if the provided selector has no matching releases.`)
|
||||
fs.BoolVar(&globalOptions.EnableLiveOutput, "enable-live-output", globalOptions.EnableLiveOutput, `Show live output from the Helm binary Stdout/Stderr into Helmfile own Stdout/Stderr.
|
||||
It only applies for the Helm CLI commands, Stdout/Stderr for Hooks are still displayed only when it's execution finishes.`)
|
||||
fs.BoolVarP(&globalOptions.Interactive, "interactive", "i", false, "Request confirmation before attempting to modify clusters")
|
||||
// avoid 'pflag: help requested' error (#251)
|
||||
fs.BoolP("help", "h", false, "help for helmfile")
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/app"
|
||||
"github.com/helmfile/helmfile/pkg/config"
|
||||
)
|
||||
|
||||
// NewStatusCmd returns status subcmd
|
||||
func NewStatusCmd(globalCfg *config.GlobalImpl) *cobra.Command {
|
||||
statusOptions := config.NewStatusOptions()
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "status",
|
||||
Short: "Retrieve status of releases in state file",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
statusImpl := config.NewStatusImpl(globalCfg, statusOptions)
|
||||
err := config.NewCLIConfigImpl(statusImpl.GlobalImpl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := statusImpl.ValidateConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a := app.New(statusImpl)
|
||||
return toCLIError(statusImpl.GlobalImpl, a.Status(statusImpl))
|
||||
},
|
||||
}
|
||||
|
||||
f := cmd.Flags()
|
||||
f.StringVar(&globalCfg.GlobalOptions.Args, "args", "", "pass args to helm exec")
|
||||
f.IntVar(&statusOptions.Concurrency, "concurrency", 0, "maximum number of concurrent helm processes to run, 0 is unlimited")
|
||||
|
||||
return cmd
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/app"
|
||||
"github.com/helmfile/helmfile/pkg/config"
|
||||
)
|
||||
|
||||
// NewSyncCmd returns sync subcmd
|
||||
func NewSyncCmd(globalCfg *config.GlobalImpl) *cobra.Command {
|
||||
syncOptions := config.NewSyncOptions()
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "sync",
|
||||
Short: "Sync releases defined in state file",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
syncImpl := config.NewSyncImpl(globalCfg, syncOptions)
|
||||
err := config.NewCLIConfigImpl(syncImpl.GlobalImpl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := syncImpl.ValidateConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a := app.New(syncImpl)
|
||||
return toCLIError(syncImpl.GlobalImpl, a.Sync(syncImpl))
|
||||
},
|
||||
}
|
||||
|
||||
f := cmd.Flags()
|
||||
f.StringVar(&globalCfg.GlobalOptions.Args, "args", "", "pass args to helm sync")
|
||||
f.StringArrayVar(&syncOptions.Set, "set", nil, "additional values to be merged into the helm command --set flag")
|
||||
f.StringArrayVar(&syncOptions.Values, "values", nil, "additional value files to be merged into the helm command --values flag")
|
||||
f.IntVar(&syncOptions.Concurrency, "concurrency", 0, "maximum number of concurrent helm processes to run, 0 is unlimited")
|
||||
f.BoolVar(&syncOptions.Validate, "validate", false, "validate your manifests against the Kubernetes cluster you are currently pointing at. Note that this requires access to a Kubernetes cluster to obtain information necessary for validating, like the sync of available API versions")
|
||||
f.BoolVar(&syncOptions.SkipNeeds, "skip-needs", true, `do not automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when --selector/-l flag is not provided. Defaults to true when --include-needs or --include-transitive-needs is not provided`)
|
||||
f.BoolVar(&syncOptions.SkipCRDs, "skip-crds", false, "if set, no CRDs will be installed on sync. By default, CRDs are installed if not already present")
|
||||
f.BoolVar(&syncOptions.IncludeNeeds, "include-needs", false, `automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when --selector/-l flag is not provided`)
|
||||
f.BoolVar(&syncOptions.IncludeTransitiveNeeds, "include-transitive-needs", false, `like --include-needs, but also includes transitive needs (needs of needs). Does nothing when --selector/-l flag is not provided. Overrides exclusions of other selectors and conditions.`)
|
||||
f.BoolVar(&syncOptions.SkipDeps, "skip-deps", false, `skip running "helm repo update" and "helm dependency build"`)
|
||||
f.BoolVar(&syncOptions.Wait, "wait", false, `Override helmDefaults.wait setting "helm upgrade --install --wait"`)
|
||||
f.BoolVar(&syncOptions.WaitForJobs, "wait-for-jobs", false, `Override helmDefaults.waitForJobs setting "helm upgrade --install --wait-for-jobs"`)
|
||||
f.BoolVar(&syncOptions.ReuseValues, "reuse-values", false, `Override helmDefaults.reuseValues "helm upgrade --install --reuse-values"`)
|
||||
f.BoolVar(&syncOptions.ResetValues, "reset-values", false, `Override helmDefaults.reuseValues "helm upgrade --install --reset-values"`)
|
||||
f.StringVar(&syncOptions.PostRenderer, "post-renderer", "", `pass --post-renderer to "helm template" or "helm upgrade --install"`)
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/app"
|
||||
"github.com/helmfile/helmfile/pkg/config"
|
||||
)
|
||||
|
||||
// NewTemplateCmd returm template subcmd
|
||||
func NewTemplateCmd(globalCfg *config.GlobalImpl) *cobra.Command {
|
||||
templateOptions := config.NewTemplateOptions()
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "template",
|
||||
Short: "Template releases defined in state file",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
templateImpl := config.NewTemplateImpl(globalCfg, templateOptions)
|
||||
err := config.NewCLIConfigImpl(templateImpl.GlobalImpl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := templateImpl.ValidateConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a := app.New(templateImpl)
|
||||
return toCLIError(templateImpl.GlobalImpl, a.Template(templateImpl))
|
||||
},
|
||||
}
|
||||
|
||||
f := cmd.Flags()
|
||||
f.StringVar(&globalCfg.GlobalOptions.Args, "args", "", "pass args to helm template")
|
||||
f.StringArrayVar(&templateOptions.Set, "set", nil, "additional values to be merged into the helm command --set flag")
|
||||
f.StringArrayVar(&templateOptions.Values, "values", nil, "additional value files to be merged into the helm command --values flag")
|
||||
f.StringVar(&templateOptions.OutputDir, "output-dir", "", "output directory to pass to helm template (helm template --output-dir)")
|
||||
f.StringVar(&templateOptions.OutputDirTemplate, "output-dir-template", "", "go text template for generating the output directory. Default: {{ .OutputDir }}/{{ .State.BaseName }}-{{ .State.AbsPathSHA1 }}-{{ .Release.Name}}")
|
||||
f.IntVar(&templateOptions.Concurrency, "concurrency", 0, "maximum number of concurrent helm processes to run, 0 is unlimited")
|
||||
f.BoolVar(&templateOptions.Validate, "validate", false, "validate your manifests against the Kubernetes cluster you are currently pointing at. Note that this requires access to a Kubernetes cluster to obtain information necessary for validating, like the template of available API versions")
|
||||
f.BoolVar(&templateOptions.IncludeCRDs, "include-crds", false, "include CRDs in the templated output")
|
||||
f.BoolVar(&templateOptions.SkipTests, "skip-tests", false, "skip tests from templated output")
|
||||
f.BoolVar(&templateOptions.SkipNeeds, "skip-needs", true, `do not automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when --selector/-l flag is not provided. Defaults to true when --include-needs or --include-transitive-needs is not provided`)
|
||||
f.BoolVar(&templateOptions.IncludeNeeds, "include-needs", false, `automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when --selector/-l flag is not provided`)
|
||||
f.BoolVar(&templateOptions.IncludeTransitiveNeeds, "include-transitive-needs", false, `like --include-needs, but also includes transitive needs (needs of needs). Does nothing when --selector/-l flag is not provided. Overrides exclusions of other selectors and conditions.`)
|
||||
f.BoolVar(&templateOptions.SkipDeps, "skip-deps", false, `skip running "helm repo update" and "helm dependency build"`)
|
||||
f.BoolVar(&templateOptions.SkipCleanup, "skip-cleanup", false, "Stop cleaning up temporary values generated by helmfile and helm-secrets. Useful for debugging. Don't use in production for security")
|
||||
f.StringVar(&templateOptions.PostRenderer, "post-renderer", "", `pass --post-renderer to "helm template" or "helm upgrade --install"`)
|
||||
|
||||
return cmd
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/app"
|
||||
"github.com/helmfile/helmfile/pkg/config"
|
||||
)
|
||||
|
||||
// NewTestCmd returns test subcmd
|
||||
func NewTestCmd(globalCfg *config.GlobalImpl) *cobra.Command {
|
||||
testOptions := config.NewTestOptions()
|
||||
testImpl := config.NewTestImpl(globalCfg, testOptions)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "test",
|
||||
Short: "Test charts from state file (helm test)",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := config.NewCLIConfigImpl(testImpl.GlobalImpl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := testImpl.ValidateConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a := app.New(testImpl)
|
||||
return toCLIError(testImpl.GlobalImpl, a.Test(testImpl))
|
||||
},
|
||||
}
|
||||
testImpl.Cmd = cmd
|
||||
|
||||
f := cmd.Flags()
|
||||
f.IntVar(&testOptions.Concurrency, "concurrency", 0, "maximum number of concurrent helm processes to run, 0 is unlimited")
|
||||
f.BoolVar(&testOptions.SkipDeps, "skip-deps", false, `skip running "helm repo update" and "helm dependency build"`)
|
||||
f.BoolVar(&testOptions.Cleanup, "cleanup", false, "delete test pods upon completion")
|
||||
f.BoolVar(&testOptions.Logs, "logs", false, "Dump the logs from test pods (this runs after all tests are complete, but before any cleanup)")
|
||||
f.StringVar(&globalCfg.GlobalOptions.Args, "args", "", "pass args to helm exec")
|
||||
f.IntVar(&testOptions.Timeout, "timeout", 300, "maximum time for tests to run before being considered failed")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/app"
|
||||
"github.com/helmfile/helmfile/pkg/config"
|
||||
)
|
||||
|
||||
// NewWriteValuesCmd returns write-values subcmd
|
||||
func NewWriteValuesCmd(globalCfg *config.GlobalImpl) *cobra.Command {
|
||||
writeValuesOptions := config.NewWriteValuesOptions()
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "write-values",
|
||||
Short: "Write values files for releases. Similar to `helmfile template`, write values files instead of manifests.",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
writeValuesImpl := config.NewWriteValuesImpl(globalCfg, writeValuesOptions)
|
||||
err := config.NewCLIConfigImpl(writeValuesImpl.GlobalImpl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := writeValuesImpl.ValidateConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a := app.New(writeValuesImpl)
|
||||
return toCLIError(writeValuesImpl.GlobalImpl, a.WriteValues(writeValuesImpl))
|
||||
},
|
||||
}
|
||||
|
||||
f := cmd.Flags()
|
||||
f.IntVar(&writeValuesOptions.Concurrency, "concurrency", 0, "maximum number of concurrent helm processes to run, 0 is unlimited")
|
||||
f.BoolVar(&writeValuesOptions.SkipDeps, "skip-deps", false, `skip running "helm repo update" and "helm dependency build"`)
|
||||
f.StringArrayVar(&writeValuesOptions.Set, "set", nil, "additional values to be merged into the helm command --set flag")
|
||||
f.StringArrayVar(&writeValuesOptions.Values, "values", nil, "additional value files to be merged into the helm command --values flag")
|
||||
f.StringVar(&writeValuesOptions.OutputFileTemplate, "output-file-template", "", "go text template for generating the output file. Default: {{ .State.BaseName }}-{{ .State.AbsPathSHA1 }}/{{ .Release.Name}}.yaml")
|
||||
|
||||
return cmd
|
||||
}
|
||||
+129
-8
@@ -3,6 +3,7 @@
|
||||
- [Import Configuration Parameters into Helmfile](#import-configuration-parameters-into-helmfile)
|
||||
- [Deploy Kustomization with Helmfile](#deploy-kustomizations-with-helmfile)
|
||||
- [Adhoc Kustomization of Helm Charts](#adhoc-kustomization-of-helm-charts)
|
||||
- [Adding dependencies without forking the chart](#adding-dependencies-without-forking-the-chart)
|
||||
|
||||
### Import Configuration Parameters into Helmfile
|
||||
|
||||
@@ -13,7 +14,7 @@ Helmfile integrates [vals]() to import configuration parameters from following b
|
||||
- Vault
|
||||
- SOPS
|
||||
|
||||
See [Vals "Suported Backends"](https://github.com/variantdev/vals#suported-backends) for the full list of available backends.
|
||||
See [Vals "Suported Backends"](https://github.com/helmfile/vals#suported-backends) for the full list of available backends.
|
||||
|
||||
This feature was implemented in https://github.com/roboll/helmfile/pull/906.
|
||||
If you're curious how it's designed and how it works, please consult the pull request.
|
||||
@@ -92,11 +93,11 @@ which can be included in the temporary chart.
|
||||
|
||||
After all, Helmfile just installs the temporary chart like standard charts, which allows you to manage everything with Helmfile regardless of each app is declared using a Helm chart or a kustomization.
|
||||
|
||||
Please also see [test/advanced/helmfile.yaml](https://github.com/roboll/helmfile/tree/master/test/advanced/helmfile.yaml) for an example of kustomization support and more.
|
||||
Please also see [test/advanced/helmfile.yaml](https://github.com/helmfile/helmfile/tree/master/test/advanced/helmfile.yaml) for an example of kustomization support and more.
|
||||
|
||||
### Adhoc Kustomization of Helm charts
|
||||
|
||||
With Helmfile's integration with Helmfile, not only deploying Kustomization as a Helm chart, you can kustomize charts before installation.
|
||||
With Helmfile's integration with Kustomize, not only deploying Kustomization as a Helm chart, you can kustomize charts before installation.
|
||||
|
||||
Currently, Helmfile allows you to set the following fields for kustomizing the chart:
|
||||
|
||||
@@ -111,7 +112,7 @@ You can add/update any Kubernetes resource field rendered from a Helm chart by s
|
||||
```
|
||||
repositories:
|
||||
- name: incubator
|
||||
url: https://kubernetes-charts-incubator.storage.googleapis.com
|
||||
url: https://charts.helm.sh/incubator
|
||||
|
||||
releases:
|
||||
- name: raw1
|
||||
@@ -146,17 +147,17 @@ Please note that the second `data` field `bar` is coming from the strategic-merg
|
||||
|
||||
There's also `releases[].jsonPatches` that works similarly to `strategicMergePatches` but has additional capability to remove fields.
|
||||
|
||||
Please also see [test/advanced/helmfile.yaml](https://github.com/roboll/helmfile/tree/master/test/advanced/helmfile.yaml) for an example of patching support and more.
|
||||
Please also see [test/advanced/helmfile.yaml](https://github.com/helmfile/helmfile/tree/master/test/advanced/helmfile.yaml) for an example of patching support and more.
|
||||
|
||||
#### `transformers`
|
||||
|
||||
You can set `transformers` to apply [Kustomize's transformers](https://github.com/kubernetes-sigs/kustomize/blob/master/examples/configureBuiltinPlugin.md#configuring-the-builtin-plugins-instead).
|
||||
|
||||
Each item can be a path to a YAML or go template file, or an embedded transformer declaration as a YAML hash.
|
||||
Each item can be a path to a YAML or Go template file, or an embedded transformer declaration as a YAML hash.
|
||||
|
||||
It's often used to add common labels and annotations to your resources.
|
||||
|
||||
In the below example. we add common annotations and labels every resource rendered from the `aws-load-balancer-controller` chart:
|
||||
In the below example, we add common annotations and labels every resource rendered from the `aws-load-balancer-controller` chart:
|
||||
|
||||
```yaml
|
||||
releases:
|
||||
@@ -186,7 +187,7 @@ releases:
|
||||
create: true
|
||||
```
|
||||
|
||||
As explained earlier, `transformers` can be not only a list of embedded transformers, but also YAML or go template files, or a mix of those three kinds.
|
||||
As explained earlier, `transformers` can be not only a list of embedded transformers, but also YAML or Go template files, or a mix of those three kinds.
|
||||
|
||||
```yaml
|
||||
transformers:
|
||||
@@ -209,3 +210,123 @@ transformers:
|
||||
```
|
||||
|
||||
Please see https://github.com/kubernetes-sigs/kustomize/blob/master/examples/configureBuiltinPlugin.md#configuring-the-builtin-plugins-instead for more information on how to declare transformers.
|
||||
|
||||
### Adding dependencies without forking the chart
|
||||
|
||||
With Helmfile, you can add chart dependencies to a Helm chart without forking it.
|
||||
|
||||
An example `helmfile.yaml` that adds a `stable/envoy` dependency to the release `foo` looks like the below:
|
||||
|
||||
```yaml
|
||||
repositories:
|
||||
- name: stable
|
||||
url: https://charts.helm.sh/stable
|
||||
|
||||
releases:
|
||||
- name: foo
|
||||
chart: ./path/to/foo
|
||||
dependencies:
|
||||
- chart: stable/envoy
|
||||
version: 1.5
|
||||
```
|
||||
|
||||
When Helmfile encounters `releases[].dependencies`, it creates another temporary chart from `./path/to/foo` and adds the following `dependencies` to the `Chart.yaml`, so that you don't need to fork the chart.
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
- name: envoy
|
||||
repo: https://charts.helm.sh/stable
|
||||
condition: envoy.enabled
|
||||
```
|
||||
|
||||
A Helm chart can have two or more dependencies for the same chart with different `alias`es. To give your dependency an `alias`, define it like you would do in a standard `Chart.yaml`:
|
||||
|
||||
```yaml
|
||||
repositories:
|
||||
- name: stable
|
||||
url: https://charts.helm.sh/stable
|
||||
|
||||
releases:
|
||||
- name: foo
|
||||
chart: ./path/to/foo
|
||||
dependencies:
|
||||
- chart: stable/envoy
|
||||
version: 1.5
|
||||
alias: bar
|
||||
- chart: stable/envoy
|
||||
version: 1.5
|
||||
alias: baz
|
||||
```
|
||||
|
||||
which will tweaks the temporary chart's `Chart.yaml` to have:
|
||||
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
- alias: bar
|
||||
name: envoy
|
||||
repo: https://charts.helm.sh/stable
|
||||
condition: bar.enabled
|
||||
- alias: baz
|
||||
name: envoy
|
||||
repo: https://charts.helm.sh/stable
|
||||
condition: baz.enabled
|
||||
```
|
||||
|
||||
Please see #649 for more context around this feature.
|
||||
|
||||
After the support for adhoc dependency to local chart (#1765),
|
||||
you can even write local file paths relative to `helmfile.yaml` in `chart`:
|
||||
|
||||
```yaml
|
||||
releases:
|
||||
- name: foo
|
||||
chart: ./path/to/foo
|
||||
dependencies:
|
||||
- chart: ./path/to/bar
|
||||
```
|
||||
|
||||
Internally, Helmfile creates another temporary chart from the local chart `./path/to/foo`, and modifies the chart's `Chart.yaml` dependencies to look like:
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
- alias: bar
|
||||
name: bar
|
||||
repo: file:///abs/path/to/bar
|
||||
condition: bar.enabled
|
||||
```
|
||||
|
||||
Please read https://github.com/roboll/helmfile/issues/1762#issuecomment-816341251 for more details.
|
||||
|
||||
#### OCI chart dependencies
|
||||
|
||||
With Helmfile version v0.146.0 or later, you can add OCI chart to chart dependencies.
|
||||
|
||||
An example `helmfile.yaml` that adds a OCI chart dependency to the release `foo` looks like the below:
|
||||
|
||||
```yaml
|
||||
releases:
|
||||
- name: foo
|
||||
chart: ./path/to/foo
|
||||
dependencies:
|
||||
- chart: oci://my-oci-registry/helm-repo/envoy
|
||||
version: 1.5
|
||||
```
|
||||
|
||||
### Lockfile per environment
|
||||
|
||||
In some cases it can be handy for CI/CD pipelines to be able to roll out updates gradually for environments, such as staging and production while using the same
|
||||
set of charts. This can be achieved by using `lockFilePath` in combination with environments, such as:
|
||||
|
||||
```yaml
|
||||
environments:
|
||||
staging:
|
||||
production
|
||||
|
||||
---
|
||||
lockFilePath: .helmfile.{{ .Environment.Name}}.lock
|
||||
|
||||
releases:
|
||||
- name: myapp
|
||||
chart: charts/myapp
|
||||
```
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
# helmfile template built-in objects
|
||||
|
||||
- `Environment`: The information about the environment. This is set by the
|
||||
`--environment` flag. It has several objects inside of it:
|
||||
- `Environment.Name`: The name of the environment
|
||||
- `Values`: Values passed into the environment.
|
||||
- `StateValues`: alias for `Values`.
|
||||
- `Namespace`: The namespace to be released into
|
||||
|
||||
# release template built-in objects
|
||||
|
||||
it be used for the below tow cases:
|
||||
|
||||
1. release definition
|
||||
```
|
||||
templates:
|
||||
default:
|
||||
chart: stable/{{`{{ .Release.Name }}`}}
|
||||
namespace: kube-system
|
||||
values:
|
||||
- config/{{`{{ .Release.Name }}`}}/values.yaml
|
||||
- config/{{`{{ .Release.Name }}`}}/{{`{{ .Environment.Name }}`}}.yaml
|
||||
secrets:
|
||||
- config/{{`{{ .Release.Name }}`}}/secrets.yaml
|
||||
- config/{{`{{ .Release.Name }}`}}/{{`{{ .Environment.Name }}`}}-secrets.yaml
|
||||
releases:
|
||||
- name: heapster
|
||||
version: 0.3.2
|
||||
inherit:
|
||||
template: default
|
||||
- name: kubernetes-dashboard
|
||||
version: 0.10.0
|
||||
inherit:
|
||||
template: default
|
||||
```
|
||||
|
||||
2. release values template
|
||||
```
|
||||
releases:
|
||||
- name: some-release
|
||||
chart: my-chart
|
||||
values:
|
||||
# This is a template file can use the built-in objects
|
||||
- path/to/values.gotmpl
|
||||
```
|
||||
|
||||
- `Release`: This object describes the release itself. It has several objects
|
||||
inside of it:
|
||||
- `Release.Name`: The release name
|
||||
- `Release.Namespace`: The namespace to be released into
|
||||
- `Release.Labels`: The labels to be applied to the release
|
||||
- `Release.Chart`: The chart name of the release
|
||||
- `Release.KubeContext`: The kube context to be used for the release
|
||||
- `Values`: Values passed into the environment.
|
||||
- `StateValues`: alias for `Values`.
|
||||
- `Environment`: The information about the environment. This is set by the
|
||||
`--environment` flag. It has several objects inside of it:
|
||||
- `Environment.Name`: The name of the environment
|
||||
- `Chart`: The chart name for the release.
|
||||
- `KubeContext`: The kube context to be used for the release
|
||||
- `Namespace`: The namespace to be released into
|
||||
|
||||
The built-in values always begin with a capital letter. This is in keeping with
|
||||
Go's naming convention. When you define your own values and template variables, you are free to use a
|
||||
convention that suits your team.
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../CONTRIBUTING.md
|
||||
+1577
File diff suppressed because it is too large
Load Diff
Symlink
+1
@@ -0,0 +1 @@
|
||||
../LICENSE
|
||||
@@ -1,4 +1,4 @@
|
||||
# Paths Overivew
|
||||
# Paths Overview
|
||||
|
||||
Using manifest files in conjunction with command line argument can be a bit confusing.
|
||||
|
||||
@@ -7,10 +7,12 @@ A few rules to clear up this ambiguity:
|
||||
- Absolute paths are always resolved as absolute paths
|
||||
- Relative paths referenced *in* the helmfile manifest itself are relative to that manifest
|
||||
- Relative paths referenced on the command line are relative to the current working directory the user is in
|
||||
- Relative paths referenced from within the helmfile loaded from the standard input using `helmfile -f -` are relative to the current working directory
|
||||
|
||||
|
||||
### Examples
|
||||
|
||||
There are several examples that we can go through in the [`/examples`](examples) folder which demonstrate this.
|
||||
There are several examples that we can go through in the [`/examples`](../examples/) folder which demonstrate this.
|
||||
|
||||
**Local Execution**
|
||||
|
||||
@@ -18,7 +20,7 @@ This is an example of a Helmfile manifest referencing a local value directly.
|
||||
|
||||
Indirect:
|
||||
```
|
||||
helmfile -f examples/deployments/local/charts.yaml sync
|
||||
helmfile -f examples/deployments/local/helmfile.yaml sync
|
||||
```
|
||||
|
||||
Direct:
|
||||
@@ -33,7 +35,7 @@ This is an example of a Helmfile manifest using relative paths for values.
|
||||
|
||||
Indirect:
|
||||
```
|
||||
helmfile -f examples/deployments/dev/charts.yaml sync
|
||||
helmfile -f examples/deployments/dev/helmfile.yaml sync
|
||||
```
|
||||
|
||||
Direct:
|
||||
@@ -50,7 +52,7 @@ NOTE: The `--values` is resolved relative to the CWD of the terminal *not* the H
|
||||
|
||||
Indirect:
|
||||
```
|
||||
helmfile -f examples/deployments/dev/charts.yaml sync --values values/replica-values.yaml
|
||||
helmfile -f examples/deployments/dev/helmfile.yaml sync --values values/replica-values.yaml
|
||||
```
|
||||
|
||||
Direct:
|
||||
@@ -0,0 +1,89 @@
|
||||
# Towards Helmfile 1.0
|
||||
|
||||
I'd like to make 3 breaking changes to Helmfile and mark it as 1.0, so that we can better communicate with the current and future Helmfile users about our stance on maintaining Helmfile.
|
||||
|
||||
Note that every breaking change should have an easy alternative way to achieve the same goal achieved using the removed functionality.
|
||||
|
||||
## The changes in 1.0
|
||||
|
||||
1. [Forbid the use of `environments` and `releases` within a single helmfile.yaml.gotmpl part](#forbid-the-use-of-environments-and-releases-within-a-single-helmfileyamlgotmpl-part)
|
||||
2. [Force `.gotmpl` file extension for `helmfile.yaml` in case you want helmfile to render it as a go template before yaml parsing.](#force-gotmpl-file-extension-for-helmfileyaml-in-case-you-want-helmfile-to-render-it-as-a-go-template-before-yaml-parsing)
|
||||
3. [Remove the `--args` flag from the `helmfile` command](#remove-the---args-flag-from-the-helmfile-command)
|
||||
4. [Remove `HELMFILE_SKIP_INSECURE_TEMPLATE_FUNCTIONS` in favor of `HELMFILE_DISABLE_INSECURE_FEATURES`](#remove-helmfile_skip_insecure_template_functions-in-favor-of-helmfile_disable_insecure_features)
|
||||
5. [The long deprecated `charts.yaml` has been finally removed](#the-long-deprecated-chartsyaml-has-been-finally-removed)
|
||||
|
||||
### Forbid the use of `environments` and `releases` within a single helmfile.yaml.gotmpl part
|
||||
|
||||
- Helmfile currently relies on a hack called "double rendering" which no one understands correctly (I suppose) to solve the chicken-and-egg problem of rendering the helmfile template(which requires helmfile to parse `environments` as yaml first) and parsing the rendered helmfile as yaml(which requires helmfile to render the template first).
|
||||
- By forcing (or print a big warning) the user to do separate helmfile parts for `environments` and `releases`, it's very unlikely Helmfile needs double-rendering at all.
|
||||
- After this change, every helmfile.yaml written this way:
|
||||
|
||||
environments:
|
||||
default:
|
||||
values:
|
||||
- foo: bar
|
||||
releases:
|
||||
- name: myapp
|
||||
chart: charts/myapp
|
||||
values:
|
||||
- {{ .Values | toYaml | nindent 4 }}
|
||||
|
||||
must be rewritten like:
|
||||
|
||||
environments:
|
||||
default:
|
||||
values:
|
||||
- foo: bar
|
||||
---
|
||||
releases:
|
||||
- name: myapp
|
||||
chart: charts/myapp
|
||||
values:
|
||||
- {{ .Values | toYaml | nindent 4 }}
|
||||
|
||||
It might not be a deal breaker as you already had to separate helmfile parts when you need to generate `environments` dynamically:
|
||||
|
||||
environments:
|
||||
default:
|
||||
values:
|
||||
- foo: bar
|
||||
---
|
||||
environments:
|
||||
default:
|
||||
values:
|
||||
- {{ .Values | toYaml | nindent 6}}}
|
||||
- bar: baz
|
||||
---
|
||||
releases:
|
||||
- name: myapp
|
||||
chart: charts/myapp
|
||||
values:
|
||||
- {{ .Values | toYaml | nindent 4 }}
|
||||
|
||||
If you're already using any helmfile.yaml files that are written in the first style, do start using `---` today! It will probably reveal and fix unintended template evaluations. If you start using `---` today, you won't need to do anything after Helmfile 1.0.
|
||||
|
||||
### Force `.gotmpl` file extension for `helmfile.yaml` in case you want helmfile to render it as a go template before yaml parsing.
|
||||
|
||||
- As the primary maintainer of the project, I'm tired of explaining why Helmfile renders go template expressions embedded in a yaml comment. [The latest example of it](https://github.com/helmfile/helmfile/issues/127).
|
||||
- When we first introduced helmfile the ability to render helmfile.yaml as a go template, I did propose it to require `.gotmpl` file extension to enable the feature. But none of active helmfile users and contributors at that time agreed with it (although I didn't have a very strong opinion on the matter either), so we enabled it without the extension. I consider it as a tech debt now.
|
||||
|
||||
### Remove the `--args` flag from the `helmfile` command
|
||||
|
||||
- It has been provided as-is, and never intended to work reliably because it's fundamentally flawed because we have no way to specify which args to be passed to which `helm` commands being called by which `helmfile` command.
|
||||
- However, I periodically see a new user finds it's not working and reporting it as a bug([the most recent example](https://github.com/roboll/helmfile/issues/2034#issuecomment-1147059088)). It's not a fault of the user. It's our fault that left this broken feature.
|
||||
- Every use-case previsouly covered (by any chance) with `--args` should be covered in new `helmfile.yaml` fields or flags.
|
||||
|
||||
### Remove `HELMFILE_SKIP_INSECURE_TEMPLATE_FUNCTIONS` in favor of `HELMFILE_DISABLE_INSECURE_FEATURES`
|
||||
|
||||
- This option didn't make much sense in practical. Generally, you'd want to disable all the insecure features altogether to make your deployment secure, or not disable any features. Disabling all is already possible via `HELMFILE_DISABLE_INSECURE_FEATURES `. In addition, `HELMFILE_SKIP_INSECURE_TEMPLATE_FUNCTIONS` literally made every insecure template function to silently skipped without any error or warning, which made debugging unnecessarily hard when the user accidentally used an insecure function.
|
||||
- See https://github.com/helmfile/helmfile/pull/564 for more context.
|
||||
|
||||
### The long deprecated `charts.yaml` has been finally removed
|
||||
|
||||
Helmfile used to load `helmfile.yaml` or `charts.yaml` when you omitted the `-f` flag. `charts.yaml` has been deprecated for a long time but never been removed. We take v1 as a chance to finally remove it.
|
||||
|
||||
## After 1.0
|
||||
|
||||
We won't add any backward-incompatible changes while in 1.x, as long as it's inevitable to fix unseen important bug(s).
|
||||
|
||||
We also quit saying [Helmfile is in its early days](https://github.com/helmfile/helmfile#status) in your README as... it's just untrue today.
|
||||
+17
-17
@@ -1,32 +1,32 @@
|
||||
# Secrets
|
||||
# Secrets
|
||||
|
||||
helmfile can handle secrets using [helm-secrets](https://github.com/zendesk/helm-secrets) plugin or using remote secrets storage
|
||||
(everything that package [vals](https://github.com/variantdev/vals) can handle vault, AWS SSM etc)
|
||||
This section will describe the second use case.
|
||||
helmfile can handle secrets using [helm-secrets](https://github.com/jkroepke/helm-secrets) plugin or using remote secrets storage
|
||||
(everything that package [vals](https://github.com/helmfile/vals) can handle vault, AWS SSM etc)
|
||||
This section will describe the second use case.
|
||||
|
||||
# Remote secrets
|
||||
# Remote secrets
|
||||
|
||||
This paragraph will describe how to use remote secrets storage (vault, SSM etc) in helmfile
|
||||
This paragraph will describe how to use remote secrets storage (vault, SSM etc) in helmfile
|
||||
|
||||
## Fetching single key
|
||||
|
||||
To fetch single key from remote secret storage you can use `fetchSecretValue` template function example below
|
||||
|
||||
```yaml
|
||||
# helmfile.yaml
|
||||
```yaml
|
||||
# helmfile.yaml
|
||||
|
||||
repositories:
|
||||
- name: stable
|
||||
url: https://kubernetes-charts.storage.googleapis.com
|
||||
repositories:
|
||||
- name: stable
|
||||
url: https://kubernetes-charts.storage.googleapis.com
|
||||
|
||||
environments:
|
||||
default:
|
||||
environments:
|
||||
default:
|
||||
values:
|
||||
- service:
|
||||
password: ref+vault://svc/#pass
|
||||
login: ref+vault://svc/#login
|
||||
releases:
|
||||
- name: service
|
||||
- name: service
|
||||
namespace: default
|
||||
labels:
|
||||
cluster: services
|
||||
@@ -37,10 +37,10 @@ releases:
|
||||
- service:
|
||||
login: {{ .Values.service.login | fetchSecretValue }} # this will resolve ref+vault://svc/#pass and fetch secret from vault
|
||||
password: {{ .Values.service.password | fetchSecretValue | quote }}
|
||||
# - values/service.yaml.gotmpl # alternatively
|
||||
# - values/service.yaml.gotmpl # alternatively
|
||||
```
|
||||
## Fetching multiple keys
|
||||
Alternatively you can use `expandSecretRefs` to fetch a map of secrets
|
||||
Alternatively you can use `expandSecretRefs` to fetch a map of secrets
|
||||
```yaml
|
||||
# values/service.yaml.gotmpl
|
||||
service:
|
||||
@@ -53,6 +53,6 @@ This will produce
|
||||
service:
|
||||
login: svc-login # fetched from vault
|
||||
password: pass
|
||||
|
||||
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
Babel==2.9.1
|
||||
click==8.1.2
|
||||
ghp-import==2.0.2
|
||||
gitdb==4.0.9
|
||||
GitPython==3.1.30
|
||||
importlib-metadata==4.11.3
|
||||
Jinja2==3.1.1
|
||||
Markdown==3.3.6
|
||||
MarkupSafe==2.1.1
|
||||
mergedeep==1.3.4
|
||||
mkdocs==1.3.0
|
||||
mkdocs-git-revision-date-localized-plugin==1.0.1
|
||||
packaging==21.3
|
||||
pyparsing==3.0.7
|
||||
python-dateutil==2.8.2
|
||||
pytz==2022.1
|
||||
PyYAML==6.0
|
||||
pyyaml_env_tag==0.1
|
||||
six==1.16.0
|
||||
smmap==5.0.0
|
||||
watchdog==2.1.7
|
||||
zipp==3.7.0
|
||||
@@ -0,0 +1,128 @@
|
||||
# Template Functions
|
||||
|
||||
#### `env`
|
||||
The `env` function allows you to declare a particular environment variable as an optional for template rendering.
|
||||
If the environment variable is unset or empty, the template rendering will continue with an empty string as a value.
|
||||
|
||||
```yaml
|
||||
{{ $envValue := env "envName" }}
|
||||
```
|
||||
|
||||
#### `requiredEnv`
|
||||
The `requiredEnv` function allows you to declare a particular environment variable as required for template rendering.
|
||||
If the environment variable is unset or empty, the template rendering will fail with an error message.
|
||||
|
||||
```yaml
|
||||
{{ $envValue := requiredEnv "envName" }}
|
||||
```
|
||||
|
||||
> If the environment variable value starts with '/' (forward slash) and [Git for Windows](https://git-scm.com/download/win) is used, you must set `MSYS_NO_PATHCONV=1` to preserve values as-is, or the environment variable value will be prefixed with the `C:\Program Files\Git`. [reference](https://github.com/git-for-windows/build-extra/blob/main/ReleaseNotes.md#known-issues)
|
||||
|
||||
#### `exec`
|
||||
The `exec` function allows you to run a command, returning the stdout of the command. When the command fails, the template rendering will fail with an error message.
|
||||
|
||||
```yaml
|
||||
{{ $cmdOutpot := exec "./mycmd" (list "arg1" "arg2" "--flag1") }}
|
||||
```
|
||||
|
||||
#### `envExec`
|
||||
The `envExec` function allows you to run a command with environment variables declared on-the-fly in addition to existing environment variables, returning the stdout of the command. When the command fails, the template rendering will fail with an error message.
|
||||
|
||||
```yaml
|
||||
{{ $cmdOutpot := envExec (dict "envKey" "envValue") "./mycmd" (list "arg1" "arg2" "--flag1") }}
|
||||
```
|
||||
|
||||
#### `readFile`
|
||||
The `readFile` function allows you to read a file and return its content as the function output. On failure, the template rendering will fail with an error message.
|
||||
|
||||
```yaml
|
||||
{{ $fileContent := readFile "./myfile" }}
|
||||
```
|
||||
|
||||
#### `readDir`
|
||||
The `readDir` function returns a list of the relative paths to the files contained within the directory. (No folders included. Use `readDirEntries` if you need folders too)
|
||||
|
||||
```yaml
|
||||
{{ range $index,$item := readDir "./testdata/tmpl/sample_folder/" }}
|
||||
{{- $itemSplit := splitList "/" $item -}}
|
||||
{{- if contains "\\" $item -}}
|
||||
{{- $itemSplit = splitList "\\" $item -}}
|
||||
{{- end -}}
|
||||
{{- $itemValue := $itemSplit | last -}}
|
||||
{{- $itemValue -}}
|
||||
{{- end -}}
|
||||
```
|
||||
|
||||
#### `readDirEntries`
|
||||
The `readDirEntries` function returns a list of [DirEntry](https://pkg.go.dev/os#DirEntry) contained within the directory
|
||||
|
||||
```yaml
|
||||
{{ range $index,$item := readDirEntries "./testdata/tmpl/sample_folder/" }}
|
||||
{{- if $item.IsDir -}}
|
||||
{{- $item.Name -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
```
|
||||
|
||||
#### `toYaml`
|
||||
The `toYaml` function allows you to convert a value to YAML string. When has failed, the template rendering will fail with an error message.
|
||||
|
||||
```yaml
|
||||
{{ $yaml := $value | toYaml }}
|
||||
```
|
||||
|
||||
#### `fromYaml`
|
||||
The `fromYaml` function allows you to convert a YAML string to a value. When has failed, the template rendering will fail with an error message.
|
||||
|
||||
```yaml
|
||||
{{ $value := $yamlString | fromYaml }}
|
||||
```
|
||||
|
||||
#### `setValueAtPath`
|
||||
The `setValueAtPath` function allows you to set a value at a path. When has failed, the template rendering will fail with an error message.
|
||||
|
||||
```yaml
|
||||
{{ $value | setValueAtPath "path.key" $newValue }}
|
||||
```
|
||||
|
||||
#### `get`
|
||||
The `get` function allows you to get a value at a path. when defaultValue not set. It will return nil. When has failed, the template rendering will fail with an error message.
|
||||
|
||||
```yaml
|
||||
{{ $Getvalue := $value | get "path.key" "defaultValue" }}
|
||||
```
|
||||
|
||||
#### `getOrNil`
|
||||
The `getOrNil` function allows you to get a value at a path. when defaultValue not set. It will return nil. When has failed, the template rendering will fail with an error message.
|
||||
|
||||
```yaml
|
||||
{{ $GetOrNlvalue := $value | getOrNil "path.key" }}
|
||||
```
|
||||
|
||||
#### `tpl`
|
||||
The `tpl` function allows you to render a template. When has failed, the template rendering will fail with an error message.
|
||||
|
||||
```yaml
|
||||
{{ $tplValue := $value | tpl "{{ .Value.key }}" }}
|
||||
```
|
||||
|
||||
#### `required`
|
||||
The `required` function returns the second argument as-is only if it is not empty. If empty, the template rendering will fail with an error message containing the first argument.
|
||||
|
||||
```yaml
|
||||
{{ $requiredValue := $value | required "value not set" }}
|
||||
```
|
||||
|
||||
#### `fetchSecretValue`
|
||||
The `fetchSecretValue` function parses the argument as a [vals](https://github.com/helmfile/vals) ref URL, retrieves and returns the remote secret value referred by the URL. In case it failed to access the remote secret backend for whatever reason or the URL was invalid, the template rendering will fail with an error message.
|
||||
|
||||
```yaml
|
||||
{{ $fetchSecretValue := fetchSecretValue "secret/path" }}
|
||||
```
|
||||
|
||||
#### `expandSecretRefs`
|
||||
The `expandSecretRefs` function takes an object as the argument and expands every [vals](https://github.com/helmfile/vals) secret reference URL embedded in the object's values. See ["Remote Secrets" page in our documentation](./remote-secrets.md) for more information.
|
||||
|
||||
```yaml
|
||||
{{ $expandSecretRefs := $value | expandSecretRefs }}
|
||||
```
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../USERS.md
|
||||
+74
-10
@@ -2,6 +2,23 @@
|
||||
|
||||
This guide covers the Helmfile’s considered patterns for writing advanced helmfiles. It focuses on how helmfile should be structured and executed.
|
||||
|
||||
## Helmfile .Values vs Helm .Values
|
||||
|
||||
Templating engine of Helmfile uses the same pipeline name `.Values` as Helm, so in some use-cases `.Values` of Helmfile and
|
||||
Helm can be seen in the same file. To distinguish these two kinds of `.Values`, Helmfile provides an alias `.StateValues`
|
||||
for its `.Values`.
|
||||
|
||||
```
|
||||
app:
|
||||
project: {{.Environment.Name}}-{{.StateValues.project}} # Same as {{.Environment.Name}}-{{.Values.project}}
|
||||
|
||||
{{`
|
||||
extraEnvVars:
|
||||
- name: APP_PROJECT
|
||||
value: {{.Values.app.project}}
|
||||
`}}
|
||||
```
|
||||
|
||||
## Missing keys and Default values
|
||||
|
||||
helmfile tries its best to inform users for noticing potential mistakes.
|
||||
@@ -68,7 +85,7 @@ It allows you to abstract away the repetitions in releases into a template, whic
|
||||
|
||||
```yaml
|
||||
templates:
|
||||
default: &default
|
||||
default:
|
||||
chart: stable/{{`{{ .Release.Name }}`}}
|
||||
namespace: kube-system
|
||||
# This prevents helmfile exiting when it encounters a missing file
|
||||
@@ -84,15 +101,19 @@ templates:
|
||||
|
||||
releases:
|
||||
- name: heapster
|
||||
<<: *default
|
||||
version: 0.3.2
|
||||
inherit:
|
||||
template: default
|
||||
- name: kubernetes-dashboard
|
||||
<<: *default
|
||||
version: 0.10.0
|
||||
inherit:
|
||||
template: default
|
||||
```
|
||||
|
||||
Release Templating supports the following parts of release definition:
|
||||
- basic fields: `name`, `namespace`, `chart`, `version`
|
||||
- boolean fields: `installed`, `wait`, `tillerless`, `verify` by the means of additional text
|
||||
fields designed for templating only: `installedTemplate`, `waitTemplate`, `tillerlessTemplate`, `verifyTemplate`
|
||||
- boolean fields: `installed`, `wait`, `waitForJobs`, `verify` by the means of additional text
|
||||
fields designed for templating only: `installedTemplate`, `waitTemplate`, `verifyTemplate`
|
||||
```yaml
|
||||
# ...
|
||||
installedTemplate: '{{`{{ eq .Release.Namespace "kube-system" }}`}}'
|
||||
@@ -125,7 +146,13 @@ Release Templating supports the following parts of release definition:
|
||||
# ...
|
||||
```
|
||||
|
||||
See the [issue 428](https://github.com/roboll/helmfile/issues/428) for more context on how this is supposed to work.
|
||||
Previously, we've been using YAML anchors for release template inheritance.
|
||||
It turned out not work well when you wanted to nest templates for complex use cases and/or you want a fine control over which fields to inherit or not.
|
||||
Thus we added a new way for inheritance, which uses the `inherit` field we introduced above.
|
||||
|
||||
See [issue helmfile/helmfile#435](https://github.com/helmfile/helmfile/issues/435#issuecomment-1362177510) for more context.
|
||||
|
||||
You might also find [issue roboll/helmfile#428](https://github.com/roboll/helmfile/issues/428) useful for more context on how we originally designed the relase template and what it's supposed to solve.
|
||||
|
||||
## Layering Release Values
|
||||
|
||||
@@ -256,7 +283,7 @@ In the following example `helmfile.yaml.gotmpl`, each `---` separated part of th
|
||||
`helmfile.yaml.gotmpl`:
|
||||
|
||||
```yaml
|
||||
# Part 1: Reused Enviroment Values
|
||||
# Part 1: Reused Environment Values
|
||||
bases:
|
||||
- myenv.yaml
|
||||
---
|
||||
@@ -301,7 +328,6 @@ Where the gotmpl file loaded in the second part looks like:
|
||||
|
||||
```yaml
|
||||
helmDefaults:
|
||||
tillerNamespace: kube-system
|
||||
kubeContext: {{ .Values.kubeContext }}
|
||||
verify: false
|
||||
{{ if .Values.wait }}
|
||||
@@ -314,13 +340,12 @@ helmDefaults:
|
||||
force: true
|
||||
```
|
||||
|
||||
Each go template is rendered in the context where `.Values` is inherited from the previous part.
|
||||
Each go template is rendered in the context where `.Values` is inherited from the previous part.
|
||||
|
||||
So in `mydefaults.yaml.gotmpl`, both `.Values.kubeContext` and `.Values.wait` are valid as they do exist in the environment values inherited from the previous part(=the first part) of your `helmfile.yaml.gotmpl`, and therefore the template is rendered to:
|
||||
|
||||
```yaml
|
||||
helmDefaults:
|
||||
tillerNamespace: kube-system
|
||||
kubeContext: test
|
||||
verify: false
|
||||
wait: false
|
||||
@@ -356,3 +381,42 @@ releases:
|
||||
repository: "nginx"
|
||||
tag: "latest"
|
||||
```
|
||||
|
||||
## Re-using environment state in sub-helmfiles
|
||||
|
||||
Do you want to decouple the environment state loading from the sub-helmfiles and load it only once?
|
||||
|
||||
This example shows how to do this:
|
||||
|
||||
```yaml
|
||||
environments:
|
||||
stage:
|
||||
values:
|
||||
- env/stage.yaml
|
||||
prod:
|
||||
values:
|
||||
- env/prod.yaml
|
||||
---
|
||||
|
||||
helmfiles:
|
||||
- path: releases/myrelease/helmfile.yaml
|
||||
values:
|
||||
- {{ toYaml .Values | nindent 4 }}
|
||||
# pass the current state values to the sub-helmfile
|
||||
# add other values to use overlay logic here
|
||||
```
|
||||
|
||||
and `releases/myrelease/helmfile.yaml` is as DRY as
|
||||
|
||||
```yaml
|
||||
releases:
|
||||
- name: mychart-{{ .Values.myrelease.myname }}
|
||||
installed: {{ .Values | get "myrelease.enabled" false }}
|
||||
chart: mychart
|
||||
version: {{ .Values.myrelease.version }}
|
||||
labels:
|
||||
chart: mychart
|
||||
values:
|
||||
- values.yaml.gotmpl
|
||||
# templated values would also inherit the values passed from upstream
|
||||
```
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
apiVersion: v2
|
||||
name: argo-cd
|
||||
description: Kubernetes continuous deployment system
|
||||
type: application
|
||||
version: 3.2.0
|
||||
appVersion: "3.2.0" # ArgoCD version
|
||||
|
||||
dependencies:
|
||||
- name: argo-cd
|
||||
version: 3.2.0
|
||||
repository: https://argoproj.github.io/argo-helm
|
||||
@@ -0,0 +1,136 @@
|
||||
# ArgoCD Deployment
|
||||
|
||||
This example is intended to show how you can use Helmfile with ArgoCD in a private way. The example deployment demonstrates how to deploy ArgoCD with the helmfile plugin, and how to use helmfile within ArgoCD to connect to various private external systems.
|
||||
|
||||
If you intend to follow this example, please pay close attention to the caveats. This chart will not work if you deploy it as-is, you will need to customise many parts of it to match the infrastructure of your organisation.
|
||||
|
||||
## What is the point of this chart?
|
||||
|
||||
There are many organisations out there which would like to widen Kubernetes adoption but struggle with the requirement to keep all data confidential and follow best practices. For example, one best practice is to to implement a CI/CD process for all deployments including Helm charts. As part of this process we may want to keep secrets in private Vault secret storage, use Helm charts from a private chart museum, customised Helm values from a private Git repository and docker images from a private docker repository. ArgoCD alone cannot do all of these things at once, but we can achieve this if we configure it with Helmfile.
|
||||
|
||||
This example is intended to show how you COULD use ArgoCD together with Helmfile to implement a fully private CI/CD system. Your deployment may be a litte bit different, or very different to this example. It's intended only as an example.
|
||||
|
||||
The helm chart in this repo can be deployed with Helmfile, if you follow the prerequisites. In order for the deployment to work correctly, you will need to follow the prerequisites and update all of the `values.yaml` file entries with a `# TODO` comment.
|
||||
|
||||
# Deployment planning
|
||||
|
||||
* Getting started: https://argoproj.github.io/argo-cd/getting_started/
|
||||
* Helm Chart: https://github.com/argoproj/argo-helm/tree/master/charts/argo-cd
|
||||
* SSO Setup: https://argoproj.github.io/argo-cd/operator-manual/user-management/microsoft/#azure-ad-app-registration-auth-using-oidc
|
||||
* RBAC: https://medium.com/dzerolabs/configuring-sso-with-azure-active-directory-on-argocd-d20be4ba753b
|
||||
* Vault
|
||||
* Private Chartmuseum (Harbor)
|
||||
|
||||
What are the interactions between the different systems?
|
||||
|
||||
* ArgoCD will run on Kubernetes. We must use a custom build of ArgoCD as the official build doesn't ship with the runtime binaries which we will need to execute Helmfile and interact with Vault.
|
||||
* ArgoCD interacts with AzureAD for SSO. ArgoCD has to be configured as an enterprise app in Azure AD for authorization and authentication by an administrator. We put the client ID and client secrets into Vault for safe keeping.
|
||||
* ArgoCD interacts with Vault in order to pull secrets for customising Helm deployments. Sometimes we need to inject secrets into the values.yaml file of a Helm deployment. ArgoCD will use an AppRole for authentication into Vault, and this is done once during the deployment. The ArgoCD Vault credentials are saved in Vault for safe keeping.
|
||||
* ArgoCD may need to interact with a private Git server. This is so that ArgoCD can access the `helmfile.yaml` files before executing Helmfile. Additional configuration files (like `conf.d` and other `values.yaml`) files can be kept in here.
|
||||
* ArgoCD will authenticate to a private chart museum. This is so that we can keep the private helm charts private.
|
||||
|
||||
# Prerequisites
|
||||
|
||||
## Docker image
|
||||
|
||||
* Build the docker image from the `argocd.dockerfile` file in this folder.
|
||||
* Upload the image to your private docker registry and make a note of the registry URL and tag.
|
||||
* Customise the `values.yaml` file in this folder.
|
||||
Replace `argo-cd.global.image.repository` for the docker registry URL of your private registry.
|
||||
Replace `argo-cd.global.image.tag` for the tag which you used to upload your private docker image.
|
||||
|
||||
## Configure Single Sign-On for ArgoCD
|
||||
|
||||
* Log into AzureAD as an administrator.
|
||||
* Create a new App Registration in your tenant for ArgoCD.
|
||||
* Create a new Client ID and client secret.
|
||||
* Save the client ID and client secret into your vault server.
|
||||
- Save the client ID and client secret into Vault. Suggested keys: `internal/argocd/auth#azure_oidc_client_id` and `internal/argocd/auth#azure_oidc_client_secret`.
|
||||
* Update `values.yaml` for your deployment
|
||||
- Update `argo-cd.server.config.oidc.config` - replace `SOME_AZURE_AD_TENANT` for your actual plaintext AzureAD tenant ID.
|
||||
- Update `argo-cd.server.config.oidc.config` - replace `SOME_AZURE_AD_UUID` for your actual plaintext AzureAD client app ID.
|
||||
- Verify that the Vault path specified in `argo-cd.config.secret.extra.oidc.azure.clientSecret` is the correct path in Vault.
|
||||
* Customise the ingress objects in the source to match what your expected external URL will be. Update `values.yaml` to have the correct values for the hostname you plan to use.
|
||||
- `argo-cd.config.server.(url|hostname)`
|
||||
```yaml
|
||||
url: 'https://my.argocd.deployment.org' # TODO
|
||||
hostname: my.argocd.deployment.org # TODO
|
||||
```
|
||||
|
||||
## Configure authentication from ArgoCD into Vault (or read next section on how to disable it)
|
||||
|
||||
* Read the documentation from Hashicorp on AppRoles. Provision a new AppRole for ArgoCD with policies.
|
||||
- In the example the authentication path in Vault is assumed to be `auth/approle/login`. If that's not the case then update line 123 in `values.yaml`.
|
||||
* Store the AppRole credentials for ArgoCD in Vault. We will use Helmfile to pull them from here when we launch the chart.
|
||||
- Suggested paths: `internal/vault/argocd#role_id` and `internal/vault/argocd#secret_id`
|
||||
* Verify that the Vault paths in `values.yaml` are correct
|
||||
- `argo-cd.config.secret.extra.vault_role_id`
|
||||
- `argo-cd.config.secret.extra.vault_secret_id`
|
||||
* Replace `ROLE_ID` and `SECRET_ID` in `values.yaml` (line 124) with the actual plaintext values (values key `argocd.server.config.configManagementPlugins`). This is not 100% secure but was the only way that I could manage to get the rest of the deployment working. I advise you add `values.yaml` to `.gitignore` after doing this step.
|
||||
|
||||
## Disable Vault (optional)
|
||||
|
||||
If you are not going to use Vault, then you need to update the values to get your deployment to use Helmfile.
|
||||
* If you are not going to use Vault, then remove the line in `values.yaml` that starts with `export VAULT_TOKEN=...`.
|
||||
|
||||
## Configure authentication from ArgoCD into private Git repository
|
||||
|
||||
* Create a new API user in your Git system (username + password auth). ArgoCD will use these credentials to pull `values.yaml` and `helmfile.yaml` for your project.
|
||||
* Save the username and password into Vault
|
||||
- Recommended keys: `internal/git/users/argocd#username` and `internal/git/users/argocd#password`
|
||||
* Verify that the Vault paths in `values.yaml` are correct
|
||||
- `argo-cd.config.secret.extra.git_username`
|
||||
- `argo-cd.config.secret.extra.git_password`
|
||||
* Update the settings in `values.yaml` for repositories - remove the example Git URLS and replace with actual Git URLs for your project.
|
||||
- `https://my.git.server.org/my-team/my-repo-one.git`
|
||||
- `https://my.git.server.org/my-team/my-repo-two.git`
|
||||
|
||||
## Configure authentication from ArgoCD to your private Docker registry and Chartmuseum
|
||||
|
||||
* This guide is written with Harbor in mind, which is both a docker registry and chart museum. If you have separate systems in place you might need to configure these things separately.
|
||||
* Create a new set of credentials for Harbor and save them into Vault.
|
||||
- Suggested paths: `internal/harbor/users/argocd#username`, `internal/harbor/users/argocd#username`.
|
||||
* Verify that the Vault paths in `values.yaml` are correct
|
||||
- `argo-cd.config.secret.extra.harbor_username`
|
||||
- `argo-cd.config.secret.extra.harbor_password`
|
||||
* Change the Helm URL on line 95 in `values.yaml` for the actual URL of your Helm Chartmuseum deployment.
|
||||
- `https://my.harbor.deployment.org/chartrepo/my-project`
|
||||
|
||||
# Deploy ArgoCD for the first time
|
||||
|
||||
Once you've completed the prerequisites you are ready to deploy ArgoCD for the first time. There is a helmfile that you can use to do this.
|
||||
|
||||
Steps:
|
||||
|
||||
* Auth to Vault and Kubernetes
|
||||
* Add ArgoCD Helm repo
|
||||
- ```bash
|
||||
helm repo add argo https://argoproj.github.io/argo-helm
|
||||
helm install --name argocd --namespace argocd argo/argo-cd
|
||||
```
|
||||
* Download dependencies (`helmfile dep update`)
|
||||
* Deploy chart (`helmfile apply`)
|
||||
- If everything worked correctly, then ArgoCD should have deployed using your customised docker image into your cluster.
|
||||
|
||||
* If the ingress deployed correctly then we should be able to access the UI: https://my.argocd.deployment.org
|
||||
* Grab the admin password from `argocd-initial-admin-secret secret` and use it to log into the UI.
|
||||
```bash
|
||||
echo "<password>" | base64 -d
|
||||
```
|
||||
* Verify that SSO works by signing on with AzureAD.
|
||||
* Verify that Helmfile appears in the list of configured plugins in the UI.
|
||||
|
||||
# Configure ArgoCD apps for deployment with Helmfile
|
||||
|
||||
* Log into ArgoCD in the UI and configure a new app.
|
||||
* Make sure that it's using the Helmfile plugin.
|
||||
* For the source, select one of the Git repos which you configured previously.
|
||||
* Make sure that your Helm repo (including `helmfile.yaml` is present in the Git repository).
|
||||
* ArgoCD will pull the project from your private Git server and read the `helmfile.yaml` file.
|
||||
* ArgoCD will also execute helmfile directly in the container using the files checked out from your project.
|
||||
* If you configured ArgoCD with logins for Vault and private Chartmuseums, these can be used in ArgoCD by Helmfile.
|
||||
* You can use the diff feature and resource views in ArgoCD when using Helmfile. Unfortunately you can no longer use the `values.yaml` editor in the UI. You can change `values.yaml` for deployments by creating `values.yaml` files and committing them to the folders in your project Git repo. You can also customise them using `!vault` tags.
|
||||
|
||||
If you would like an example, this ArgoCD + helmfile deployment can itself be deployed by ArgoCD + helmfile (you may need to drop the pods after you do deploy this way). Any other projects - just structure them in the same way as this example.
|
||||
|
||||
Be careful when reconfiguring a project which was previously deploy by ArgoCD with Helmfile. There are some differences around the way in which annotations are used.
|
||||
@@ -0,0 +1,27 @@
|
||||
# Custom Dockerfile to install required helm plugins
|
||||
FROM argoproj/argocd:latest
|
||||
|
||||
USER root
|
||||
# Download OS dependencies
|
||||
RUN apt-get update && \
|
||||
apt-get install -y \
|
||||
curl git wget unzip && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
# Download helmfile
|
||||
RUN wget https://github.com/roboll/helmfile/releases/download/v0.138.7/helmfile_linux_amd64 && \
|
||||
mv helmfile_linux_amd64 /usr/local/bin/helmfile && \
|
||||
chmod a+x /usr/local/bin/helmfile
|
||||
|
||||
# Download Vault
|
||||
RUN wget https://releases.hashicorp.com/vault/1.5.0/vault_1.5.0_linux_amd64.zip -O /tmp/vault.zip --quiet && \
|
||||
unzip -p /tmp/vault.zip vault > /usr/local/bin/vault && \
|
||||
chmod a+x /usr/local/bin/vault && \
|
||||
rm /tmp/vault.zip
|
||||
|
||||
USER argocd
|
||||
|
||||
# Install helm-secrets plugin (as argocd user)
|
||||
RUN helm plugin install https://github.com/jkroepke/helm-secrets --version v3.6.0
|
||||
|
||||
ENV HELM_PLUGINS="/home/argocd/.local/share/helm/plugins/"
|
||||
@@ -0,0 +1,43 @@
|
||||
# What is a helmfile? Read here...
|
||||
# https://github.com/helmfile/helmfile
|
||||
|
||||
# Before deployment, export the required env vars
|
||||
# export HELM_SECRETS_DRIVER=vault
|
||||
|
||||
# Commands to deploy:
|
||||
# helmfile deps # < download dependencies
|
||||
# helmfile template # < template stuff. You might have to be signed into vault
|
||||
# helmfile sync # < deploy everything
|
||||
# helmfile apply # < deploy diff only
|
||||
|
||||
helmDefaults:
|
||||
wait: true
|
||||
timeout: 600
|
||||
recreatePods: true
|
||||
force: false
|
||||
|
||||
commonLabels:
|
||||
system: argocd
|
||||
|
||||
releases:
|
||||
- name: argocd # name of this release
|
||||
namespace: argocd # target namespace
|
||||
createNamespace: true # helm 3.2+ automatically create release namespace (default true)
|
||||
labels: # Arbitrary key value pairs for filtering releases
|
||||
env: prod
|
||||
chart: "." # the chart being installed to create this release, referenced by `repository/chart` syntax
|
||||
version: 3.2.2 # the semver of the chart. range constraint is supported
|
||||
missingFileHandler: Warn # set to either "Error" or "Warn". "Error" instructs helmfile to fail when unable to find a values or secrets file. When "Warn", it prints the file and continues.
|
||||
|
||||
# will attempt to decrypt secrets using helm-secrets plugin
|
||||
secrets:
|
||||
# {{ requiredEnv "HELM_SECRETS_DRIVER" }}
|
||||
- values.yaml
|
||||
verify: false
|
||||
wait: true
|
||||
timeout: 600
|
||||
recreatePods: true
|
||||
force: false
|
||||
installed: true
|
||||
atomic: true
|
||||
cleanupOnFail: false
|
||||
@@ -0,0 +1,26 @@
|
||||
kind: Ingress
|
||||
apiVersion: extensions/v1beta1
|
||||
metadata:
|
||||
name: argocd-ingress
|
||||
namespace: argocd
|
||||
labels:
|
||||
app: argocd
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
app.kubernetes.io/component: ingress
|
||||
app.kubernetes.io/name: argocd-ingress
|
||||
app.kubernetes.io/part-of: argocd
|
||||
annotations:
|
||||
external-dns.alpha.kubernetes.io/hostname: {{ .Values.argo-cd.server.config.hostname }}
|
||||
spec:
|
||||
tls:
|
||||
- hosts:
|
||||
- {{ .Values.argo-cd.server.config.hostname }}
|
||||
secretName: argocd-secret
|
||||
rules:
|
||||
- host: {{ .Values.argo-cd.server.config.hostname }}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
backend:
|
||||
serviceName: argocd-server
|
||||
servicePort: http
|
||||
@@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
type: kubernetes.io/dockerconfigjson
|
||||
metadata:
|
||||
name: dockerpullsecrets
|
||||
stringData:
|
||||
.dockerconfigjson: |
|
||||
{
|
||||
"auths" : {
|
||||
"my.harbor.deployment.org" : {
|
||||
{{ $authstring := (printf "%v:%v" .Values.secrets.harbor.username .Values.secrets.harbor.password ) }}
|
||||
"auth": "{{ $authstring | b64enc }}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
type: secret
|
||||
metadata:
|
||||
name: argocd-git
|
||||
stringData:
|
||||
username: "{{ .Values.secrets.git.username }}"
|
||||
password: "{{ .Values.secrets.git.password }}"
|
||||
@@ -0,0 +1,8 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
type: secret
|
||||
metadata:
|
||||
name: argocd-harbor
|
||||
stringData:
|
||||
username: "{{ .Values.secrets.harbor.username }}"
|
||||
password: "{{ .Values.secrets.harbor.password }}"
|
||||
@@ -0,0 +1,8 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
type: secret
|
||||
metadata:
|
||||
name: argocd-vault
|
||||
stringData:
|
||||
role_id: "{{ .Values.secrets.vault.role_id }}"
|
||||
secret_id: "{{ .Values.secrets.vault.secret_id }}"
|
||||
@@ -0,0 +1,154 @@
|
||||
# TODO - Any of the fields with TODO must be changed for the deployment to work.
|
||||
secrets:
|
||||
harbor:
|
||||
username: !vault internal/harbor/users/argocd#username # TODO
|
||||
password: !vault internal/harbor/users/argocd#password # TODO
|
||||
git:
|
||||
username: !vault internal/git/users/argocd#username # TODO
|
||||
password: !vault internal/git/users/argocd#password # TODO
|
||||
vault:
|
||||
role_id: !vault internal/vault/argocd#role_id # TODO
|
||||
secret_id: !vault internal/vault/argocd#secret_id # TODO
|
||||
|
||||
argo-cd:
|
||||
global:
|
||||
image:
|
||||
repository: my.private.docker.registry.local/internal/argocd # TODO
|
||||
tag: 1.2.3 # TODO
|
||||
imagePullPolicy: IfNotPresent
|
||||
securityContext: {}
|
||||
# runAsUser: 999
|
||||
# runAsGroup: 999
|
||||
# fsGroup: 999
|
||||
imagePullSecrets:
|
||||
- name: dockerpullsecrets
|
||||
hostAliases: []
|
||||
# - ip: 10.20.30.40
|
||||
# hostnames:
|
||||
# - git.myhostname
|
||||
# set harbor creds in environment, set HELM_SECRET_DRIVER as well
|
||||
server:
|
||||
## Argo server log format: text|json
|
||||
logFormat: text
|
||||
## Argo server log level
|
||||
logLevel: debug
|
||||
env:
|
||||
- name: "HELM_SECRETS_DRIVER"
|
||||
value: "vault"
|
||||
- name: "HARBOR_USERNAME"
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: argocd-harbor
|
||||
key: username
|
||||
- name: "HARBOR_PASSWORD"
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: argocd-harbor
|
||||
key: password
|
||||
# Unfortunately these envvars don't seem to be working correctly.
|
||||
- name: "VAULT_APPROLE_ID"
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: argocd-vault
|
||||
key: role_id
|
||||
- name: "VAULT_APPROLE_SECRET"
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: argocd-vault
|
||||
key: secret_id
|
||||
config:
|
||||
application.instanceLabelKey: argocd.argoproj.io/instance
|
||||
# TODO - customise oidc.config - replace SOME_AZURE_AD_TENANT and SOME_AZURE_AD_UUID with the plaintext values
|
||||
oidc.config: |
|
||||
name: Azure
|
||||
issuer: SOME_AZURE_AD_TENANT
|
||||
clientID: SOME_AZURE_AD_UUID
|
||||
clientSecret: $oidc.azure.clientSecret
|
||||
requestedIDTokenClaims:
|
||||
groups:
|
||||
essential: true
|
||||
requestedScopes:
|
||||
- openid
|
||||
- profile
|
||||
- email
|
||||
# TODO - customise repositories to include correct git URLs
|
||||
repositories: |
|
||||
- type: git
|
||||
url: https://my.git.server.org/my-team/my-repo-one.git
|
||||
usernameSecret:
|
||||
key: username
|
||||
name: argocd-git
|
||||
passwordSecret:
|
||||
key: password
|
||||
name: argocd-git
|
||||
- type: git
|
||||
url: https://my.git.server.org/my-team/my-repo-two.git
|
||||
usernameSecret:
|
||||
name: argocd-git
|
||||
key: username
|
||||
passwordSecret:
|
||||
name: argocd-git
|
||||
key: password
|
||||
|
||||
- type: helm
|
||||
url: https://my.harbor.deployment.org/chartrepo/my-project
|
||||
usernameSecret:
|
||||
name: argocd-harbor
|
||||
key: username
|
||||
passwordSecret:
|
||||
name: argocd-harbor
|
||||
key: password
|
||||
|
||||
url: 'https://my.argocd.deployment.org' # TODO
|
||||
hostname: my.argocd.deployment.org # TODO
|
||||
configManagementPlugins: |
|
||||
# If you just want to use helm-secrets this will work.
|
||||
- name: helm-secrets
|
||||
generate: # Command to generate manifests YAML
|
||||
command: ["/bin/bash", "-c"]
|
||||
args: ["echo \"$HELM_VALUES\" > ./values-local.yaml && helm secrets -d vault template $HELM_OPTS -n $ARGOCD_APP_NAMESPACE -f ./values-local.yaml $ARGOCD_APP_NAME . && rm ./values-local.yaml"]
|
||||
|
||||
# You can use helm-secrets via helmfile also.
|
||||
- name: helmfile
|
||||
init:
|
||||
command: ["/bin/bash", "-c"]
|
||||
args:
|
||||
- >
|
||||
helmfile repos ;
|
||||
helm dependency update ;
|
||||
true
|
||||
generate:
|
||||
command: ["/bin/bash", "-c"]
|
||||
args:
|
||||
- >
|
||||
export VAULT_TOKEN=$(vault write auth/approle/login role_id=ROLE_ID secret_id=SECRET_ID | grep token | head -n 1 | tr -s ' ' | cut -d ' ' -f 2) &&
|
||||
helmfile template --skip-deps
|
||||
# ^^^ I am not sure why but I can't seem to get it working without hardcoding the secrets here :(
|
||||
# TODO - replace ROLE_ID and SECRET_ID with the actual plaintext values
|
||||
|
||||
## ArgoCD rbac config
|
||||
## reference https://github.com/argoproj/argo-cd/blob/master/docs/operator-manual/rbac.md
|
||||
rbacConfig:
|
||||
policy.csv: |
|
||||
p, role:org-admin, applications, *, */*, allow
|
||||
p, role:org-admin, clusters, get, *, allow
|
||||
p, role:org-admin, repositories, get, *, allow
|
||||
p, role:org-admin, repositories, create, *, allow
|
||||
p, role:org-admin, repositories, update, *, allow
|
||||
p, role:org-admin, repositories, delete, *, allow
|
||||
g, "OrgAdmin", role:org-admin
|
||||
g, "ReadOnly", role:readonly
|
||||
policy.default: 'role:readonly'
|
||||
scopes: '[roles, email]'
|
||||
|
||||
## Argo Configs
|
||||
configs:
|
||||
secret:
|
||||
extra:
|
||||
oidc.azure.clientSecret: !vault internal/argocd/auth#azure_oidc_client_secret # TODO
|
||||
harbor_username: !vault internal/harbor/users/argocd#username # TODO
|
||||
harbor_password: !vault internal/harbor/users/argocd#password # TODO
|
||||
git_username: !vault internal/git/users/argocd#username # TODO
|
||||
git_password: !vault internal/git/users/argocd#password # TODO
|
||||
vault_role_id: !vault internal/vault/argocd#role_id # TODO
|
||||
vault_secret_id: !vault internal/vault/argocd#secret_id # TODO
|
||||
@@ -1,4 +1,4 @@
|
||||
charts:
|
||||
releases:
|
||||
- name: dev-paths-example
|
||||
namespace: dev
|
||||
chart: ../../charts/paths-example/
|
||||
@@ -1,4 +1,4 @@
|
||||
charts:
|
||||
releases:
|
||||
- name: local-paths-example
|
||||
namespace: local
|
||||
chart: ../../charts/paths-example/
|
||||
@@ -1,4 +1,4 @@
|
||||
charts:
|
||||
releases:
|
||||
- name: prod-paths-example
|
||||
namespace: prod
|
||||
chart: ../../charts/paths-example/
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
charts:
|
||||
releases:
|
||||
# Published chart example
|
||||
- name: grafana # helm release name
|
||||
namespace: grafana # target namespace
|
||||
@@ -0,0 +1,10 @@
|
||||
helmfiles:
|
||||
- path: git::https://github.com/cloudposse/helmfiles.git@releases/echo-server/helmfile.yaml?ref=master
|
||||
values:
|
||||
- installed: true
|
||||
stage: test
|
||||
environment: test
|
||||
|
||||
releases:
|
||||
- name: apache
|
||||
chart: git::https://github.com/bitnami/charts.git@bitnami/apache?ref=master
|
||||
@@ -1,38 +1,216 @@
|
||||
module github.com/roboll/helmfile
|
||||
module github.com/helmfile/helmfile
|
||||
|
||||
go 1.14
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/Azure/azure-sdk-for-go v35.0.0+incompatible // indirect
|
||||
github.com/Masterminds/semver/v3 v3.1.0
|
||||
github.com/Masterminds/sprig/v3 v3.1.0
|
||||
github.com/Masterminds/semver/v3 v3.2.0
|
||||
github.com/Masterminds/sprig/v3 v3.2.3
|
||||
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/frankban/quicktest v1.11.2 // indirect
|
||||
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32
|
||||
github.com/go-test/deep v1.0.7
|
||||
github.com/golang/mock v1.4.4
|
||||
github.com/google/go-cmp v0.5.2
|
||||
github.com/go-test/deep v1.1.0
|
||||
github.com/goccy/go-yaml v1.10.1
|
||||
github.com/golang/mock v1.6.0
|
||||
github.com/google/go-cmp v0.5.9
|
||||
github.com/gosuri/uitable v0.0.4
|
||||
github.com/hashicorp/go-getter v1.5.0
|
||||
github.com/hashicorp/go-retryablehttp v0.6.3 // indirect
|
||||
github.com/hashicorp/go-version v1.2.1
|
||||
github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c // indirect
|
||||
github.com/imdario/mergo v0.3.11
|
||||
github.com/pierrec/lz4 v2.3.0+incompatible // indirect
|
||||
github.com/r3labs/diff v1.1.0
|
||||
github.com/spf13/cobra v1.1.1
|
||||
github.com/hashicorp/go-getter v1.7.1
|
||||
github.com/helmfile/chartify v0.14.0
|
||||
github.com/helmfile/vals v0.23.0
|
||||
github.com/imdario/mergo v0.3.15
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.8.2
|
||||
github.com/tatsushid/go-prettytable v0.0.0-20141013043238-ed2d14c29939
|
||||
github.com/urfave/cli v1.22.5
|
||||
github.com/variantdev/chartify v0.6.0
|
||||
github.com/variantdev/dag v0.0.0-20191028002400-bb0b3c785363
|
||||
github.com/variantdev/vals v0.12.0
|
||||
go.uber.org/multierr v1.6.0
|
||||
go.uber.org/zap v1.16.0
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
|
||||
gopkg.in/square/go-jose.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.3.0
|
||||
gotest.tools v2.2.0+incompatible
|
||||
gotest.tools/v3 v3.0.3
|
||||
k8s.io/apimachinery v0.19.3
|
||||
github.com/variantdev/dag v1.1.0
|
||||
go.szostok.io/version v1.1.0
|
||||
go.uber.org/multierr v1.10.0
|
||||
go.uber.org/zap v1.24.0
|
||||
golang.org/x/sync v0.1.0
|
||||
golang.org/x/term v0.6.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
helm.sh/helm/v3 v3.11.2
|
||||
k8s.io/apimachinery v0.26.3
|
||||
)
|
||||
|
||||
replace gopkg.in/yaml.v3 => github.com/colega/go-yaml-yaml v0.0.0-20220720070545-aaba007ebc22
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.104.0 // indirect
|
||||
cloud.google.com/go/compute v1.10.0 // indirect
|
||||
cloud.google.com/go/iam v0.5.0 // indirect
|
||||
cloud.google.com/go/storage v1.27.0 // indirect
|
||||
filippo.io/age v1.0.0-beta7 // indirect
|
||||
github.com/Azure/azure-pipeline-go v0.2.3 // indirect
|
||||
github.com/Azure/azure-sdk-for-go v66.0.0+incompatible // indirect
|
||||
github.com/Azure/azure-storage-blob-go v0.14.0 // indirect
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||
github.com/Azure/go-autorest/autorest v0.11.27 // indirect
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.20 // indirect
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.8 // indirect
|
||||
github.com/Azure/go-autorest/autorest/azure/cli v0.4.2 // indirect
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
||||
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
|
||||
github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
|
||||
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/a8m/envsubst v1.3.0 // indirect
|
||||
github.com/alecthomas/participle v0.4.2-0.20191220090139-9fbceec1d131 // indirect
|
||||
github.com/aws/aws-sdk-go v1.44.122 // indirect
|
||||
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
|
||||
github.com/blang/semver v3.5.1+incompatible // indirect
|
||||
github.com/dimchansky/utfbom v1.1.1 // indirect
|
||||
github.com/fatih/color v1.13.0 // indirect
|
||||
github.com/fujiwara/tfstate-lookup v0.4.4 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/golang/snappy v0.0.3 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.6.0 // indirect
|
||||
github.com/goware/prefixer v0.0.0-20160118172347-395022866408 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
|
||||
github.com/hashicorp/go-safetemp v1.0.0 // indirect
|
||||
github.com/hashicorp/go-slug v0.8.1 // indirect
|
||||
github.com/hashicorp/go-sockaddr v1.0.2 // indirect
|
||||
github.com/hashicorp/go-tfe v1.2.0 // indirect
|
||||
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/hashicorp/jsonapi v0.0.0-20210826224640-ee7dae0fb22d // indirect
|
||||
github.com/hashicorp/vault/api v1.0.4 // indirect
|
||||
github.com/hashicorp/vault/sdk v0.1.14-0.20200215224050-f6547fa8e820 // indirect
|
||||
github.com/huandu/xstrings v1.4.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||
github.com/itchyny/gojq v0.9.0 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/klauspost/compress v1.15.11 // indirect
|
||||
github.com/lestrrat-go/strftime v1.0.1 // indirect
|
||||
github.com/lib/pq v1.10.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-ieproxy v0.0.1 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
|
||||
github.com/mitchellh/go-wordwrap v1.0.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.3 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/otiai10/copy v1.1.1 // indirect
|
||||
github.com/pbnjay/strptime v0.0.0-20140226051138-5c05b0d668c9 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/ryanuber/go-glob v1.0.0 // indirect
|
||||
github.com/sergi/go-diff v1.1.0 // indirect
|
||||
github.com/shopspring/decimal v1.3.1 // indirect
|
||||
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/ulikunitz/xz v0.5.10 // indirect
|
||||
go.mozilla.org/gopgagent v0.0.0-20170926210634-4d7ea76ff71a // indirect
|
||||
go.mozilla.org/sops/v3 v3.7.1 // indirect
|
||||
go.opencensus.io v0.23.0 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
golang.org/x/net v0.8.0 // indirect
|
||||
golang.org/x/oauth2 v0.1.0 // indirect
|
||||
golang.org/x/sys v0.6.0 // indirect
|
||||
golang.org/x/text v0.8.0 // indirect
|
||||
golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
google.golang.org/api v0.100.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71 // indirect
|
||||
google.golang.org/grpc v1.50.1 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
gopkg.in/ini.v1 v1.66.2 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.5.1 // indirect
|
||||
gopkg.in/urfave/cli.v1 v1.20.0 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
|
||||
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/secretmanager v1.6.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.4 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.10.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.0 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v0.7.0 // indirect
|
||||
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de // indirect
|
||||
github.com/aymanbagabas/go-osc52 v1.0.3 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/containerd/containerd v1.6.18 // indirect
|
||||
github.com/docker/cli v20.10.21+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.1+incompatible // indirect
|
||||
github.com/docker/docker v20.10.21+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.7.0 // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-metrics v0.0.1 // indirect
|
||||
github.com/docker/go-units v0.4.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.9.0 // indirect
|
||||
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
|
||||
github.com/go-errors/errors v1.0.1 // indirect
|
||||
github.com/go-logr/logr v1.2.3 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.0 // indirect
|
||||
github.com/go-openapi/swag v0.19.14 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/google/btree v1.0.1 // indirect
|
||||
github.com/google/gnostic v0.5.7-v3refs // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect
|
||||
github.com/gorilla/mux v1.8.0 // indirect
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.1 // indirect
|
||||
github.com/hashicorp/go-version v1.6.0 // indirect
|
||||
github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f // indirect
|
||||
github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.6 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/moby/locker v1.0.1 // indirect
|
||||
github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/muesli/termenv v0.13.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0-rc2 // indirect
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
||||
github.com/pierrec/lz4 v2.3.0+incompatible // indirect
|
||||
github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 // indirect
|
||||
github.com/prometheus/client_golang v1.14.0 // indirect
|
||||
github.com/prometheus/client_model v0.3.0 // indirect
|
||||
github.com/prometheus/common v0.37.0 // indirect
|
||||
github.com/prometheus/procfs v0.8.0 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/xlab/treeprint v1.1.0 // indirect
|
||||
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
|
||||
go.uber.org/goleak v1.1.12 // indirect
|
||||
golang.org/x/crypto v0.7.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/api v0.26.0 // indirect
|
||||
k8s.io/cli-runtime v0.26.0 // indirect
|
||||
k8s.io/client-go v0.26.0 // indirect
|
||||
k8s.io/klog/v2 v2.80.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect
|
||||
k8s.io/utils v0.0.0-20221107191617-1a15be271d1d // indirect
|
||||
oras.land/oras-go v1.2.2 // indirect
|
||||
sigs.k8s.io/kustomize/api v0.12.1 // indirect
|
||||
sigs.k8s.io/kustomize/kyaml v0.13.9 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
|
||||
)
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "=> Building container"
|
||||
docker build \
|
||||
--tag "$IMAGE_NAME" \
|
||||
--file Dockerfile .
|
||||
@@ -1,9 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
go()
|
||||
{
|
||||
docker run --rm -v "$(pwd):/workspace" -w /workspace golang:latest go $@;
|
||||
}
|
||||
|
||||
go get -u github.com/tcnksm/ghr github.com/mitchellh/gox
|
||||
go mod vendor
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "=> Fetch unshallow origin"
|
||||
git fetch --unshallow origin || true
|
||||
|
||||
echo "=> Tag image:"
|
||||
docker tag "${IMAGE_NAME}" "${DOCKER_REPO}:$(git describe --tags --abbrev=0 HEAD^)"
|
||||
docker tag "${IMAGE_NAME}" "${DOCKER_REPO}:$(git describe --tags --abbrev=0 HEAD)"
|
||||
|
||||
echo "=> Push images"
|
||||
docker push "${DOCKER_REPO}"
|
||||
+1507
File diff suppressed because one or more lines are too long
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
@@ -3,803 +3,41 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/roboll/helmfile/pkg/app/version"
|
||||
|
||||
"github.com/roboll/helmfile/pkg/app"
|
||||
"github.com/roboll/helmfile/pkg/helmexec"
|
||||
"github.com/roboll/helmfile/pkg/maputil"
|
||||
"github.com/roboll/helmfile/pkg/state"
|
||||
"github.com/urfave/cli"
|
||||
"go.uber.org/zap"
|
||||
"github.com/helmfile/helmfile/cmd"
|
||||
"github.com/helmfile/helmfile/pkg/app"
|
||||
"github.com/helmfile/helmfile/pkg/config"
|
||||
"github.com/helmfile/helmfile/pkg/errors"
|
||||
)
|
||||
|
||||
var logger *zap.SugaredLogger
|
||||
|
||||
func configureLogging(c *cli.Context) error {
|
||||
// Valid levels:
|
||||
// https://github.com/uber-go/zap/blob/7e7e266a8dbce911a49554b945538c5b950196b8/zapcore/level.go#L126
|
||||
logLevel := c.GlobalString("log-level")
|
||||
if c.GlobalBool("debug") {
|
||||
logLevel = "debug"
|
||||
} else if c.GlobalBool("quiet") {
|
||||
logLevel = "warn"
|
||||
}
|
||||
logger = helmexec.NewLogger(os.Stderr, logLevel)
|
||||
if c.App.Metadata == nil {
|
||||
// Auto-initialised in 1.19.0
|
||||
// https://github.com/urfave/cli/blob/master/CHANGELOG.md#1190---2016-11-19
|
||||
c.App.Metadata = make(map[string]interface{})
|
||||
}
|
||||
c.App.Metadata["logger"] = logger
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
var sig os.Signal
|
||||
sigs := make(chan os.Signal, 1)
|
||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
cliApp := cli.NewApp()
|
||||
cliApp.Name = "helmfile"
|
||||
cliApp.Usage = ""
|
||||
cliApp.Version = version.Version
|
||||
cliApp.EnableBashCompletion = true
|
||||
cliApp.Flags = []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "helm-binary, b",
|
||||
Usage: "path to helm binary",
|
||||
Value: app.DefaultHelmBinary,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "file, f",
|
||||
Usage: "load config from file or directory. defaults to `helmfile.yaml` or `helmfile.d`(means `helmfile.d/*.yaml`) in this preference",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "environment, e",
|
||||
Usage: `specify the environment name. defaults to "default"`,
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "state-values-set",
|
||||
Usage: "set state values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "state-values-file",
|
||||
Usage: "specify state values in a YAML file",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "quiet, q",
|
||||
Usage: "Silence output. Equivalent to log-level warn",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "kube-context",
|
||||
Usage: "Set kubectl context. Uses current context by default",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "debug",
|
||||
Usage: "Enable verbose output for Helm and set log-level to debug, this disables --quiet/-q effect",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "no-color",
|
||||
Usage: "Output without color",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "log-level",
|
||||
Usage: "Set log level, default info",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "namespace, n",
|
||||
Usage: "Set namespace. Uses the namespace set in the context by default, and is available in templates as {{ .Namespace }}",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "selector, l",
|
||||
Usage: `Only run using the releases that match labels. Labels can take the form of foo=bar or foo!=bar.
|
||||
A release must match all labels in a group in order to be used. Multiple groups can be specified at once.
|
||||
--selector tier=frontend,tier!=proxy --selector tier=backend. Will match all frontend, non-proxy releases AND all backend releases.
|
||||
The name of a release can be used as a label. --selector name=myrelease`,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "allow-no-matching-release",
|
||||
Usage: `Do not exit with an error code if the provided selector has no matching releases.`,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "interactive, i",
|
||||
Usage: "Request confirmation before attempting to modify clusters",
|
||||
},
|
||||
}
|
||||
go func() {
|
||||
sig = <-sigs
|
||||
}()
|
||||
|
||||
cliApp.Before = configureLogging
|
||||
cliApp.Commands = []cli.Command{
|
||||
{
|
||||
Name: "deps",
|
||||
Usage: "update charts based on their requirements",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "args",
|
||||
Value: "",
|
||||
Usage: "pass args to helm exec",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "skip-repos",
|
||||
Usage: `skip running "helm repo update" before running "helm dependency build"`,
|
||||
},
|
||||
},
|
||||
Action: action(func(run *app.App, c configImpl) error {
|
||||
return run.Deps(c)
|
||||
}),
|
||||
},
|
||||
{
|
||||
Name: "repos",
|
||||
Usage: "sync repositories from state file (helm repo add && helm repo update)",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "args",
|
||||
Value: "",
|
||||
Usage: "pass args to helm exec",
|
||||
},
|
||||
},
|
||||
Action: action(func(run *app.App, c configImpl) error {
|
||||
return run.Repos(c)
|
||||
}),
|
||||
},
|
||||
{
|
||||
Name: "charts",
|
||||
Usage: "DEPRECATED: sync releases from state file (helm upgrade --install)",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "args",
|
||||
Value: "",
|
||||
Usage: "pass args to helm exec",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "set",
|
||||
Usage: "additional values to be merged into the command",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "values",
|
||||
Usage: "additional value files to be merged into the command",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "concurrency",
|
||||
Value: 0,
|
||||
Usage: "maximum number of concurrent helm processes to run, 0 is unlimited",
|
||||
},
|
||||
},
|
||||
Action: action(func(run *app.App, c configImpl) error {
|
||||
return run.DeprecatedSyncCharts(c)
|
||||
}),
|
||||
},
|
||||
{
|
||||
Name: "diff",
|
||||
Usage: "diff releases from state file against env (helm diff)",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "args",
|
||||
Value: "",
|
||||
Usage: "pass args to helm exec",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "set",
|
||||
Usage: "additional values to be merged into the command",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "values",
|
||||
Usage: "additional value files to be merged into the command",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "skip-deps",
|
||||
Usage: `skip running "helm repo update" and "helm dependency build"`,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "detailed-exitcode",
|
||||
Usage: "return a non-zero exit code when there are changes",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "include-tests",
|
||||
Usage: "enable the diffing of the helm test hooks",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "suppress-secrets",
|
||||
Usage: "suppress secrets in the output. highly recommended to specify on CI/CD use-cases",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "concurrency",
|
||||
Value: 0,
|
||||
Usage: "maximum number of concurrent helm processes to run, 0 is unlimited",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "context",
|
||||
Value: 0,
|
||||
Usage: "output NUM lines of context around changes",
|
||||
},
|
||||
},
|
||||
Action: action(func(run *app.App, c configImpl) error {
|
||||
return run.Diff(c)
|
||||
}),
|
||||
},
|
||||
{
|
||||
Name: "template",
|
||||
Usage: "template releases from state file against env (helm template)",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "args",
|
||||
Value: "",
|
||||
Usage: "pass args to helm template",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "set",
|
||||
Usage: "additional values to be merged into the command",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "values",
|
||||
Usage: "additional value files to be merged into the command",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "output-dir",
|
||||
Usage: "output directory to pass to helm template (helm template --output-dir)",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "output-dir-template",
|
||||
Usage: "go text template for generating the output directory. Default: {{ .OutputDir }}/{{ .State.BaseName }}-{{ .State.AbsPathSHA1 }}-{{ .Release.Name}}",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "concurrency",
|
||||
Value: 0,
|
||||
Usage: "maximum number of concurrent downloads of release charts",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "validate",
|
||||
Usage: "validate your manifests against the Kubernetes cluster you are currently pointing at",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "include-crds",
|
||||
Usage: "include CRDs in the templated output",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "skip-deps",
|
||||
Usage: `skip running "helm repo update" and "helm dependency build"`,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "skip-cleanup",
|
||||
Usage: "Stop cleaning up temporary values generated by helmfile and helm-secrets. Useful for debugging. Don't use in production for security",
|
||||
},
|
||||
},
|
||||
Action: action(func(run *app.App, c configImpl) error {
|
||||
return run.Template(c)
|
||||
}),
|
||||
},
|
||||
{
|
||||
Name: "write-values",
|
||||
Usage: "write values files for releases. Similar to `helmfile template`, write values files instead of manifests.",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringSliceFlag{
|
||||
Name: "set",
|
||||
Usage: "additional values to be merged into the command",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "values",
|
||||
Usage: "additional value files to be merged into the command",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "output-file-template",
|
||||
Usage: "go text template for generating the output file. Default: {{ .State.BaseName }}-{{ .State.AbsPathSHA1 }}/{{ .Release.Name}}.yaml",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "concurrency",
|
||||
Value: 0,
|
||||
Usage: "maximum number of concurrent downloads of release charts",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "skip-deps",
|
||||
Usage: `skip running "helm repo update" and "helm dependency build"`,
|
||||
},
|
||||
},
|
||||
Action: action(func(run *app.App, c configImpl) error {
|
||||
return run.WriteValues(c)
|
||||
}),
|
||||
},
|
||||
{
|
||||
Name: "lint",
|
||||
Usage: "lint charts from state file (helm lint)",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "args",
|
||||
Value: "",
|
||||
Usage: "pass args to helm exec",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "set",
|
||||
Usage: "additional values to be merged into the command",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "values",
|
||||
Usage: "additional value files to be merged into the command",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "concurrency",
|
||||
Value: 0,
|
||||
Usage: "maximum number of concurrent downloads of release charts",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "skip-deps",
|
||||
Usage: `skip running "helm repo update" and "helm dependency build"`,
|
||||
},
|
||||
},
|
||||
Action: action(func(run *app.App, c configImpl) error {
|
||||
return run.Lint(c)
|
||||
}),
|
||||
},
|
||||
{
|
||||
Name: "sync",
|
||||
Usage: "sync all resources from state file (repos, releases and chart deps)",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringSliceFlag{
|
||||
Name: "set",
|
||||
Usage: "additional values to be merged into the command",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "values",
|
||||
Usage: "additional value files to be merged into the command",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "concurrency",
|
||||
Value: 0,
|
||||
Usage: "maximum number of concurrent helm processes to run, 0 is unlimited",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "args",
|
||||
Value: "",
|
||||
Usage: "pass args to helm exec",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "skip-deps",
|
||||
Usage: `skip running "helm repo update" and "helm dependency build"`,
|
||||
},
|
||||
},
|
||||
Action: action(func(run *app.App, c configImpl) error {
|
||||
return run.Sync(c)
|
||||
}),
|
||||
},
|
||||
{
|
||||
Name: "apply",
|
||||
Usage: "apply all resources from state file only when there are changes",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringSliceFlag{
|
||||
Name: "set",
|
||||
Usage: "additional values to be merged into the command",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "values",
|
||||
Usage: "additional value files to be merged into the command",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "concurrency",
|
||||
Value: 0,
|
||||
Usage: "maximum number of concurrent helm processes to run, 0 is unlimited",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "context",
|
||||
Value: 0,
|
||||
Usage: "output NUM lines of context around changes",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "detailed-exitcode",
|
||||
Usage: "return a non-zero exit code 2 instead of 0 when there were changes detected AND the changes are synced successfully",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "args",
|
||||
Value: "",
|
||||
Usage: "pass args to helm exec",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "retain-values-files",
|
||||
Usage: "DEPRECATED: Use skip-cleanup instead",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "skip-cleanup",
|
||||
Usage: "Stop cleaning up temporary values generated by helmfile and helm-secrets. Useful for debugging. Don't use in production for security",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "skip-diff-on-install",
|
||||
Usage: "Skips running helm-diff on releases being newly installed on this apply. Useful when the release manifests are too huge to be reviewed, or it's too time-consuming to diff at all",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "include-tests",
|
||||
Usage: "enable the diffing of the helm test hooks",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "suppress-secrets",
|
||||
Usage: "suppress secrets in the diff output. highly recommended to specify on CI/CD use-cases",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "suppress-diff",
|
||||
Usage: "suppress diff in the output. Usable in new installs",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "skip-deps",
|
||||
Usage: `skip running "helm repo update" and "helm dependency build"`,
|
||||
},
|
||||
},
|
||||
Action: action(func(run *app.App, c configImpl) error {
|
||||
return run.Apply(c)
|
||||
}),
|
||||
},
|
||||
{
|
||||
Name: "status",
|
||||
Usage: "retrieve status of releases in state file",
|
||||
Flags: []cli.Flag{
|
||||
cli.IntFlag{
|
||||
Name: "concurrency",
|
||||
Value: 0,
|
||||
Usage: "maximum number of concurrent helm processes to run, 0 is unlimited",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "args",
|
||||
Value: "",
|
||||
Usage: "pass args to helm exec",
|
||||
},
|
||||
},
|
||||
Action: action(func(run *app.App, c configImpl) error {
|
||||
return run.Status(c)
|
||||
}),
|
||||
},
|
||||
{
|
||||
Name: "delete",
|
||||
Usage: "DEPRECATED: delete releases from state file (helm delete)",
|
||||
Flags: []cli.Flag{
|
||||
cli.IntFlag{
|
||||
Name: "concurrency",
|
||||
Value: 0,
|
||||
Usage: "maximum number of concurrent helm processes to run, 0 is unlimited",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "args",
|
||||
Value: "",
|
||||
Usage: "pass args to helm exec",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "purge",
|
||||
Usage: "purge releases i.e. free release names and histories",
|
||||
},
|
||||
},
|
||||
Action: action(func(run *app.App, c configImpl) error {
|
||||
return run.Delete(c)
|
||||
}),
|
||||
},
|
||||
{
|
||||
Name: "destroy",
|
||||
Usage: "deletes and then purges releases",
|
||||
Flags: []cli.Flag{
|
||||
cli.IntFlag{
|
||||
Name: "concurrency",
|
||||
Value: 0,
|
||||
Usage: "maximum number of concurrent helm processes to run, 0 is unlimited",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "args",
|
||||
Value: "",
|
||||
Usage: "pass args to helm exec",
|
||||
},
|
||||
},
|
||||
Action: action(func(run *app.App, c configImpl) error {
|
||||
return run.Destroy(c)
|
||||
}),
|
||||
},
|
||||
{
|
||||
Name: "test",
|
||||
Usage: "test releases from state file (helm test)",
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "cleanup",
|
||||
Usage: "delete test pods upon completion",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "logs",
|
||||
Usage: "Dump the logs from test pods (this runs after all tests are complete, but before any cleanup)",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "args",
|
||||
Value: "",
|
||||
Usage: "pass additional args to helm exec",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "timeout",
|
||||
Value: 300,
|
||||
Usage: "maximum time for tests to run before being considered failed",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "concurrency",
|
||||
Value: 0,
|
||||
Usage: "maximum number of concurrent helm processes to run, 0 is unlimited",
|
||||
},
|
||||
},
|
||||
Action: action(func(run *app.App, c configImpl) error {
|
||||
return run.Test(c)
|
||||
}),
|
||||
},
|
||||
{
|
||||
Name: "build",
|
||||
Usage: "output compiled helmfile state(s) as YAML",
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "embed-values",
|
||||
Usage: "Read all the values files for every release and embed into the output helmfile.yaml",
|
||||
},
|
||||
},
|
||||
Action: action(func(run *app.App, c configImpl) error {
|
||||
return run.PrintState(c)
|
||||
}),
|
||||
},
|
||||
{
|
||||
Name: "list",
|
||||
Usage: "list releases defined in state file",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "output",
|
||||
Value: "",
|
||||
Usage: "output releases list as a json string",
|
||||
},
|
||||
},
|
||||
Action: action(func(run *app.App, c configImpl) error {
|
||||
return run.ListReleases(c)
|
||||
}),
|
||||
},
|
||||
{
|
||||
Name: "version",
|
||||
Usage: "Show the version for Helmfile.",
|
||||
ArgsUsage: "[command]",
|
||||
Action: func(c *cli.Context) error {
|
||||
cli.ShowVersion(c)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}
|
||||
globalConfig := new(config.GlobalOptions)
|
||||
rootCmd, err := cmd.NewRootCmd(globalConfig)
|
||||
errors.HandleExitCoder(err)
|
||||
|
||||
err := cliApp.Run(os.Args)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||
os.Exit(3)
|
||||
}
|
||||
}
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
if sig != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
app.CleanWaitGroup.Wait()
|
||||
|
||||
type configImpl struct {
|
||||
c *cli.Context
|
||||
|
||||
set map[string]interface{}
|
||||
}
|
||||
|
||||
func NewUrfaveCliConfigImpl(c *cli.Context) (configImpl, error) {
|
||||
if c.NArg() > 0 {
|
||||
cli.ShowAppHelp(c)
|
||||
return configImpl{}, fmt.Errorf("err: extraneous arguments: %s", strings.Join(c.Args(), ", "))
|
||||
}
|
||||
|
||||
conf := configImpl{
|
||||
c: c,
|
||||
}
|
||||
|
||||
optsSet := c.GlobalStringSlice("state-values-set")
|
||||
if len(optsSet) > 0 {
|
||||
set := map[string]interface{}{}
|
||||
for i := range optsSet {
|
||||
ops := strings.Split(optsSet[i], ",")
|
||||
for j := range ops {
|
||||
op := strings.SplitN(ops[j], "=", 2)
|
||||
k := maputil.ParseKey(op[0])
|
||||
v := op[1]
|
||||
|
||||
maputil.Set(set, k, v)
|
||||
// See http://tldp.org/LDP/abs/html/exitcodes.html
|
||||
switch sig {
|
||||
case syscall.SIGINT:
|
||||
os.Exit(130)
|
||||
case syscall.SIGTERM:
|
||||
os.Exit(143)
|
||||
}
|
||||
}
|
||||
conf.set = set
|
||||
}
|
||||
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
func (c configImpl) Set() []string {
|
||||
return c.c.StringSlice("set")
|
||||
}
|
||||
|
||||
func (c configImpl) SkipRepos() bool {
|
||||
return c.c.Bool("skip-repos")
|
||||
}
|
||||
|
||||
func (c configImpl) Values() []string {
|
||||
return c.c.StringSlice("values")
|
||||
}
|
||||
|
||||
func (c configImpl) Args() string {
|
||||
args := c.c.String("args")
|
||||
enableHelmDebug := c.c.GlobalBool("debug")
|
||||
|
||||
if enableHelmDebug {
|
||||
args = fmt.Sprintf("%s %s", args, "--debug")
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
func (c configImpl) OutputDir() string {
|
||||
return c.c.String("output-dir")
|
||||
}
|
||||
|
||||
func (c configImpl) OutputDirTemplate() string {
|
||||
return c.c.String("output-dir-template")
|
||||
}
|
||||
|
||||
func (c configImpl) OutputFileTemplate() string {
|
||||
return c.c.String("output-file-template")
|
||||
}
|
||||
|
||||
func (c configImpl) Validate() bool {
|
||||
return c.c.Bool("validate")
|
||||
}
|
||||
|
||||
func (c configImpl) Concurrency() int {
|
||||
return c.c.Int("concurrency")
|
||||
}
|
||||
|
||||
func (c configImpl) HasCommandName(name string) bool {
|
||||
return c.c.Command.HasName(name)
|
||||
}
|
||||
|
||||
// DiffConfig
|
||||
|
||||
func (c configImpl) SkipDeps() bool {
|
||||
return c.c.Bool("skip-deps")
|
||||
}
|
||||
|
||||
func (c configImpl) DetailedExitcode() bool {
|
||||
return c.c.Bool("detailed-exitcode")
|
||||
}
|
||||
|
||||
func (c configImpl) RetainValuesFiles() bool {
|
||||
return c.c.Bool("retain-values-files")
|
||||
}
|
||||
|
||||
func (c configImpl) IncludeTests() bool {
|
||||
return c.c.Bool("include-tests")
|
||||
}
|
||||
|
||||
func (c configImpl) SuppressSecrets() bool {
|
||||
return c.c.Bool("suppress-secrets")
|
||||
}
|
||||
|
||||
func (c configImpl) SuppressDiff() bool {
|
||||
return c.c.Bool("suppress-diff")
|
||||
}
|
||||
|
||||
// DeleteConfig
|
||||
|
||||
func (c configImpl) Purge() bool {
|
||||
return c.c.Bool("purge")
|
||||
}
|
||||
|
||||
// TestConfig
|
||||
|
||||
func (c configImpl) Cleanup() bool {
|
||||
return c.c.Bool("cleanup")
|
||||
}
|
||||
|
||||
func (c configImpl) Logs() bool {
|
||||
return c.c.Bool("logs")
|
||||
}
|
||||
|
||||
func (c configImpl) Timeout() int {
|
||||
if !c.c.IsSet("timeout") {
|
||||
return state.EmptyTimeout
|
||||
}
|
||||
return c.c.Int("timeout")
|
||||
}
|
||||
|
||||
// ListConfig
|
||||
|
||||
func (c configImpl) Output() string {
|
||||
return c.c.String("output")
|
||||
}
|
||||
|
||||
// GlobalConfig
|
||||
|
||||
func (c configImpl) HelmBinary() string {
|
||||
return c.c.GlobalString("helm-binary")
|
||||
}
|
||||
|
||||
func (c configImpl) KubeContext() string {
|
||||
return c.c.GlobalString("kube-context")
|
||||
}
|
||||
|
||||
func (c configImpl) Namespace() string {
|
||||
return c.c.GlobalString("namespace")
|
||||
}
|
||||
|
||||
func (c configImpl) FileOrDir() string {
|
||||
return c.c.GlobalString("file")
|
||||
}
|
||||
|
||||
func (c configImpl) Selectors() []string {
|
||||
return c.c.GlobalStringSlice("selector")
|
||||
}
|
||||
|
||||
func (c configImpl) StateValuesSet() map[string]interface{} {
|
||||
return c.set
|
||||
}
|
||||
|
||||
func (c configImpl) StateValuesFiles() []string {
|
||||
return c.c.GlobalStringSlice("state-values-file")
|
||||
}
|
||||
|
||||
func (c configImpl) Interactive() bool {
|
||||
return c.c.GlobalBool("interactive")
|
||||
}
|
||||
|
||||
func (c configImpl) NoColor() bool {
|
||||
return c.c.GlobalBool("no-color")
|
||||
}
|
||||
|
||||
func (c configImpl) Context() int {
|
||||
return c.c.Int("context")
|
||||
}
|
||||
|
||||
func (c configImpl) SkipCleanup() bool {
|
||||
return c.c.Bool("skip-cleanup")
|
||||
}
|
||||
|
||||
func (c configImpl) SkipDiffOnInstall() bool {
|
||||
return c.c.Bool("skip-diff-on-install")
|
||||
}
|
||||
|
||||
func (c configImpl) EmbedValues() bool {
|
||||
return c.c.Bool("embed-values")
|
||||
}
|
||||
|
||||
func (c configImpl) IncludeCRDs() bool {
|
||||
return c.c.Bool("include-crds")
|
||||
}
|
||||
|
||||
func (c configImpl) Logger() *zap.SugaredLogger {
|
||||
return c.c.App.Metadata["logger"].(*zap.SugaredLogger)
|
||||
}
|
||||
|
||||
func (c configImpl) Env() string {
|
||||
env := c.c.GlobalString("environment")
|
||||
if env == "" {
|
||||
env = os.Getenv("HELMFILE_ENVIRONMENT")
|
||||
if env == "" {
|
||||
env = state.DefaultEnv
|
||||
}
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
func action(do func(*app.App, configImpl) error) func(*cli.Context) error {
|
||||
return func(implCtx *cli.Context) error {
|
||||
conf, err := NewUrfaveCliConfigImpl(implCtx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a := app.New(conf)
|
||||
|
||||
if err := do(a, conf); err != nil {
|
||||
return toCliError(implCtx, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
errors.HandleExitCoder(err)
|
||||
}
|
||||
}
|
||||
|
||||
func toCliError(c *cli.Context, err error) error {
|
||||
if err != nil {
|
||||
switch e := err.(type) {
|
||||
case *app.NoMatchingHelmfileError:
|
||||
noMatchingExitCode := 3
|
||||
if c.GlobalBool("allow-no-matching-release") {
|
||||
noMatchingExitCode = 0
|
||||
}
|
||||
return cli.NewExitError(e.Error(), noMatchingExitCode)
|
||||
case *app.Error:
|
||||
return cli.NewExitError(e.Error(), e.Code())
|
||||
default:
|
||||
panic(fmt.Errorf("BUG: please file an github issue for this unhandled error: %T: %v", e, e))
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
# Test this locally with:
|
||||
# $ docker run --rm -it -v $(pwd):$(pwd) --workdir $(pwd) python:3 bash
|
||||
# # pip install -r docs/requirements.txt
|
||||
# # mkdocs serve
|
||||
# # mkdocs build
|
||||
# $ ls -lah site/
|
||||
site_name: helmfile
|
||||
site_author: Helmfile Authors
|
||||
|
||||
repo_name: helmfile/helmfile/
|
||||
repo_url: https://github.com/helmfile/helmfile/
|
||||
edit_uri: ''
|
||||
|
||||
docs_dir: docs
|
||||
|
||||
nav:
|
||||
- Home: index.md
|
||||
- Getting Started:
|
||||
- Paths Overview: paths.md
|
||||
- Templating Funs: templating_funcs.md
|
||||
- Built-in Objects: builtin-objects.md
|
||||
- Advanced Features:
|
||||
- Best Practices Guide: writing-helmfile.md
|
||||
- Advanced Features: advanced-features.md
|
||||
- Secrets: remote-secrets.md
|
||||
- Shared Configuration Across Teams: shared-configuration-across-teams.md
|
||||
- About:
|
||||
- Users: users.md
|
||||
- License: license.md
|
||||
- Contributing: contributing.md
|
||||
|
||||
theme:
|
||||
name: readthedocs
|
||||
features:
|
||||
- navigation.tabs
|
||||
collapse_navigation: false
|
||||
hljs_languages:
|
||||
- yaml
|
||||
- dockerfile
|
||||
|
||||
markdown_extensions:
|
||||
# meta, toc, table and fenced_code extensions are included by default
|
||||
- extra
|
||||
- admonition
|
||||
- smarty
|
||||
- sane_lists
|
||||
- nl2br
|
||||
- toc:
|
||||
permalink: true
|
||||
|
||||
plugins:
|
||||
- search
|
||||
- git-revision-date-localized:
|
||||
type: date
|
||||
fallback_to_build_date: true
|
||||
+975
-439
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,458 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/helmfile/vals"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/exectest"
|
||||
"github.com/helmfile/helmfile/pkg/helmexec"
|
||||
"github.com/helmfile/helmfile/pkg/testhelper"
|
||||
)
|
||||
|
||||
func TestApply_hooks(t *testing.T) {
|
||||
type fields struct {
|
||||
skipNeeds bool
|
||||
includeNeeds bool
|
||||
includeTransitiveNeeds bool
|
||||
}
|
||||
|
||||
type testcase struct {
|
||||
fields fields
|
||||
ns string
|
||||
concurrency int
|
||||
skipDiffOnInstall bool
|
||||
error string
|
||||
files map[string]string
|
||||
selectors []string
|
||||
lists map[exectest.ListKey]string
|
||||
diffs map[exectest.DiffKey]error
|
||||
upgraded []exectest.Release
|
||||
deleted []exectest.Release
|
||||
log string
|
||||
logLevel string
|
||||
}
|
||||
|
||||
check := func(t *testing.T, tc testcase) {
|
||||
t.Helper()
|
||||
|
||||
wantUpgrades := tc.upgraded
|
||||
wantDeletes := tc.deleted
|
||||
|
||||
var helm = &exectest.Helm{
|
||||
FailOnUnexpectedList: true,
|
||||
FailOnUnexpectedDiff: true,
|
||||
Lists: tc.lists,
|
||||
Diffs: tc.diffs,
|
||||
DiffMutex: &sync.Mutex{},
|
||||
ChartsMutex: &sync.Mutex{},
|
||||
ReleasesMutex: &sync.Mutex{},
|
||||
Helm3: true,
|
||||
}
|
||||
|
||||
bs := runWithLogCapture(t, tc.logLevel, func(t *testing.T, logger *zap.SugaredLogger) {
|
||||
t.Helper()
|
||||
|
||||
valsRuntime, err := vals.New(vals.Options{CacheSize: 32})
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error creating vals runtime: %v", err)
|
||||
}
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
helms: map[helmKey]helmexec.Interface{
|
||||
createHelmKey("helm", "default"): helm,
|
||||
},
|
||||
valsRuntime: valsRuntime,
|
||||
}, tc.files)
|
||||
|
||||
if tc.ns != "" {
|
||||
app.Namespace = tc.ns
|
||||
}
|
||||
|
||||
if tc.selectors != nil {
|
||||
app.Selectors = tc.selectors
|
||||
}
|
||||
|
||||
syncErr := app.Apply(applyConfig{
|
||||
// if we check log output, concurrency must be 1. otherwise the test becomes non-deterministic.
|
||||
concurrency: tc.concurrency,
|
||||
logger: logger,
|
||||
skipDiffOnInstall: tc.skipDiffOnInstall,
|
||||
skipNeeds: tc.fields.skipNeeds,
|
||||
includeNeeds: tc.fields.includeNeeds,
|
||||
includeTransitiveNeeds: tc.fields.includeTransitiveNeeds,
|
||||
})
|
||||
|
||||
var gotErr string
|
||||
if syncErr != nil {
|
||||
gotErr = syncErr.Error()
|
||||
}
|
||||
|
||||
if d := cmp.Diff(tc.error, gotErr); d != "" {
|
||||
t.Fatalf("unexpected error: want (-), got (+): %s", d)
|
||||
}
|
||||
|
||||
if len(wantUpgrades) > len(helm.Releases) {
|
||||
t.Fatalf("insufficient number of upgrades: got %d, want %d", len(helm.Releases), len(wantUpgrades))
|
||||
}
|
||||
|
||||
for relIdx := range wantUpgrades {
|
||||
if wantUpgrades[relIdx].Name != helm.Releases[relIdx].Name {
|
||||
t.Errorf("releases[%d].name: got %q, want %q", relIdx, helm.Releases[relIdx].Name, wantUpgrades[relIdx].Name)
|
||||
}
|
||||
for flagIdx := range wantUpgrades[relIdx].Flags {
|
||||
if wantUpgrades[relIdx].Flags[flagIdx] != helm.Releases[relIdx].Flags[flagIdx] {
|
||||
t.Errorf("releases[%d].flags[%d]: got %v, want %v", relIdx, flagIdx, helm.Releases[relIdx].Flags[flagIdx], wantUpgrades[relIdx].Flags[flagIdx])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(wantDeletes) > len(helm.Deleted) {
|
||||
t.Fatalf("insufficient number of deletes: got %d, want %d", len(helm.Deleted), len(wantDeletes))
|
||||
}
|
||||
|
||||
for relIdx := range wantDeletes {
|
||||
if wantDeletes[relIdx].Name != helm.Deleted[relIdx].Name {
|
||||
t.Errorf("releases[%d].name: got %q, want %q", relIdx, helm.Deleted[relIdx].Name, wantDeletes[relIdx].Name)
|
||||
}
|
||||
for flagIdx := range wantDeletes[relIdx].Flags {
|
||||
if wantDeletes[relIdx].Flags[flagIdx] != helm.Deleted[relIdx].Flags[flagIdx] {
|
||||
t.Errorf("releaes[%d].flags[%d]: got %v, want %v", relIdx, flagIdx, helm.Deleted[relIdx].Flags[flagIdx], wantDeletes[relIdx].Flags[flagIdx])
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if tc.log != "" {
|
||||
actual := bs.String()
|
||||
|
||||
diff, exists := testhelper.Diff(tc.log, actual, 3)
|
||||
if exists {
|
||||
t.Errorf("unexpected log:\nDIFF\n%s\nEOD", diff)
|
||||
}
|
||||
} else {
|
||||
assertLogEqualsToSnapshot(t, bs.String())
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("apply release with preapply hook", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
releases:
|
||||
- name: foo
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
hooks:
|
||||
- events: ["preapply"]
|
||||
command: echo
|
||||
showlogs: true
|
||||
args: ["foo"]
|
||||
`,
|
||||
},
|
||||
selectors: []string{"name=foo"},
|
||||
upgraded: []exectest.Release{
|
||||
{Name: "foo"},
|
||||
},
|
||||
diffs: map[exectest.DiffKey]error{
|
||||
{Name: "foo", Chart: "incubator/raw", Flags: "--kube-contextdefault--namespacedefault--detailed-exitcode--reset-values"}: helmexec.ExitError{Code: 2},
|
||||
},
|
||||
error: "",
|
||||
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
|
||||
concurrency: 1,
|
||||
logLevel: "info",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("apply release with preapply hook", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
releases:
|
||||
- name: foo
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
hooks:
|
||||
- events: ["prepare", "preapply", "presync"]
|
||||
command: echo
|
||||
showlogs: true
|
||||
args: ["foo"]
|
||||
`,
|
||||
},
|
||||
selectors: []string{"name=foo"},
|
||||
upgraded: []exectest.Release{
|
||||
{Name: "foo"},
|
||||
},
|
||||
diffs: map[exectest.DiffKey]error{
|
||||
{Name: "foo", Chart: "incubator/raw", Flags: "--kube-contextdefault--namespacedefault--detailed-exitcode--reset-values"}: helmexec.ExitError{Code: 2},
|
||||
},
|
||||
error: "",
|
||||
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
|
||||
concurrency: 1,
|
||||
logLevel: "info",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("apply release with preapply hook", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
releases:
|
||||
- name: foo
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
hooks:
|
||||
- events: ["presync"]
|
||||
command: echo
|
||||
showlogs: true
|
||||
args: ["foo"]
|
||||
`,
|
||||
},
|
||||
selectors: []string{"name=foo"},
|
||||
upgraded: []exectest.Release{
|
||||
{Name: "foo"},
|
||||
},
|
||||
diffs: map[exectest.DiffKey]error{
|
||||
{Name: "foo", Chart: "incubator/raw", Flags: "--kube-contextdefault--namespacedefault--detailed-exitcode--reset-values"}: helmexec.ExitError{Code: 2},
|
||||
},
|
||||
error: "",
|
||||
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
|
||||
concurrency: 1,
|
||||
logLevel: "info",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("hooks for no-diff release", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
releases:
|
||||
- name: foo
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
hooks:
|
||||
# only prepare and cleanup are run
|
||||
- events: ["prepare", "preapply", "presync", "cleanup"]
|
||||
command: echo
|
||||
showlogs: true
|
||||
args: ["foo"]
|
||||
`,
|
||||
},
|
||||
selectors: []string{"app=test"},
|
||||
diffs: map[exectest.DiffKey]error{
|
||||
{Name: "foo", Chart: "incubator/raw", Flags: "--kube-contextdefault--namespacedefault--detailed-exitcode--reset-values"}: nil,
|
||||
},
|
||||
error: "",
|
||||
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
|
||||
concurrency: 1,
|
||||
logLevel: "info",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("hooks are run on enabled release", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
values:
|
||||
- bar:
|
||||
enabled: true
|
||||
|
||||
releases:
|
||||
- name: foo
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
hooks:
|
||||
- events: ["prepare", "preapply", "presync"]
|
||||
command: echo
|
||||
showlogs: true
|
||||
args: ["foo"]
|
||||
- name: bar
|
||||
condition: bar.enabled
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
hooks:
|
||||
- events: ["prepare", "preapply", "presync"]
|
||||
command: echo
|
||||
showlogs: true
|
||||
args: ["bar"]
|
||||
`,
|
||||
},
|
||||
selectors: []string{"app=test"},
|
||||
upgraded: []exectest.Release{
|
||||
{Name: "foo"},
|
||||
{Name: "bar"},
|
||||
},
|
||||
diffs: map[exectest.DiffKey]error{
|
||||
{Name: "foo", Chart: "incubator/raw", Flags: "--kube-contextdefault--namespacedefault--detailed-exitcode--reset-values"}: helmexec.ExitError{Code: 2},
|
||||
{Name: "bar", Chart: "incubator/raw", Flags: "--kube-contextdefault--namespacedefault--detailed-exitcode--reset-values"}: helmexec.ExitError{Code: 2},
|
||||
},
|
||||
error: "",
|
||||
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
|
||||
concurrency: 1,
|
||||
logLevel: "info",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("hooks are not run on disabled release", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
values:
|
||||
- bar:
|
||||
enabled: false
|
||||
|
||||
releases:
|
||||
- name: foo
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
hooks:
|
||||
- events: ["prepare", "preapply", "presync"]
|
||||
command: echo
|
||||
showlogs: true
|
||||
args: ["foo"]
|
||||
- name: bar
|
||||
condition: bar.enabled
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
hooks:
|
||||
- events: ["prepare", "preapply", "presync"]
|
||||
command: echo
|
||||
showlogs: true
|
||||
args: ["bar"]
|
||||
`,
|
||||
},
|
||||
selectors: []string{"app=test"},
|
||||
upgraded: []exectest.Release{
|
||||
{Name: "foo"},
|
||||
},
|
||||
diffs: map[exectest.DiffKey]error{
|
||||
{Name: "foo", Chart: "incubator/raw", Flags: "--kube-contextdefault--namespacedefault--detailed-exitcode--reset-values"}: helmexec.ExitError{Code: 2},
|
||||
},
|
||||
error: "",
|
||||
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
|
||||
concurrency: 1,
|
||||
logLevel: "info",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("hooks are run on to-be-uninstalled release", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
releases:
|
||||
- name: foo
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
hooks:
|
||||
- events: ["prepare", "preapply", "presync"]
|
||||
command: echo
|
||||
showlogs: true
|
||||
args: ["foo"]
|
||||
- name: bar
|
||||
installed: false
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
hooks:
|
||||
- events: ["prepare", "preapply", "presync"]
|
||||
command: echo
|
||||
showlogs: true
|
||||
args: ["bar"]
|
||||
`,
|
||||
},
|
||||
selectors: []string{"app=test"},
|
||||
lists: map[exectest.ListKey]string{
|
||||
{Filter: "^foo$", Flags: listFlags("default", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
foo 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^bar$", Flags: listFlags("default", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
bar 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
|
||||
`,
|
||||
},
|
||||
upgraded: []exectest.Release{
|
||||
{Name: "foo"},
|
||||
},
|
||||
diffs: map[exectest.DiffKey]error{
|
||||
{Name: "foo", Chart: "incubator/raw", Flags: "--kube-contextdefault--namespacedefault--detailed-exitcode--reset-values"}: helmexec.ExitError{Code: 2},
|
||||
},
|
||||
error: "",
|
||||
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
|
||||
concurrency: 1,
|
||||
logLevel: "info",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("hooks are not run on alreadyd uninstalled release", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
releases:
|
||||
- name: foo
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
hooks:
|
||||
- events: ["prepare", "preapply", "presync"]
|
||||
command: echo
|
||||
showlogs: true
|
||||
args: ["foo"]
|
||||
- name: bar
|
||||
installed: false
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
hooks:
|
||||
- events: ["prepare", "preapply", "presync"]
|
||||
command: echo
|
||||
showlogs: true
|
||||
args: ["bar"]
|
||||
`,
|
||||
},
|
||||
selectors: []string{"app=test"},
|
||||
lists: map[exectest.ListKey]string{
|
||||
{Filter: "^foo$", Flags: listFlags("default", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
foo 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^bar$", Flags: listFlags("default", "default")}: ``,
|
||||
},
|
||||
upgraded: []exectest.Release{
|
||||
{Name: "foo"},
|
||||
},
|
||||
diffs: map[exectest.DiffKey]error{
|
||||
{Name: "foo", Chart: "incubator/raw", Flags: "--kube-contextdefault--namespacedefault--detailed-exitcode--reset-values"}: helmexec.ExitError{Code: 2},
|
||||
},
|
||||
error: "",
|
||||
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
|
||||
concurrency: 1,
|
||||
logLevel: "info",
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,568 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/helmfile/vals"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/exectest"
|
||||
"github.com/helmfile/helmfile/pkg/filesystem"
|
||||
"github.com/helmfile/helmfile/pkg/helmexec"
|
||||
"github.com/helmfile/helmfile/pkg/testhelper"
|
||||
)
|
||||
|
||||
func TestApply_3(t *testing.T) {
|
||||
type fields struct {
|
||||
skipNeeds bool
|
||||
includeNeeds bool
|
||||
includeTransitiveNeeds bool
|
||||
}
|
||||
|
||||
type testcase struct {
|
||||
fields fields
|
||||
ns string
|
||||
concurrency int
|
||||
skipDiffOnInstall bool
|
||||
error string
|
||||
files map[string]string
|
||||
selectors []string
|
||||
lists map[exectest.ListKey]string
|
||||
diffs map[exectest.DiffKey]error
|
||||
upgraded []exectest.Release
|
||||
deleted []exectest.Release
|
||||
log string
|
||||
}
|
||||
|
||||
check := func(t *testing.T, tc testcase) {
|
||||
t.Helper()
|
||||
|
||||
wantUpgrades := tc.upgraded
|
||||
wantDeletes := tc.deleted
|
||||
|
||||
var helm = &exectest.Helm{
|
||||
FailOnUnexpectedList: true,
|
||||
FailOnUnexpectedDiff: true,
|
||||
Lists: tc.lists,
|
||||
Diffs: tc.diffs,
|
||||
DiffMutex: &sync.Mutex{},
|
||||
ChartsMutex: &sync.Mutex{},
|
||||
ReleasesMutex: &sync.Mutex{},
|
||||
Helm3: true,
|
||||
}
|
||||
|
||||
bs := runWithLogCapture(t, "debug", func(t *testing.T, logger *zap.SugaredLogger) {
|
||||
t.Helper()
|
||||
|
||||
valsRuntime, err := vals.New(vals.Options{CacheSize: 32})
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error creating vals runtime: %v", err)
|
||||
}
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: filesystem.DefaultFileSystem(),
|
||||
OverrideKubeContext: "",
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
helms: map[helmKey]helmexec.Interface{
|
||||
createHelmKey("helm", ""): helm,
|
||||
},
|
||||
valsRuntime: valsRuntime,
|
||||
}, tc.files)
|
||||
|
||||
if tc.ns != "" {
|
||||
app.Namespace = tc.ns
|
||||
}
|
||||
|
||||
if tc.selectors != nil {
|
||||
app.Selectors = tc.selectors
|
||||
}
|
||||
|
||||
syncErr := app.Apply(applyConfig{
|
||||
// if we check log output, concurrency must be 1. otherwise the test becomes non-deterministic.
|
||||
concurrency: tc.concurrency,
|
||||
logger: logger,
|
||||
skipDiffOnInstall: tc.skipDiffOnInstall,
|
||||
skipNeeds: tc.fields.skipNeeds,
|
||||
includeNeeds: tc.fields.includeNeeds,
|
||||
includeTransitiveNeeds: tc.fields.includeTransitiveNeeds,
|
||||
})
|
||||
|
||||
var gotErr string
|
||||
if syncErr != nil {
|
||||
gotErr = syncErr.Error()
|
||||
}
|
||||
|
||||
if d := cmp.Diff(tc.error, gotErr); d != "" {
|
||||
t.Fatalf("unexpected error: want (-), got (+): %s", d)
|
||||
}
|
||||
|
||||
if len(wantUpgrades) > len(helm.Releases) {
|
||||
t.Fatalf("insufficient number of upgrades: got %d, want %d", len(helm.Releases), len(wantUpgrades))
|
||||
}
|
||||
|
||||
for relIdx := range wantUpgrades {
|
||||
if wantUpgrades[relIdx].Name != helm.Releases[relIdx].Name {
|
||||
t.Errorf("releases[%d].name: got %q, want %q", relIdx, helm.Releases[relIdx].Name, wantUpgrades[relIdx].Name)
|
||||
}
|
||||
for flagIdx := range wantUpgrades[relIdx].Flags {
|
||||
if wantUpgrades[relIdx].Flags[flagIdx] != helm.Releases[relIdx].Flags[flagIdx] {
|
||||
t.Errorf("releaes[%d].flags[%d]: got %v, want %v", relIdx, flagIdx, helm.Releases[relIdx].Flags[flagIdx], wantUpgrades[relIdx].Flags[flagIdx])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(wantDeletes) > len(helm.Deleted) {
|
||||
t.Fatalf("insufficient number of deletes: got %d, want %d", len(helm.Deleted), len(wantDeletes))
|
||||
}
|
||||
|
||||
for relIdx := range wantDeletes {
|
||||
if wantDeletes[relIdx].Name != helm.Deleted[relIdx].Name {
|
||||
t.Errorf("releases[%d].name: got %q, want %q", relIdx, helm.Deleted[relIdx].Name, wantDeletes[relIdx].Name)
|
||||
}
|
||||
for flagIdx := range wantDeletes[relIdx].Flags {
|
||||
if wantDeletes[relIdx].Flags[flagIdx] != helm.Deleted[relIdx].Flags[flagIdx] {
|
||||
t.Errorf("releaes[%d].flags[%d]: got %v, want %v", relIdx, flagIdx, helm.Deleted[relIdx].Flags[flagIdx], wantDeletes[relIdx].Flags[flagIdx])
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if tc.log != "" {
|
||||
actual := bs.String()
|
||||
|
||||
diff, exists := testhelper.Diff(tc.log, actual, 3)
|
||||
if exists {
|
||||
t.Errorf("unexpected log:\nDIFF\n%s\nEOD", diff)
|
||||
}
|
||||
} else {
|
||||
assertLogEqualsToSnapshot(t, bs.String())
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("skip-needs=true", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: true,
|
||||
},
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
{{ $mark := "a" }}
|
||||
|
||||
releases:
|
||||
- name: kubernetes-external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: kube-system
|
||||
|
||||
- name: external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- kube-system/kubernetes-external-secrets
|
||||
|
||||
- name: my-release
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- default/external-secrets
|
||||
`,
|
||||
},
|
||||
selectors: []string{"app=test"},
|
||||
upgraded: []exectest.Release{
|
||||
{Name: "external-secrets", Flags: []string{"--namespace", "default"}},
|
||||
{Name: "my-release", Flags: []string{"--namespace", "default"}},
|
||||
},
|
||||
diffs: map[exectest.DiffKey]error{
|
||||
{Name: "external-secrets", Chart: "incubator/raw", Flags: "--namespacedefault--detailed-exitcode--reset-values"}: helmexec.ExitError{Code: 2},
|
||||
{Name: "my-release", Chart: "incubator/raw", Flags: "--namespacedefault--detailed-exitcode--reset-values"}: helmexec.ExitError{Code: 2},
|
||||
},
|
||||
lists: map[exectest.ListKey]string{
|
||||
{Filter: "^external-secrets$", Flags: listFlags("default", "")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
external-secrets 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^my-release$", Flags: listFlags("default", "")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
my-release 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
|
||||
`,
|
||||
},
|
||||
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
|
||||
concurrency: 1,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("skip-needs=true with no diff on a release", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: true,
|
||||
},
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
{{ $mark := "a" }}
|
||||
|
||||
releases:
|
||||
- name: kubernetes-external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: kube-system
|
||||
|
||||
- name: external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- kube-system/kubernetes-external-secrets
|
||||
|
||||
- name: my-release
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- default/external-secrets
|
||||
`,
|
||||
},
|
||||
selectors: []string{"app=test"},
|
||||
upgraded: []exectest.Release{
|
||||
{Name: "external-secrets", Flags: []string{"--namespace", "default"}},
|
||||
},
|
||||
lists: map[exectest.ListKey]string{
|
||||
{Filter: "^external-secrets$", Flags: listFlags("default", "")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
^external-secrets$ 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
|
||||
`,
|
||||
},
|
||||
diffs: map[exectest.DiffKey]error{
|
||||
{Name: "external-secrets", Chart: "incubator/raw", Flags: "--namespacedefault--detailed-exitcode--reset-values"}: helmexec.ExitError{Code: 2},
|
||||
{Name: "my-release", Chart: "incubator/raw", Flags: "--namespacedefault--detailed-exitcode--reset-values"}: nil,
|
||||
},
|
||||
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
|
||||
concurrency: 1,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("skip-needs=false include-needs=true", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: false,
|
||||
includeNeeds: true,
|
||||
},
|
||||
error: ``,
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
{{ $mark := "a" }}
|
||||
|
||||
releases:
|
||||
- name: kubernetes-external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: kube-system
|
||||
|
||||
- name: external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- kube-system/kubernetes-external-secrets
|
||||
|
||||
- name: my-release
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- default/external-secrets
|
||||
`,
|
||||
},
|
||||
selectors: []string{"app=test"},
|
||||
upgraded: []exectest.Release{},
|
||||
diffs: map[exectest.DiffKey]error{
|
||||
{Name: "kubernetes-external-secrets", Chart: "incubator/raw", Flags: "--namespacekube-system--detailed-exitcode--reset-values"}: helmexec.ExitError{Code: 2},
|
||||
{Name: "external-secrets", Chart: "incubator/raw", Flags: "--namespacedefault--detailed-exitcode--reset-values"}: helmexec.ExitError{Code: 2},
|
||||
{Name: "my-release", Chart: "incubator/raw", Flags: "--namespacedefault--detailed-exitcode--reset-values"}: helmexec.ExitError{Code: 2},
|
||||
},
|
||||
lists: map[exectest.ListKey]string{
|
||||
{Filter: "^kubernetes-external-secrets$", Flags: listFlags("kube-system", "")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
kubernetes-external-secrets 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^external-secrets$", Flags: listFlags("default", "")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
external-secrets 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^my-release$", Flags: listFlags("default", "")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
my-release 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
|
||||
`,
|
||||
},
|
||||
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
|
||||
concurrency: 1,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("skip-needs=false include-needs=true but no diff on needed release", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: false,
|
||||
includeNeeds: true,
|
||||
},
|
||||
error: ``,
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
{{ $mark := "a" }}
|
||||
|
||||
releases:
|
||||
- name: kubernetes-external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: kube-system
|
||||
|
||||
- name: external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- kube-system/kubernetes-external-secrets
|
||||
|
||||
- name: my-release
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- default/external-secrets
|
||||
`,
|
||||
},
|
||||
selectors: []string{"app=test"},
|
||||
upgraded: []exectest.Release{},
|
||||
diffs: map[exectest.DiffKey]error{
|
||||
{Name: "kubernetes-external-secrets", Chart: "incubator/raw", Flags: "--namespacekube-system--detailed-exitcode--reset-values"}: nil,
|
||||
{Name: "external-secrets", Chart: "incubator/raw", Flags: "--namespacedefault--detailed-exitcode--reset-values"}: helmexec.ExitError{Code: 2},
|
||||
{Name: "my-release", Chart: "incubator/raw", Flags: "--namespacedefault--detailed-exitcode--reset-values"}: helmexec.ExitError{Code: 2},
|
||||
},
|
||||
lists: map[exectest.ListKey]string{
|
||||
{Filter: "^external-secrets$", Flags: listFlags("default", "")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
external-secrets 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^my-release$", Flags: listFlags("default", "")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
my-release 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
|
||||
`,
|
||||
},
|
||||
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
|
||||
concurrency: 1,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("skip-needs=false include-needs=true with installed but disabled release", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: false,
|
||||
includeNeeds: true,
|
||||
},
|
||||
error: ``,
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
{{ $mark := "a" }}
|
||||
|
||||
releases:
|
||||
- name: kubernetes-external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: kube-system
|
||||
installed: false
|
||||
|
||||
- name: external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- kube-system/kubernetes-external-secrets
|
||||
|
||||
- name: my-release
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- default/external-secrets
|
||||
`,
|
||||
},
|
||||
selectors: []string{"app=test"},
|
||||
upgraded: []exectest.Release{},
|
||||
lists: map[exectest.ListKey]string{
|
||||
{Filter: "^kubernetes-external-secrets$", Flags: listFlags("kube-system", "")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
kubernetes-external-secrets 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^external-secrets$", Flags: listFlags("default", "")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
external-secrets 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^my-release$", Flags: listFlags("default", "")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
my-release 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
|
||||
`,
|
||||
},
|
||||
diffs: map[exectest.DiffKey]error{
|
||||
{Name: "external-secrets", Chart: "incubator/raw", Flags: "--namespacedefault--detailed-exitcode--reset-values"}: helmexec.ExitError{Code: 2},
|
||||
{Name: "my-release", Chart: "incubator/raw", Flags: "--namespacedefault--detailed-exitcode--reset-values"}: helmexec.ExitError{Code: 2},
|
||||
},
|
||||
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
|
||||
concurrency: 1,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("skip-needs=false include-needs=true with not installed and disabled release", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: false,
|
||||
includeNeeds: true,
|
||||
},
|
||||
error: ``,
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
{{ $mark := "a" }}
|
||||
|
||||
releases:
|
||||
- name: kubernetes-external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: kube-system
|
||||
installed: false
|
||||
|
||||
- name: external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- kube-system/kubernetes-external-secrets
|
||||
|
||||
- name: my-release
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- default/external-secrets
|
||||
`,
|
||||
},
|
||||
selectors: []string{"app=test"},
|
||||
upgraded: []exectest.Release{},
|
||||
lists: map[exectest.ListKey]string{
|
||||
{Filter: "^kubernetes-external-secrets$", Flags: listFlags("kube-system", "")}: ``,
|
||||
{Filter: "^external-secrets$", Flags: listFlags("default", "")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
external-secrets 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^my-release$", Flags: listFlags("default", "")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
my-release 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
|
||||
`,
|
||||
},
|
||||
diffs: map[exectest.DiffKey]error{
|
||||
{Name: "external-secrets", Chart: "incubator/raw", Flags: "--namespacedefault--detailed-exitcode--reset-values"}: helmexec.ExitError{Code: 2},
|
||||
{Name: "my-release", Chart: "incubator/raw", Flags: "--namespacedefault--detailed-exitcode--reset-values"}: helmexec.ExitError{Code: 2},
|
||||
},
|
||||
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
|
||||
concurrency: 1,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("bad --selector", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
{{ $mark := "a" }}
|
||||
|
||||
releases:
|
||||
- name: kubernetes-external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: kube-system
|
||||
|
||||
- name: external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- kube-system/kubernetes-external-secrets
|
||||
|
||||
- name: my-release
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- default/external-secrets
|
||||
`,
|
||||
},
|
||||
selectors: []string{"app=test_non_existent"},
|
||||
upgraded: []exectest.Release{},
|
||||
error: "err: no releases found that matches specified selector(app=test_non_existent) and environment(default), in any helmfile",
|
||||
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
|
||||
concurrency: 1,
|
||||
log: `processing file "helmfile.yaml" in directory "."
|
||||
changing working directory to "/path/to"
|
||||
first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
|
||||
first-pass uses: &{default map[] map[]}
|
||||
first-pass rendering output of "helmfile.yaml.part.0":
|
||||
0:
|
||||
1:
|
||||
2:
|
||||
3: releases:
|
||||
4: - name: kubernetes-external-secrets
|
||||
5: chart: incubator/raw
|
||||
6: namespace: kube-system
|
||||
7:
|
||||
8: - name: external-secrets
|
||||
9: chart: incubator/raw
|
||||
10: namespace: default
|
||||
11: labels:
|
||||
12: app: test
|
||||
13: needs:
|
||||
14: - kube-system/kubernetes-external-secrets
|
||||
15:
|
||||
16: - name: my-release
|
||||
17: chart: incubator/raw
|
||||
18: namespace: default
|
||||
19: labels:
|
||||
20: app: test
|
||||
21: needs:
|
||||
22: - default/external-secrets
|
||||
23:
|
||||
|
||||
first-pass produced: &{default map[] map[]}
|
||||
first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]}
|
||||
vals:
|
||||
map[]
|
||||
defaultVals:[]
|
||||
second-pass rendering result of "helmfile.yaml.part.0":
|
||||
0:
|
||||
1:
|
||||
2:
|
||||
3: releases:
|
||||
4: - name: kubernetes-external-secrets
|
||||
5: chart: incubator/raw
|
||||
6: namespace: kube-system
|
||||
7:
|
||||
8: - name: external-secrets
|
||||
9: chart: incubator/raw
|
||||
10: namespace: default
|
||||
11: labels:
|
||||
12: app: test
|
||||
13: needs:
|
||||
14: - kube-system/kubernetes-external-secrets
|
||||
15:
|
||||
16: - name: my-release
|
||||
17: chart: incubator/raw
|
||||
18: namespace: default
|
||||
19: labels:
|
||||
20: app: test
|
||||
21: needs:
|
||||
22: - default/external-secrets
|
||||
23:
|
||||
|
||||
merged environment: &{default map[] map[]}
|
||||
0 release(s) matching app=test_non_existent found in helmfile.yaml
|
||||
|
||||
changing working directory back to "/path/to"
|
||||
`,
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,725 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/helmfile/vals"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/exectest"
|
||||
"github.com/helmfile/helmfile/pkg/filesystem"
|
||||
"github.com/helmfile/helmfile/pkg/helmexec"
|
||||
"github.com/helmfile/helmfile/pkg/testhelper"
|
||||
)
|
||||
|
||||
func TestApply_2(t *testing.T) {
|
||||
type fields struct {
|
||||
skipNeeds bool
|
||||
includeNeeds bool
|
||||
includeTransitiveNeeds bool
|
||||
}
|
||||
|
||||
type testcase struct {
|
||||
fields fields
|
||||
ns string
|
||||
concurrency int
|
||||
skipDiffOnInstall bool
|
||||
error string
|
||||
files map[string]string
|
||||
selectors []string
|
||||
lists map[exectest.ListKey]string
|
||||
diffs map[exectest.DiffKey]error
|
||||
upgraded []exectest.Release
|
||||
deleted []exectest.Release
|
||||
log string
|
||||
}
|
||||
|
||||
check := func(t *testing.T, tc testcase) {
|
||||
t.Helper()
|
||||
|
||||
wantUpgrades := tc.upgraded
|
||||
wantDeletes := tc.deleted
|
||||
|
||||
var helm = &exectest.Helm{
|
||||
FailOnUnexpectedList: true,
|
||||
FailOnUnexpectedDiff: true,
|
||||
Lists: tc.lists,
|
||||
Diffs: tc.diffs,
|
||||
DiffMutex: &sync.Mutex{},
|
||||
ChartsMutex: &sync.Mutex{},
|
||||
ReleasesMutex: &sync.Mutex{},
|
||||
Helm3: true,
|
||||
}
|
||||
|
||||
bs := runWithLogCapture(t, "debug", func(t *testing.T, logger *zap.SugaredLogger) {
|
||||
t.Helper()
|
||||
|
||||
valsRuntime, err := vals.New(vals.Options{CacheSize: 32})
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error creating vals runtime: %v", err)
|
||||
}
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: filesystem.DefaultFileSystem(),
|
||||
OverrideKubeContext: "default",
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
helms: map[helmKey]helmexec.Interface{
|
||||
createHelmKey("helm", "default"): helm,
|
||||
},
|
||||
valsRuntime: valsRuntime,
|
||||
}, tc.files)
|
||||
|
||||
if tc.ns != "" {
|
||||
app.Namespace = tc.ns
|
||||
}
|
||||
|
||||
if tc.selectors != nil {
|
||||
app.Selectors = tc.selectors
|
||||
}
|
||||
|
||||
syncErr := app.Apply(applyConfig{
|
||||
// if we check log output, concurrency must be 1. otherwise the test becomes non-deterministic.
|
||||
concurrency: tc.concurrency,
|
||||
logger: logger,
|
||||
skipDiffOnInstall: tc.skipDiffOnInstall,
|
||||
skipNeeds: tc.fields.skipNeeds,
|
||||
includeNeeds: tc.fields.includeNeeds,
|
||||
includeTransitiveNeeds: tc.fields.includeTransitiveNeeds,
|
||||
})
|
||||
|
||||
var gotErr string
|
||||
if syncErr != nil {
|
||||
gotErr = syncErr.Error()
|
||||
}
|
||||
|
||||
if d := cmp.Diff(tc.error, gotErr); d != "" {
|
||||
t.Fatalf("unexpected error: want (-), got (+): %s", d)
|
||||
}
|
||||
|
||||
if len(wantUpgrades) > len(helm.Releases) {
|
||||
t.Fatalf("insufficient number of upgrades: got %d, want %d", len(helm.Releases), len(wantUpgrades))
|
||||
}
|
||||
|
||||
for relIdx := range wantUpgrades {
|
||||
if wantUpgrades[relIdx].Name != helm.Releases[relIdx].Name {
|
||||
t.Errorf("releases[%d].name: got %q, want %q", relIdx, helm.Releases[relIdx].Name, wantUpgrades[relIdx].Name)
|
||||
}
|
||||
for flagIdx := range wantUpgrades[relIdx].Flags {
|
||||
if wantUpgrades[relIdx].Flags[flagIdx] != helm.Releases[relIdx].Flags[flagIdx] {
|
||||
t.Errorf("releases[%d].flags[%d]: got %v, want %v", relIdx, flagIdx, helm.Releases[relIdx].Flags[flagIdx], wantUpgrades[relIdx].Flags[flagIdx])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(wantDeletes) > len(helm.Deleted) {
|
||||
t.Fatalf("insufficient number of deletes: got %d, want %d", len(helm.Deleted), len(wantDeletes))
|
||||
}
|
||||
|
||||
for relIdx := range wantDeletes {
|
||||
if wantDeletes[relIdx].Name != helm.Deleted[relIdx].Name {
|
||||
t.Errorf("releases[%d].name: got %q, want %q", relIdx, helm.Deleted[relIdx].Name, wantDeletes[relIdx].Name)
|
||||
}
|
||||
for flagIdx := range wantDeletes[relIdx].Flags {
|
||||
if wantDeletes[relIdx].Flags[flagIdx] != helm.Deleted[relIdx].Flags[flagIdx] {
|
||||
t.Errorf("releaes[%d].flags[%d]: got %v, want %v", relIdx, flagIdx, helm.Deleted[relIdx].Flags[flagIdx], wantDeletes[relIdx].Flags[flagIdx])
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if tc.log != "" {
|
||||
actual := bs.String()
|
||||
|
||||
diff, exists := testhelper.Diff(tc.log, actual, 3)
|
||||
if exists {
|
||||
t.Errorf("unexpected log:\nDIFF\n%s\nEOD", diff)
|
||||
}
|
||||
} else {
|
||||
assertLogEqualsToSnapshot(t, bs.String())
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("skip-needs=true", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: true,
|
||||
},
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
{{ $mark := "a" }}
|
||||
|
||||
releases:
|
||||
- name: kubernetes-external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: kube-system
|
||||
|
||||
- name: external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- kube-system/kubernetes-external-secrets
|
||||
|
||||
- name: my-release
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- default/external-secrets
|
||||
`,
|
||||
},
|
||||
selectors: []string{"app=test"},
|
||||
upgraded: []exectest.Release{
|
||||
{Name: "external-secrets", Flags: []string{"--kube-context", "default", "--namespace", "default"}},
|
||||
{Name: "my-release", Flags: []string{"--kube-context", "default", "--namespace", "default"}},
|
||||
},
|
||||
diffs: map[exectest.DiffKey]error{
|
||||
{Name: "external-secrets", Chart: "incubator/raw", Flags: "--kube-contextdefault--namespacedefault--detailed-exitcode--reset-values"}: helmexec.ExitError{Code: 2},
|
||||
{Name: "my-release", Chart: "incubator/raw", Flags: "--kube-contextdefault--namespacedefault--detailed-exitcode--reset-values"}: helmexec.ExitError{Code: 2},
|
||||
},
|
||||
lists: map[exectest.ListKey]string{
|
||||
{Filter: "^external-secrets$", Flags: listFlags("default", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
external-secrets 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^my-release$", Flags: listFlags("default", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
my-release 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
|
||||
`,
|
||||
},
|
||||
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
|
||||
concurrency: 1,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("skip-needs=true with no diff on a release", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: true,
|
||||
},
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
{{ $mark := "a" }}
|
||||
|
||||
releases:
|
||||
- name: kubernetes-external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: kube-system
|
||||
|
||||
- name: external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- kube-system/kubernetes-external-secrets
|
||||
|
||||
- name: my-release
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- default/external-secrets
|
||||
`,
|
||||
},
|
||||
selectors: []string{"app=test"},
|
||||
upgraded: []exectest.Release{
|
||||
{Name: "external-secrets", Flags: []string{"--kube-context", "default", "--namespace", "default"}},
|
||||
},
|
||||
lists: map[exectest.ListKey]string{
|
||||
{Filter: "^external-secrets$", Flags: listFlags("default", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
^external-secrets$ 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
|
||||
`,
|
||||
},
|
||||
diffs: map[exectest.DiffKey]error{
|
||||
{Name: "external-secrets", Chart: "incubator/raw", Flags: "--kube-contextdefault--namespacedefault--detailed-exitcode--reset-values"}: helmexec.ExitError{Code: 2},
|
||||
{Name: "my-release", Chart: "incubator/raw", Flags: "--kube-contextdefault--namespacedefault--detailed-exitcode--reset-values"}: nil,
|
||||
},
|
||||
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
|
||||
concurrency: 1,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("skip-needs=false include-needs=true", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: false,
|
||||
includeNeeds: true,
|
||||
},
|
||||
error: ``,
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
{{ $mark := "a" }}
|
||||
|
||||
releases:
|
||||
- name: kubernetes-external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: kube-system
|
||||
|
||||
- name: external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- kube-system/kubernetes-external-secrets
|
||||
|
||||
- name: my-release
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- default/external-secrets
|
||||
`,
|
||||
},
|
||||
selectors: []string{"app=test"},
|
||||
upgraded: []exectest.Release{},
|
||||
diffs: map[exectest.DiffKey]error{
|
||||
{Name: "kubernetes-external-secrets", Chart: "incubator/raw", Flags: "--kube-contextdefault--namespacekube-system--detailed-exitcode--reset-values"}: helmexec.ExitError{Code: 2},
|
||||
{Name: "external-secrets", Chart: "incubator/raw", Flags: "--kube-contextdefault--namespacedefault--detailed-exitcode--reset-values"}: helmexec.ExitError{Code: 2},
|
||||
{Name: "my-release", Chart: "incubator/raw", Flags: "--kube-contextdefault--namespacedefault--detailed-exitcode--reset-values"}: helmexec.ExitError{Code: 2},
|
||||
},
|
||||
lists: map[exectest.ListKey]string{
|
||||
{Filter: "^kubernetes-external-secrets$", Flags: listFlags("kube-system", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
kubernetes-external-secrets 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^external-secrets$", Flags: listFlags("default", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
external-secrets 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^my-release$", Flags: listFlags("default", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
my-release 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
|
||||
`,
|
||||
},
|
||||
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
|
||||
concurrency: 1,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("skip-needs=false include-needs=true but no diff on needed release", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: false,
|
||||
includeNeeds: true,
|
||||
},
|
||||
error: ``,
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
{{ $mark := "a" }}
|
||||
|
||||
releases:
|
||||
- name: kubernetes-external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: kube-system
|
||||
|
||||
- name: external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- kube-system/kubernetes-external-secrets
|
||||
|
||||
- name: my-release
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- default/external-secrets
|
||||
`,
|
||||
},
|
||||
selectors: []string{"app=test"},
|
||||
upgraded: []exectest.Release{},
|
||||
diffs: map[exectest.DiffKey]error{
|
||||
{Name: "kubernetes-external-secrets", Chart: "incubator/raw", Flags: "--kube-contextdefault--namespacekube-system--detailed-exitcode--reset-values"}: nil,
|
||||
{Name: "external-secrets", Chart: "incubator/raw", Flags: "--kube-contextdefault--namespacedefault--detailed-exitcode--reset-values"}: helmexec.ExitError{Code: 2},
|
||||
{Name: "my-release", Chart: "incubator/raw", Flags: "--kube-contextdefault--namespacedefault--detailed-exitcode--reset-values"}: helmexec.ExitError{Code: 2},
|
||||
},
|
||||
lists: map[exectest.ListKey]string{
|
||||
{Filter: "^external-secrets$", Flags: listFlags("default", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
external-secrets 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^my-release$", Flags: listFlags("default", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
my-release 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
|
||||
`,
|
||||
},
|
||||
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
|
||||
concurrency: 1,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("skip-needs=false include-needs=true with installed but disabled release", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: false,
|
||||
includeNeeds: true,
|
||||
},
|
||||
error: ``,
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
{{ $mark := "a" }}
|
||||
|
||||
releases:
|
||||
- name: kubernetes-external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: kube-system
|
||||
installed: false
|
||||
|
||||
- name: external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- kube-system/kubernetes-external-secrets
|
||||
|
||||
- name: my-release
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- default/external-secrets
|
||||
`,
|
||||
},
|
||||
selectors: []string{"app=test"},
|
||||
upgraded: []exectest.Release{},
|
||||
lists: map[exectest.ListKey]string{
|
||||
{Filter: "^kubernetes-external-secrets$", Flags: listFlags("kube-system", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
kubernetes-external-secrets 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^external-secrets$", Flags: listFlags("default", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
external-secrets 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^my-release$", Flags: listFlags("default", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
my-release 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
|
||||
`,
|
||||
},
|
||||
diffs: map[exectest.DiffKey]error{
|
||||
{Name: "external-secrets", Chart: "incubator/raw", Flags: "--kube-contextdefault--namespacedefault--detailed-exitcode--reset-values"}: helmexec.ExitError{Code: 2},
|
||||
{Name: "my-release", Chart: "incubator/raw", Flags: "--kube-contextdefault--namespacedefault--detailed-exitcode--reset-values"}: helmexec.ExitError{Code: 2},
|
||||
},
|
||||
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
|
||||
concurrency: 1,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("skip-needs=false include-needs=true with not installed and disabled release", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: false,
|
||||
includeNeeds: true,
|
||||
},
|
||||
error: ``,
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
{{ $mark := "a" }}
|
||||
|
||||
releases:
|
||||
- name: kubernetes-external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: kube-system
|
||||
installed: false
|
||||
|
||||
- name: external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- kube-system/kubernetes-external-secrets
|
||||
|
||||
- name: my-release
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- default/external-secrets
|
||||
`,
|
||||
},
|
||||
selectors: []string{"app=test"},
|
||||
upgraded: []exectest.Release{},
|
||||
lists: map[exectest.ListKey]string{
|
||||
{Filter: "^kubernetes-external-secrets$", Flags: listFlags("kube-system", "default")}: ``,
|
||||
{Filter: "^external-secrets$", Flags: listFlags("default", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
external-secrets 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^my-release$", Flags: listFlags("default", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
my-release 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
|
||||
`,
|
||||
},
|
||||
diffs: map[exectest.DiffKey]error{
|
||||
{Name: "external-secrets", Chart: "incubator/raw", Flags: "--kube-contextdefault--namespacedefault--detailed-exitcode--reset-values"}: helmexec.ExitError{Code: 2},
|
||||
{Name: "my-release", Chart: "incubator/raw", Flags: "--kube-contextdefault--namespacedefault--detailed-exitcode--reset-values"}: helmexec.ExitError{Code: 2},
|
||||
},
|
||||
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
|
||||
concurrency: 1,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("include-transitive-needs=true", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: false,
|
||||
includeNeeds: true,
|
||||
includeTransitiveNeeds: true,
|
||||
},
|
||||
error: ``,
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
{{ $mark := "a" }}
|
||||
|
||||
releases:
|
||||
- name: serviceA
|
||||
chart: my/chart
|
||||
needs:
|
||||
- serviceB
|
||||
|
||||
- name: serviceB
|
||||
chart: my/chart
|
||||
needs:
|
||||
- serviceC
|
||||
|
||||
- name: serviceC
|
||||
chart: my/chart
|
||||
|
||||
- name: serviceD
|
||||
chart: my/chart
|
||||
`,
|
||||
},
|
||||
selectors: []string{"name=serviceA"},
|
||||
upgraded: []exectest.Release{},
|
||||
diffs: map[exectest.DiffKey]error{
|
||||
{Name: "serviceA", Chart: "my/chart", Flags: "--kube-contextdefault--detailed-exitcode--reset-values"}: helmexec.ExitError{Code: 2},
|
||||
{Name: "serviceB", Chart: "my/chart", Flags: "--kube-contextdefault--detailed-exitcode--reset-values"}: helmexec.ExitError{Code: 2},
|
||||
{Name: "serviceC", Chart: "my/chart", Flags: "--kube-contextdefault--detailed-exitcode--reset-values"}: helmexec.ExitError{Code: 2},
|
||||
},
|
||||
lists: map[exectest.ListKey]string{
|
||||
{Filter: "^serviceA$", Flags: listFlags("", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
serviceA 4 Fri Nov 1 08:40:07 2019 DEPLOYED chart-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^serviceB$", Flags: listFlags("", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
serviceB 4 Fri Nov 1 08:40:07 2019 DEPLOYED chart-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^serviceC$", Flags: listFlags("", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
serviceC 4 Fri Nov 1 08:40:07 2019 DEPLOYED chart-3.1.0 3.1.0 default
|
||||
`,
|
||||
},
|
||||
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
|
||||
concurrency: 1,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("bad --selector", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
{{ $mark := "a" }}
|
||||
|
||||
releases:
|
||||
- name: kubernetes-external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: kube-system
|
||||
|
||||
- name: external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- kube-system/kubernetes-external-secrets
|
||||
|
||||
- name: my-release
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- default/external-secrets
|
||||
`,
|
||||
},
|
||||
selectors: []string{"app=test_non_existent"},
|
||||
upgraded: []exectest.Release{},
|
||||
error: "err: no releases found that matches specified selector(app=test_non_existent) and environment(default), in any helmfile",
|
||||
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
|
||||
concurrency: 1,
|
||||
log: `processing file "helmfile.yaml" in directory "."
|
||||
changing working directory to "/path/to"
|
||||
first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
|
||||
first-pass uses: &{default map[] map[]}
|
||||
first-pass rendering output of "helmfile.yaml.part.0":
|
||||
0:
|
||||
1:
|
||||
2:
|
||||
3: releases:
|
||||
4: - name: kubernetes-external-secrets
|
||||
5: chart: incubator/raw
|
||||
6: namespace: kube-system
|
||||
7:
|
||||
8: - name: external-secrets
|
||||
9: chart: incubator/raw
|
||||
10: namespace: default
|
||||
11: labels:
|
||||
12: app: test
|
||||
13: needs:
|
||||
14: - kube-system/kubernetes-external-secrets
|
||||
15:
|
||||
16: - name: my-release
|
||||
17: chart: incubator/raw
|
||||
18: namespace: default
|
||||
19: labels:
|
||||
20: app: test
|
||||
21: needs:
|
||||
22: - default/external-secrets
|
||||
23:
|
||||
|
||||
first-pass produced: &{default map[] map[]}
|
||||
first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]}
|
||||
vals:
|
||||
map[]
|
||||
defaultVals:[]
|
||||
second-pass rendering result of "helmfile.yaml.part.0":
|
||||
0:
|
||||
1:
|
||||
2:
|
||||
3: releases:
|
||||
4: - name: kubernetes-external-secrets
|
||||
5: chart: incubator/raw
|
||||
6: namespace: kube-system
|
||||
7:
|
||||
8: - name: external-secrets
|
||||
9: chart: incubator/raw
|
||||
10: namespace: default
|
||||
11: labels:
|
||||
12: app: test
|
||||
13: needs:
|
||||
14: - kube-system/kubernetes-external-secrets
|
||||
15:
|
||||
16: - name: my-release
|
||||
17: chart: incubator/raw
|
||||
18: namespace: default
|
||||
19: labels:
|
||||
20: app: test
|
||||
21: needs:
|
||||
22: - default/external-secrets
|
||||
23:
|
||||
|
||||
merged environment: &{default map[] map[]}
|
||||
0 release(s) matching app=test_non_existent found in helmfile.yaml
|
||||
|
||||
changing working directory back to "/path/to"
|
||||
`,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("deduplicate by --selector", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
releases:
|
||||
- name: foo
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
component: raw
|
||||
index: '1'
|
||||
|
||||
- name: foo
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
component: raw
|
||||
index: '2'
|
||||
`,
|
||||
},
|
||||
selectors: []string{"index=1"},
|
||||
upgraded: []exectest.Release{},
|
||||
diffs: map[exectest.DiffKey]error{
|
||||
{Name: "foo", Chart: "incubator/raw", Flags: "--kube-contextdefault--namespacedefault--detailed-exitcode--reset-values"}: helmexec.ExitError{Code: 2},
|
||||
},
|
||||
lists: map[exectest.ListKey]string{
|
||||
{Filter: "^foo$", Flags: listFlags("default", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
foo 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
|
||||
`,
|
||||
},
|
||||
error: "",
|
||||
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
|
||||
concurrency: 1,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("select non existent release with --allow-no-matching-release", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
releases:
|
||||
- name: foo
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
|
||||
- name: bar
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
`,
|
||||
},
|
||||
selectors: []string{"app=foo"},
|
||||
upgraded: []exectest.Release{},
|
||||
error: "err: no releases found that matches specified selector(app=foo) and environment(default), in any helmfile",
|
||||
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
|
||||
concurrency: 1,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("select single release from helmfile with two duplicates", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
releases:
|
||||
- name: foo
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
|
||||
- name: bar
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: build
|
||||
|
||||
- name: bar
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
`,
|
||||
},
|
||||
selectors: []string{"name=foo"},
|
||||
upgraded: []exectest.Release{},
|
||||
diffs: map[exectest.DiffKey]error{
|
||||
{Name: "foo", Chart: "incubator/raw", Flags: "--kube-contextdefault--namespacedefault--detailed-exitcode--reset-values"}: helmexec.ExitError{Code: 2},
|
||||
},
|
||||
lists: map[exectest.ListKey]string{
|
||||
{Filter: "^foo$", Flags: listFlags("default", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
foo 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
|
||||
`,
|
||||
},
|
||||
error: "",
|
||||
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
|
||||
concurrency: 1,
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,418 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/helmfile/vals"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/exectest"
|
||||
"github.com/helmfile/helmfile/pkg/filesystem"
|
||||
"github.com/helmfile/helmfile/pkg/helmexec"
|
||||
"github.com/helmfile/helmfile/pkg/testhelper"
|
||||
)
|
||||
|
||||
func TestDiffWithNeeds(t *testing.T) {
|
||||
type fields struct {
|
||||
skipNeeds bool
|
||||
includeNeeds bool
|
||||
includeTransitiveNeeds bool
|
||||
noHooks bool
|
||||
}
|
||||
|
||||
type testcase struct {
|
||||
fields fields
|
||||
ns string
|
||||
error string
|
||||
selectors []string
|
||||
diffed []exectest.Release
|
||||
}
|
||||
|
||||
check := func(t *testing.T, tc testcase) {
|
||||
t.Helper()
|
||||
|
||||
wantDiffs := tc.diffed
|
||||
|
||||
var helm = &exectest.Helm{
|
||||
FailOnUnexpectedList: true,
|
||||
FailOnUnexpectedDiff: true,
|
||||
DiffMutex: &sync.Mutex{},
|
||||
ChartsMutex: &sync.Mutex{},
|
||||
ReleasesMutex: &sync.Mutex{},
|
||||
Helm3: true,
|
||||
}
|
||||
|
||||
bs := runWithLogCapture(t, "debug", func(t *testing.T, logger *zap.SugaredLogger) {
|
||||
t.Helper()
|
||||
|
||||
valsRuntime, err := vals.New(vals.Options{CacheSize: 32})
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error creating vals runtime: %v", err)
|
||||
}
|
||||
|
||||
files := map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
releases:
|
||||
- name: logging
|
||||
chart: incubator/raw
|
||||
namespace: kube-system
|
||||
|
||||
- name: kubernetes-external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: kube-system
|
||||
needs:
|
||||
- kube-system/logging
|
||||
|
||||
- name: external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- kube-system/kubernetes-external-secrets
|
||||
|
||||
- name: my-release
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- default/external-secrets
|
||||
|
||||
|
||||
# Disabled releases are treated as missing
|
||||
- name: disabled
|
||||
chart: incubator/raw
|
||||
namespace: kube-system
|
||||
installed: false
|
||||
|
||||
- name: test2
|
||||
chart: incubator/raw
|
||||
needs:
|
||||
- kube-system/disabled
|
||||
|
||||
- name: test3
|
||||
chart: incubator/raw
|
||||
needs:
|
||||
- test2
|
||||
`,
|
||||
}
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: filesystem.DefaultFileSystem(),
|
||||
OverrideKubeContext: "default",
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
helms: map[helmKey]helmexec.Interface{
|
||||
createHelmKey("helm", "default"): helm,
|
||||
},
|
||||
valsRuntime: valsRuntime,
|
||||
}, files)
|
||||
|
||||
if tc.ns != "" {
|
||||
app.Namespace = tc.ns
|
||||
}
|
||||
|
||||
if tc.selectors != nil {
|
||||
app.Selectors = tc.selectors
|
||||
}
|
||||
|
||||
diffErr := app.Diff(applyConfig{
|
||||
// if we check log output, concurrency must be 1. otherwise the test becomes non-deterministic.
|
||||
concurrency: 1,
|
||||
logger: logger,
|
||||
skipNeeds: tc.fields.skipNeeds,
|
||||
includeNeeds: tc.fields.includeNeeds,
|
||||
includeTransitiveNeeds: tc.fields.includeTransitiveNeeds,
|
||||
noHooks: tc.fields.noHooks,
|
||||
})
|
||||
|
||||
var gotErr string
|
||||
if diffErr != nil {
|
||||
gotErr = diffErr.Error()
|
||||
}
|
||||
|
||||
if d := cmp.Diff(tc.error, gotErr); d != "" {
|
||||
t.Fatalf("unexpected error: want (-), got (+): %s", d)
|
||||
}
|
||||
|
||||
require.Equal(t, wantDiffs, helm.Diffed)
|
||||
})
|
||||
|
||||
testhelper.RequireLog(t, "app_diff_test", bs)
|
||||
}
|
||||
|
||||
t.Run("fail on unselected need by default", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
selectors: []string{"app=test"},
|
||||
error: `in ./helmfile.yaml: release "default/default/external-secrets" depends on "default/kube-system/kubernetes-external-secrets" which does not match the selectors. Please add a selector like "--selector name=kubernetes-external-secrets", or indicate whether to skip (--skip-needs) or include (--include-needs) these dependencies`,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("skip-needs", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: true,
|
||||
},
|
||||
selectors: []string{"app=test"},
|
||||
diffed: []exectest.Release{
|
||||
{Name: "external-secrets", Flags: []string{"--kube-context", "default", "--namespace", "default", "--reset-values"}},
|
||||
{Name: "my-release", Flags: []string{"--kube-context", "default", "--namespace", "default", "--reset-values"}},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("include-needs", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: false,
|
||||
includeNeeds: true,
|
||||
},
|
||||
error: ``,
|
||||
selectors: []string{"app=test"},
|
||||
diffed: []exectest.Release{
|
||||
// TODO: Turned out we can't differentiate needs vs transitive needs in this case :thinking:
|
||||
{Name: "logging", Flags: []string{"--kube-context", "default", "--namespace", "kube-system", "--reset-values"}},
|
||||
{Name: "kubernetes-external-secrets", Flags: []string{"--kube-context", "default", "--namespace", "kube-system", "--reset-values"}},
|
||||
{Name: "external-secrets", Flags: []string{"--kube-context", "default", "--namespace", "default", "--reset-values"}},
|
||||
{Name: "my-release", Flags: []string{"--kube-context", "default", "--namespace", "default", "--reset-values"}},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("include-transitive-needs", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: false,
|
||||
includeTransitiveNeeds: true,
|
||||
},
|
||||
error: ``,
|
||||
selectors: []string{"app=test"},
|
||||
diffed: []exectest.Release{
|
||||
{Name: "logging", Flags: []string{"--kube-context", "default", "--namespace", "kube-system", "--reset-values"}},
|
||||
{Name: "kubernetes-external-secrets", Flags: []string{"--kube-context", "default", "--namespace", "kube-system", "--reset-values"}},
|
||||
{Name: "external-secrets", Flags: []string{"--kube-context", "default", "--namespace", "default", "--reset-values"}},
|
||||
{Name: "my-release", Flags: []string{"--kube-context", "default", "--namespace", "default", "--reset-values"}},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("include-needs should not fail on disabled direct need", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: false,
|
||||
includeNeeds: true,
|
||||
},
|
||||
selectors: []string{"name=test2"},
|
||||
diffed: []exectest.Release{
|
||||
{Name: "test2", Flags: []string{"--kube-context", "default", "--reset-values"}},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("include-needs should not fail on disabled transitive need", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: false,
|
||||
includeNeeds: true,
|
||||
},
|
||||
selectors: []string{"name=test3"},
|
||||
diffed: []exectest.Release{
|
||||
{Name: "test2", Flags: []string{"--kube-context", "default", "--reset-values"}},
|
||||
{Name: "test3", Flags: []string{"--kube-context", "default", "--reset-values"}},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("include-transitive-needs should not fail on disabled transitive need", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: false,
|
||||
includeNeeds: false,
|
||||
includeTransitiveNeeds: true,
|
||||
},
|
||||
selectors: []string{"name=test3"},
|
||||
diffed: []exectest.Release{
|
||||
{Name: "test2", Flags: []string{"--kube-context", "default", "--reset-values"}},
|
||||
{Name: "test3", Flags: []string{"--kube-context", "default", "--reset-values"}},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("include-needs with include-transitive-needs should not fail on disabled direct need", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: false,
|
||||
includeNeeds: true,
|
||||
includeTransitiveNeeds: true,
|
||||
},
|
||||
selectors: []string{"name=test2"},
|
||||
diffed: []exectest.Release{
|
||||
{Name: "test2", Flags: []string{"--kube-context", "default", "--reset-values"}},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("include-needs with include-transitive-needs should not fail on disabled transitive need", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: false,
|
||||
includeNeeds: true,
|
||||
includeTransitiveNeeds: true,
|
||||
},
|
||||
selectors: []string{"name=test3"},
|
||||
diffed: []exectest.Release{
|
||||
{Name: "test2", Flags: []string{"--kube-context", "default", "--reset-values"}},
|
||||
{Name: "test3", Flags: []string{"--kube-context", "default", "--reset-values"}},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("no-hooks", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: true,
|
||||
noHooks: true,
|
||||
},
|
||||
selectors: []string{"app=test"},
|
||||
diffed: []exectest.Release{
|
||||
{Name: "external-secrets", Flags: []string{"--kube-context", "default", "--namespace", "default", "--no-hooks", "--reset-values"}},
|
||||
{Name: "my-release", Flags: []string{"--kube-context", "default", "--namespace", "default", "--no-hooks", "--reset-values"}},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("bad selector", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
selectors: []string{"app=test_non_existent"},
|
||||
diffed: nil,
|
||||
error: "err: no releases found that matches specified selector(app=test_non_existent) and environment(default), in any helmfile",
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDiffWithInstalled(t *testing.T) {
|
||||
type testcase struct {
|
||||
helmfile string
|
||||
ns string
|
||||
error string
|
||||
selectors []string
|
||||
lists map[exectest.ListKey]string
|
||||
diffed []exectest.Release
|
||||
}
|
||||
|
||||
check := func(t *testing.T, tc testcase) {
|
||||
t.Helper()
|
||||
|
||||
wantDiffs := tc.diffed
|
||||
|
||||
var helm = &exectest.Helm{
|
||||
FailOnUnexpectedList: true,
|
||||
FailOnUnexpectedDiff: true,
|
||||
Lists: tc.lists,
|
||||
DiffMutex: &sync.Mutex{},
|
||||
ChartsMutex: &sync.Mutex{},
|
||||
ReleasesMutex: &sync.Mutex{},
|
||||
Helm3: true,
|
||||
}
|
||||
|
||||
bs := runWithLogCapture(t, "debug", func(t *testing.T, logger *zap.SugaredLogger) {
|
||||
t.Helper()
|
||||
|
||||
valsRuntime, err := vals.New(vals.Options{CacheSize: 32})
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error creating vals runtime: %v", err)
|
||||
}
|
||||
|
||||
files := map[string]string{
|
||||
"/path/to/helmfile.yaml": tc.helmfile,
|
||||
}
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: filesystem.DefaultFileSystem(),
|
||||
OverrideKubeContext: "default",
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
helms: map[helmKey]helmexec.Interface{
|
||||
createHelmKey("helm", "default"): helm,
|
||||
},
|
||||
valsRuntime: valsRuntime,
|
||||
}, files)
|
||||
|
||||
if tc.ns != "" {
|
||||
app.Namespace = tc.ns
|
||||
}
|
||||
|
||||
if tc.selectors != nil {
|
||||
app.Selectors = tc.selectors
|
||||
}
|
||||
|
||||
diffErr := app.Diff(applyConfig{
|
||||
// if we check log output, concurrency must be 1. otherwise the test becomes non-deterministic.
|
||||
concurrency: 1,
|
||||
logger: logger,
|
||||
skipNeeds: true,
|
||||
})
|
||||
|
||||
var gotErr string
|
||||
if diffErr != nil {
|
||||
gotErr = diffErr.Error()
|
||||
}
|
||||
|
||||
if d := cmp.Diff(tc.error, gotErr); d != "" {
|
||||
t.Fatalf("unexpected error: want (-), got (+): %s", d)
|
||||
}
|
||||
|
||||
require.Equal(t, wantDiffs, helm.Diffed)
|
||||
})
|
||||
|
||||
testhelper.RequireLog(t, "app_diff_test", bs)
|
||||
}
|
||||
|
||||
t.Run("show diff on changed selected release", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
helmfile: `
|
||||
releases:
|
||||
- name: a
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
- name: b
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
`,
|
||||
selectors: []string{"name=a"},
|
||||
lists: map[exectest.ListKey]string{
|
||||
{Filter: "^a$", Flags: listFlags("default", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
foo 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
|
||||
`,
|
||||
},
|
||||
diffed: []exectest.Release{
|
||||
{Name: "a", Flags: []string{"--kube-context", "default", "--namespace", "default", "--reset-values"}},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("shows no diff on already uninstalled selected release", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
helmfile: `
|
||||
releases:
|
||||
- name: a
|
||||
chart: incubator/raw
|
||||
installed: false
|
||||
namespace: default
|
||||
- name: b
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
`,
|
||||
selectors: []string{"name=a"},
|
||||
lists: map[exectest.ListKey]string{
|
||||
{Filter: "^a$", Flags: listFlags("default", "default")}: ``,
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/helmfile/vals"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/exectest"
|
||||
ffs "github.com/helmfile/helmfile/pkg/filesystem"
|
||||
"github.com/helmfile/helmfile/pkg/helmexec"
|
||||
"github.com/helmfile/helmfile/pkg/testhelper"
|
||||
)
|
||||
|
||||
func TestLint(t *testing.T) {
|
||||
type fields struct {
|
||||
skipNeeds bool
|
||||
includeNeeds bool
|
||||
includeTransitiveNeeds bool
|
||||
}
|
||||
|
||||
type testcase struct {
|
||||
fields fields
|
||||
ns string
|
||||
error string
|
||||
selectors []string
|
||||
linted []exectest.Release
|
||||
}
|
||||
|
||||
check := func(t *testing.T, tc testcase) {
|
||||
t.Helper()
|
||||
|
||||
wantLints := tc.linted
|
||||
|
||||
var helm = &exectest.Helm{
|
||||
FailOnUnexpectedList: true,
|
||||
FailOnUnexpectedDiff: true,
|
||||
DiffMutex: &sync.Mutex{},
|
||||
ChartsMutex: &sync.Mutex{},
|
||||
ReleasesMutex: &sync.Mutex{},
|
||||
Helm3: true,
|
||||
}
|
||||
|
||||
bs := runWithLogCapture(t, "debug", func(t *testing.T, logger *zap.SugaredLogger) {
|
||||
t.Helper()
|
||||
|
||||
valsRuntime, err := vals.New(vals.Options{CacheSize: 32})
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error creating vals runtime: %v", err)
|
||||
}
|
||||
|
||||
files := map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
releases:
|
||||
- name: logging
|
||||
chart: incubator/raw
|
||||
namespace: kube-system
|
||||
|
||||
- name: kubernetes-external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: kube-system
|
||||
needs:
|
||||
- kube-system/logging
|
||||
|
||||
- name: external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- kube-system/kubernetes-external-secrets
|
||||
|
||||
- name: my-release
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- default/external-secrets
|
||||
|
||||
|
||||
# Disabled releases are treated as missing
|
||||
- name: disabled
|
||||
chart: incubator/raw
|
||||
namespace: kube-system
|
||||
installed: false
|
||||
|
||||
- name: test2
|
||||
chart: incubator/raw
|
||||
needs:
|
||||
- kube-system/disabled
|
||||
|
||||
- name: test3
|
||||
chart: incubator/raw
|
||||
needs:
|
||||
- test2
|
||||
`,
|
||||
}
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: ffs.DefaultFileSystem(),
|
||||
OverrideKubeContext: "default",
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
helms: map[helmKey]helmexec.Interface{
|
||||
createHelmKey("helm", "default"): helm,
|
||||
},
|
||||
valsRuntime: valsRuntime,
|
||||
}, files)
|
||||
|
||||
if tc.ns != "" {
|
||||
app.Namespace = tc.ns
|
||||
}
|
||||
|
||||
if tc.selectors != nil {
|
||||
app.Selectors = tc.selectors
|
||||
}
|
||||
|
||||
lintErr := app.Lint(applyConfig{
|
||||
// if we check log output, concurrency must be 1. otherwise the test becomes non-deterministic.
|
||||
concurrency: 1,
|
||||
logger: logger,
|
||||
skipNeeds: tc.fields.skipNeeds,
|
||||
includeNeeds: tc.fields.includeNeeds,
|
||||
includeTransitiveNeeds: tc.fields.includeTransitiveNeeds,
|
||||
})
|
||||
|
||||
var gotErr string
|
||||
if lintErr != nil {
|
||||
gotErr = lintErr.Error()
|
||||
}
|
||||
|
||||
if d := cmp.Diff(tc.error, gotErr); d != "" {
|
||||
t.Fatalf("unexpected error: want (-), got (+): %s", d)
|
||||
}
|
||||
|
||||
require.Equal(t, wantLints, helm.Linted)
|
||||
})
|
||||
|
||||
testNameComponents := strings.Split(t.Name(), "/")
|
||||
testBaseName := strings.ToLower(
|
||||
strings.ReplaceAll(
|
||||
testNameComponents[len(testNameComponents)-1],
|
||||
" ",
|
||||
"_",
|
||||
),
|
||||
)
|
||||
wantLogFileDir := filepath.Join("testdata", "app_lint_test")
|
||||
wantLogFile := filepath.Join(wantLogFileDir, testBaseName)
|
||||
wantLogData, err := os.ReadFile(wantLogFile)
|
||||
updateLogFile := err != nil
|
||||
wantLog := string(wantLogData)
|
||||
gotLog := bs.String()
|
||||
if updateLogFile {
|
||||
if err := os.MkdirAll(wantLogFileDir, 0755); err != nil {
|
||||
t.Fatalf("unable to create directory %q: %v", wantLogFileDir, err)
|
||||
}
|
||||
if err := os.WriteFile(wantLogFile, bs.Bytes(), 0644); err != nil {
|
||||
t.Fatalf("unable to update lint log snapshot: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
diff, exists := testhelper.Diff(wantLog, gotLog, 3)
|
||||
if exists {
|
||||
t.Errorf("unexpected log:\nDIFF\n%s\nEOD", diff)
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("fail on unselected need by default", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
selectors: []string{"app=test"},
|
||||
error: `in ./helmfile.yaml: release "default/default/external-secrets" depends on "default/kube-system/kubernetes-external-secrets" which does not match the selectors. Please add a selector like "--selector name=kubernetes-external-secrets", or indicate whether to skip (--skip-needs) or include (--include-needs) these dependencies`,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("skip-needs", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: true,
|
||||
},
|
||||
selectors: []string{"app=test"},
|
||||
linted: []exectest.Release{
|
||||
{Name: "external-secrets", Flags: []string{"--namespace", "default"}},
|
||||
{Name: "my-release", Flags: []string{"--namespace", "default"}},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("include-needs", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: false,
|
||||
includeNeeds: true,
|
||||
},
|
||||
error: ``,
|
||||
selectors: []string{"app=test"},
|
||||
linted: []exectest.Release{
|
||||
// TODO: Turned out we can't differentiate needs vs transitive needs in this case :thinking:
|
||||
{Name: "logging", Flags: []string{"--namespace", "kube-system"}},
|
||||
{Name: "kubernetes-external-secrets", Flags: []string{"--namespace", "kube-system"}},
|
||||
{Name: "external-secrets", Flags: []string{"--namespace", "default"}},
|
||||
{Name: "my-release", Flags: []string{"--namespace", "default"}},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("include-transitive-needs", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: false,
|
||||
includeTransitiveNeeds: true,
|
||||
},
|
||||
error: ``,
|
||||
selectors: []string{"app=test"},
|
||||
linted: []exectest.Release{
|
||||
{Name: "logging", Flags: []string{"--namespace", "kube-system"}},
|
||||
{Name: "kubernetes-external-secrets", Flags: []string{"--namespace", "kube-system"}},
|
||||
{Name: "external-secrets", Flags: []string{"--namespace", "default"}},
|
||||
{Name: "my-release", Flags: []string{"--namespace", "default"}},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("include-needs should not fail on disabled direct need", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: false,
|
||||
includeNeeds: true,
|
||||
},
|
||||
selectors: []string{"name=test2"},
|
||||
linted: []exectest.Release{
|
||||
{Name: "test2", Flags: []string{}},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("include-needs should not fail on disabled transitive need", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: false,
|
||||
includeNeeds: true,
|
||||
},
|
||||
selectors: []string{"name=test3"},
|
||||
linted: []exectest.Release{
|
||||
{Name: "test2", Flags: []string{}},
|
||||
{Name: "test3", Flags: []string{}},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("include-transitive-needs should not fail on disabled transitive need", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: false,
|
||||
includeNeeds: false,
|
||||
includeTransitiveNeeds: true,
|
||||
},
|
||||
selectors: []string{"name=test3"},
|
||||
linted: []exectest.Release{
|
||||
{Name: "test2", Flags: []string{}},
|
||||
{Name: "test3", Flags: []string{}},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("include-needs with include-transitive-needs should not fail on disabled direct need", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: false,
|
||||
includeNeeds: true,
|
||||
includeTransitiveNeeds: true,
|
||||
},
|
||||
selectors: []string{"name=test2"},
|
||||
linted: []exectest.Release{
|
||||
{Name: "test2", Flags: []string{}},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("include-needs with include-transitive-needs should not fail on disabled transitive need", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: false,
|
||||
includeNeeds: true,
|
||||
includeTransitiveNeeds: true,
|
||||
},
|
||||
selectors: []string{"name=test3"},
|
||||
linted: []exectest.Release{
|
||||
{Name: "test2", Flags: []string{}},
|
||||
{Name: "test3", Flags: []string{}},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("bad selector", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
selectors: []string{"app=test_non_existent"},
|
||||
linted: nil,
|
||||
error: "err: no releases found that matches specified selector(app=test_non_existent) and environment(default), in any helmfile",
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,297 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/helmfile/vals"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/zap"
|
||||
|
||||
ffs "github.com/helmfile/helmfile/pkg/filesystem"
|
||||
"github.com/helmfile/helmfile/pkg/helmexec"
|
||||
"github.com/helmfile/helmfile/pkg/testhelper"
|
||||
"github.com/helmfile/helmfile/pkg/testutil"
|
||||
)
|
||||
|
||||
func testListWithEnvironment(t *testing.T, cfg configImpl) {
|
||||
type testcase struct {
|
||||
environment string
|
||||
ns string
|
||||
error string
|
||||
selectors []string
|
||||
expected string
|
||||
}
|
||||
|
||||
check := func(t *testing.T, tc testcase, cfg configImpl) {
|
||||
t.Helper()
|
||||
|
||||
bs := runWithLogCapture(t, "debug", func(t *testing.T, logger *zap.SugaredLogger) {
|
||||
t.Helper()
|
||||
|
||||
valsRuntime, err := vals.New(vals.Options{CacheSize: 32})
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error creating vals runtime: %v", err)
|
||||
}
|
||||
|
||||
files := map[string]string{
|
||||
"/path/to/helmfile.d/helmfile_1.yaml": `
|
||||
environments:
|
||||
development: {}
|
||||
shared: {}
|
||||
|
||||
releases:
|
||||
- name: logging
|
||||
chart: incubator/raw
|
||||
namespace: kube-system
|
||||
|
||||
- name: kubernetes-external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: kube-system
|
||||
needs:
|
||||
- kube-system/logging
|
||||
|
||||
- name: external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- kube-system/kubernetes-external-secrets
|
||||
|
||||
- name: my-release
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- default/external-secrets
|
||||
|
||||
|
||||
# Disabled releases are treated as missing
|
||||
- name: disabled
|
||||
chart: incubator/raw
|
||||
namespace: kube-system
|
||||
installed: false
|
||||
|
||||
- name: test2
|
||||
chart: incubator/raw
|
||||
needs:
|
||||
- kube-system/disabled
|
||||
|
||||
- name: test3
|
||||
chart: incubator/raw
|
||||
needs:
|
||||
- test2
|
||||
`,
|
||||
"/path/to/helmfile.d/helmfile_2.yaml": `
|
||||
environments:
|
||||
test: {}
|
||||
shared: {}
|
||||
|
||||
repositories:
|
||||
- name: bitnami
|
||||
url: https://charts.bitnami.com/bitnami
|
||||
|
||||
releases:
|
||||
- name: cache
|
||||
namespace: my-app
|
||||
chart: bitnami/redis
|
||||
version: 17.0.7
|
||||
labels:
|
||||
app: test
|
||||
|
||||
- name: database
|
||||
namespace: my-app
|
||||
chart: bitnami/postgres
|
||||
version: 11.6.22
|
||||
`,
|
||||
"/path/to/helmfile.d/helmfile_3.yaml": `
|
||||
releases:
|
||||
- name: global
|
||||
chart: incubator/raw
|
||||
namespace: kube-system
|
||||
`,
|
||||
}
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: ffs.DefaultFileSystem(),
|
||||
OverrideKubeContext: "default",
|
||||
Env: tc.environment,
|
||||
Logger: logger,
|
||||
valsRuntime: valsRuntime,
|
||||
}, files)
|
||||
|
||||
expectNoCallsToHelm(app)
|
||||
|
||||
if tc.ns != "" {
|
||||
app.Namespace = tc.ns
|
||||
}
|
||||
|
||||
if tc.selectors != nil {
|
||||
app.Selectors = tc.selectors
|
||||
}
|
||||
|
||||
var listErr error
|
||||
out := testutil.CaptureStdout(func() {
|
||||
listErr = app.ListReleases(cfg)
|
||||
})
|
||||
|
||||
var gotErr string
|
||||
if listErr != nil {
|
||||
gotErr = listErr.Error()
|
||||
}
|
||||
|
||||
if d := cmp.Diff(tc.error, gotErr); d != "" {
|
||||
t.Fatalf("unexpected error: want (-), got (+): %s", d)
|
||||
}
|
||||
|
||||
assert.Equal(t, tc.expected, out)
|
||||
})
|
||||
|
||||
testhelper.RequireLog(t, "app_list_test", bs)
|
||||
}
|
||||
|
||||
t.Run("default environment includes all releases", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
environment: "default",
|
||||
expected: `NAME NAMESPACE ENABLED INSTALLED LABELS CHART VERSION
|
||||
logging kube-system true true incubator/raw
|
||||
kubernetes-external-secrets kube-system true true incubator/raw
|
||||
external-secrets default true true app:test incubator/raw
|
||||
my-release default true true app:test incubator/raw
|
||||
disabled kube-system true false incubator/raw
|
||||
test2 true true incubator/raw
|
||||
test3 true true incubator/raw
|
||||
cache my-app true true app:test bitnami/redis 17.0.7
|
||||
database my-app true true bitnami/postgres 11.6.22
|
||||
global kube-system true true incubator/raw
|
||||
`,
|
||||
}, cfg)
|
||||
})
|
||||
|
||||
t.Run("fail on unknown environment", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
environment: "staging",
|
||||
error: `err: no releases found that matches specified selector() and environment(staging), in any helmfile`,
|
||||
}, cfg)
|
||||
})
|
||||
|
||||
t.Run("list releases matching selector and environment", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
environment: "development",
|
||||
selectors: []string{"app=test"},
|
||||
expected: `NAME NAMESPACE ENABLED INSTALLED LABELS CHART VERSION
|
||||
external-secrets default true true app:test,chart:raw,name:external-secrets,namespace:default incubator/raw
|
||||
my-release default true true app:test,chart:raw,name:my-release,namespace:default incubator/raw
|
||||
`,
|
||||
}, cfg)
|
||||
})
|
||||
|
||||
t.Run("filters releases for environment used in one file only", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
environment: "test",
|
||||
expected: `NAME NAMESPACE ENABLED INSTALLED LABELS CHART VERSION
|
||||
cache my-app true true app:test bitnami/redis 17.0.7
|
||||
database my-app true true bitnami/postgres 11.6.22
|
||||
`,
|
||||
}, cfg)
|
||||
})
|
||||
|
||||
t.Run("filters releases for environment used in multiple files", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
environment: "shared",
|
||||
// 'global' release has no environments, so is still excluded
|
||||
expected: `NAME NAMESPACE ENABLED INSTALLED LABELS CHART VERSION
|
||||
logging kube-system true true incubator/raw
|
||||
kubernetes-external-secrets kube-system true true incubator/raw
|
||||
external-secrets default true true app:test incubator/raw
|
||||
my-release default true true app:test incubator/raw
|
||||
disabled kube-system true false incubator/raw
|
||||
test2 true true incubator/raw
|
||||
test3 true true incubator/raw
|
||||
cache my-app true true app:test bitnami/redis 17.0.7
|
||||
database my-app true true bitnami/postgres 11.6.22
|
||||
`,
|
||||
}, cfg)
|
||||
})
|
||||
}
|
||||
|
||||
func TestListWithEnvironment(t *testing.T) {
|
||||
t.Run("with skipCharts=false", func(t *testing.T) {
|
||||
testListWithEnvironment(t, configImpl{skipCharts: false})
|
||||
})
|
||||
t.Run("with skipCharts=true", func(t *testing.T) {
|
||||
testListWithEnvironment(t, configImpl{skipCharts: true})
|
||||
})
|
||||
}
|
||||
|
||||
func testListWithJSONOutput(t *testing.T, cfg configImpl) {
|
||||
cfg.output = "json"
|
||||
|
||||
files := map[string]string{
|
||||
"/path/to/helmfile.d/first.yaml": `
|
||||
environments:
|
||||
default:
|
||||
values:
|
||||
- myrelease2:
|
||||
enabled: false
|
||||
releases:
|
||||
- name: myrelease1
|
||||
chart: mychart1
|
||||
installed: false
|
||||
labels:
|
||||
id: myrelease1
|
||||
- name: myrelease2
|
||||
chart: mychart1
|
||||
condition: myrelease2.enabled
|
||||
`,
|
||||
"/path/to/helmfile.d/second.yaml": `
|
||||
releases:
|
||||
- name: myrelease3
|
||||
chart: mychart1
|
||||
installed: true
|
||||
- name: myrelease4
|
||||
chart: mychart1
|
||||
labels:
|
||||
id: myrelease1
|
||||
`,
|
||||
}
|
||||
stdout := os.Stdout
|
||||
defer func() { os.Stdout = stdout }()
|
||||
|
||||
var buffer bytes.Buffer
|
||||
logger := helmexec.NewLogger(&buffer, "debug")
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: ffs.DefaultFileSystem(),
|
||||
OverrideKubeContext: "default",
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
Namespace: "testNamespace",
|
||||
}, files)
|
||||
|
||||
expectNoCallsToHelm(app)
|
||||
|
||||
out := testutil.CaptureStdout(func() {
|
||||
err := app.ListReleases(cfg)
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
|
||||
expected := `[{"name":"myrelease1","namespace":"testNamespace","enabled":true,"installed":false,"labels":"id:myrelease1","chart":"mychart1","version":""},{"name":"myrelease2","namespace":"testNamespace","enabled":false,"installed":true,"labels":"","chart":"mychart1","version":""},{"name":"myrelease3","namespace":"testNamespace","enabled":true,"installed":true,"labels":"","chart":"mychart1","version":""},{"name":"myrelease4","namespace":"testNamespace","enabled":true,"installed":true,"labels":"id:myrelease1","chart":"mychart1","version":""}]
|
||||
`
|
||||
assert.Equal(t, expected, out)
|
||||
}
|
||||
|
||||
func TestListWithJSONOutput(t *testing.T) {
|
||||
t.Run("with skipCharts=false", func(t *testing.T) {
|
||||
testListWithJSONOutput(t, configImpl{skipCharts: false})
|
||||
})
|
||||
t.Run("with skipCharts=true", func(t *testing.T) {
|
||||
testListWithJSONOutput(t, configImpl{skipCharts: true})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,924 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/helmfile/vals"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/exectest"
|
||||
ffs "github.com/helmfile/helmfile/pkg/filesystem"
|
||||
"github.com/helmfile/helmfile/pkg/helmexec"
|
||||
"github.com/helmfile/helmfile/pkg/testhelper"
|
||||
)
|
||||
|
||||
func TestSync(t *testing.T) {
|
||||
type fields struct {
|
||||
skipNeeds bool
|
||||
includeNeeds bool
|
||||
includeTransitiveNeeds bool
|
||||
}
|
||||
|
||||
type testcase struct {
|
||||
fields fields
|
||||
ns string
|
||||
concurrency int
|
||||
skipDiffOnInstall bool
|
||||
error string
|
||||
files map[string]string
|
||||
selectors []string
|
||||
lists map[exectest.ListKey]string
|
||||
upgraded []exectest.Release
|
||||
deleted []exectest.Release
|
||||
log string
|
||||
}
|
||||
|
||||
check := func(t *testing.T, tc testcase) {
|
||||
t.Helper()
|
||||
|
||||
wantUpgrades := tc.upgraded
|
||||
wantDeletes := tc.deleted
|
||||
|
||||
var helm = &exectest.Helm{
|
||||
FailOnUnexpectedList: true,
|
||||
FailOnUnexpectedDiff: true,
|
||||
Lists: tc.lists,
|
||||
DiffMutex: &sync.Mutex{},
|
||||
ChartsMutex: &sync.Mutex{},
|
||||
ReleasesMutex: &sync.Mutex{},
|
||||
Helm3: true,
|
||||
}
|
||||
|
||||
bs := runWithLogCapture(t, "debug", func(t *testing.T, logger *zap.SugaredLogger) {
|
||||
t.Helper()
|
||||
|
||||
valsRuntime, err := vals.New(vals.Options{CacheSize: 32})
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error creating vals runtime: %v", err)
|
||||
}
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: ffs.DefaultFileSystem(),
|
||||
OverrideKubeContext: "default",
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
helms: map[helmKey]helmexec.Interface{
|
||||
createHelmKey("helm", "default"): helm,
|
||||
},
|
||||
valsRuntime: valsRuntime,
|
||||
}, tc.files)
|
||||
|
||||
if tc.ns != "" {
|
||||
app.Namespace = tc.ns
|
||||
}
|
||||
|
||||
if tc.selectors != nil {
|
||||
app.Selectors = tc.selectors
|
||||
}
|
||||
|
||||
syncErr := app.Sync(applyConfig{
|
||||
// if we check log output, concurrency must be 1. otherwise the test becomes non-deterministic.
|
||||
concurrency: tc.concurrency,
|
||||
logger: logger,
|
||||
skipDiffOnInstall: tc.skipDiffOnInstall,
|
||||
skipNeeds: tc.fields.skipNeeds,
|
||||
includeNeeds: tc.fields.includeNeeds,
|
||||
includeTransitiveNeeds: tc.fields.includeTransitiveNeeds,
|
||||
})
|
||||
|
||||
var gotErr string
|
||||
if syncErr != nil {
|
||||
gotErr = syncErr.Error()
|
||||
}
|
||||
|
||||
if d := cmp.Diff(tc.error, gotErr); d != "" {
|
||||
t.Fatalf("unexpected error: want (-), got (+): %s", d)
|
||||
}
|
||||
|
||||
if len(wantUpgrades) > len(helm.Releases) {
|
||||
t.Fatalf("insufficient number of upgrades: got %d, want %d", len(helm.Releases), len(wantUpgrades))
|
||||
}
|
||||
|
||||
for relIdx := range wantUpgrades {
|
||||
if wantUpgrades[relIdx].Name != helm.Releases[relIdx].Name {
|
||||
t.Errorf("releases[%d].name: got %q, want %q", relIdx, helm.Releases[relIdx].Name, wantUpgrades[relIdx].Name)
|
||||
}
|
||||
for flagIdx := range wantUpgrades[relIdx].Flags {
|
||||
if wantUpgrades[relIdx].Flags[flagIdx] != helm.Releases[relIdx].Flags[flagIdx] {
|
||||
t.Errorf("releaes[%d].flags[%d]: got %v, want %v", relIdx, flagIdx, helm.Releases[relIdx].Flags[flagIdx], wantUpgrades[relIdx].Flags[flagIdx])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(wantDeletes) > len(helm.Deleted) {
|
||||
t.Fatalf("insufficient number of deletes: got %d, want %d", len(helm.Deleted), len(wantDeletes))
|
||||
}
|
||||
|
||||
for relIdx := range wantDeletes {
|
||||
if wantDeletes[relIdx].Name != helm.Deleted[relIdx].Name {
|
||||
t.Errorf("releases[%d].name: got %q, want %q", relIdx, helm.Deleted[relIdx].Name, wantDeletes[relIdx].Name)
|
||||
}
|
||||
for flagIdx := range wantDeletes[relIdx].Flags {
|
||||
if wantDeletes[relIdx].Flags[flagIdx] != helm.Deleted[relIdx].Flags[flagIdx] {
|
||||
t.Errorf("releaes[%d].flags[%d]: got %v, want %v", relIdx, flagIdx, helm.Deleted[relIdx].Flags[flagIdx], wantDeletes[relIdx].Flags[flagIdx])
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if tc.log != "" {
|
||||
actual := bs.String()
|
||||
|
||||
diff, exists := testhelper.Diff(tc.log, actual, 3)
|
||||
if exists {
|
||||
t.Errorf("unexpected log:\nDIFF\n%s\nEOD", diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("skip-needs=true", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: true,
|
||||
},
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
{{ $mark := "a" }}
|
||||
|
||||
releases:
|
||||
- name: kubernetes-external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: kube-system
|
||||
|
||||
- name: external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- kube-system/kubernetes-external-secrets
|
||||
|
||||
- name: my-release
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- default/external-secrets
|
||||
`,
|
||||
},
|
||||
selectors: []string{"app=test"},
|
||||
upgraded: []exectest.Release{
|
||||
{Name: "external-secrets", Flags: []string{"--kube-context", "default", "--namespace", "default"}},
|
||||
{Name: "my-release", Flags: []string{"--kube-context", "default", "--namespace", "default"}},
|
||||
},
|
||||
lists: map[exectest.ListKey]string{
|
||||
{Filter: "^external-secrets$", Flags: listFlags("default", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
external-secrets 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^my-release$", Flags: listFlags("default", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
my-release 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
|
||||
`,
|
||||
},
|
||||
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
|
||||
concurrency: 1,
|
||||
log: `processing file "helmfile.yaml" in directory "."
|
||||
changing working directory to "/path/to"
|
||||
first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
|
||||
first-pass uses: &{default map[] map[]}
|
||||
first-pass rendering output of "helmfile.yaml.part.0":
|
||||
0:
|
||||
1:
|
||||
2:
|
||||
3: releases:
|
||||
4: - name: kubernetes-external-secrets
|
||||
5: chart: incubator/raw
|
||||
6: namespace: kube-system
|
||||
7:
|
||||
8: - name: external-secrets
|
||||
9: chart: incubator/raw
|
||||
10: namespace: default
|
||||
11: labels:
|
||||
12: app: test
|
||||
13: needs:
|
||||
14: - kube-system/kubernetes-external-secrets
|
||||
15:
|
||||
16: - name: my-release
|
||||
17: chart: incubator/raw
|
||||
18: namespace: default
|
||||
19: labels:
|
||||
20: app: test
|
||||
21: needs:
|
||||
22: - default/external-secrets
|
||||
23:
|
||||
|
||||
first-pass produced: &{default map[] map[]}
|
||||
first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]}
|
||||
vals:
|
||||
map[]
|
||||
defaultVals:[]
|
||||
second-pass rendering result of "helmfile.yaml.part.0":
|
||||
0:
|
||||
1:
|
||||
2:
|
||||
3: releases:
|
||||
4: - name: kubernetes-external-secrets
|
||||
5: chart: incubator/raw
|
||||
6: namespace: kube-system
|
||||
7:
|
||||
8: - name: external-secrets
|
||||
9: chart: incubator/raw
|
||||
10: namespace: default
|
||||
11: labels:
|
||||
12: app: test
|
||||
13: needs:
|
||||
14: - kube-system/kubernetes-external-secrets
|
||||
15:
|
||||
16: - name: my-release
|
||||
17: chart: incubator/raw
|
||||
18: namespace: default
|
||||
19: labels:
|
||||
20: app: test
|
||||
21: needs:
|
||||
22: - default/external-secrets
|
||||
23:
|
||||
|
||||
merged environment: &{default map[] map[]}
|
||||
2 release(s) matching app=test found in helmfile.yaml
|
||||
|
||||
Affected releases are:
|
||||
external-secrets (incubator/raw) UPDATED
|
||||
my-release (incubator/raw) UPDATED
|
||||
|
||||
processing 2 groups of releases in this order:
|
||||
GROUP RELEASES
|
||||
1 default/default/external-secrets
|
||||
2 default/default/my-release
|
||||
|
||||
processing releases in group 1/2: default/default/external-secrets
|
||||
processing releases in group 2/2: default/default/my-release
|
||||
|
||||
UPDATED RELEASES:
|
||||
NAME CHART VERSION
|
||||
external-secrets incubator/raw 3.1.0
|
||||
my-release incubator/raw 3.1.0
|
||||
|
||||
changing working directory back to "/path/to"
|
||||
`,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("skip-needs=false include-needs=true", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: false,
|
||||
includeNeeds: true,
|
||||
},
|
||||
error: ``,
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
{{ $mark := "a" }}
|
||||
|
||||
releases:
|
||||
- name: kubernetes-external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: kube-system
|
||||
|
||||
- name: external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- kube-system/kubernetes-external-secrets
|
||||
|
||||
- name: my-release
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- default/external-secrets
|
||||
`,
|
||||
},
|
||||
selectors: []string{"app=test"},
|
||||
upgraded: []exectest.Release{},
|
||||
lists: map[exectest.ListKey]string{
|
||||
{Filter: "^kubernetes-external-secrets$", Flags: listFlags("kube-system", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
kubernetes-external-secrets 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^external-secrets$", Flags: listFlags("default", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
external-secrets 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^my-release$", Flags: listFlags("default", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
my-release 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
|
||||
`,
|
||||
},
|
||||
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
|
||||
concurrency: 1,
|
||||
log: `processing file "helmfile.yaml" in directory "."
|
||||
changing working directory to "/path/to"
|
||||
first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
|
||||
first-pass uses: &{default map[] map[]}
|
||||
first-pass rendering output of "helmfile.yaml.part.0":
|
||||
0:
|
||||
1:
|
||||
2:
|
||||
3: releases:
|
||||
4: - name: kubernetes-external-secrets
|
||||
5: chart: incubator/raw
|
||||
6: namespace: kube-system
|
||||
7:
|
||||
8: - name: external-secrets
|
||||
9: chart: incubator/raw
|
||||
10: namespace: default
|
||||
11: labels:
|
||||
12: app: test
|
||||
13: needs:
|
||||
14: - kube-system/kubernetes-external-secrets
|
||||
15:
|
||||
16: - name: my-release
|
||||
17: chart: incubator/raw
|
||||
18: namespace: default
|
||||
19: labels:
|
||||
20: app: test
|
||||
21: needs:
|
||||
22: - default/external-secrets
|
||||
23:
|
||||
|
||||
first-pass produced: &{default map[] map[]}
|
||||
first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]}
|
||||
vals:
|
||||
map[]
|
||||
defaultVals:[]
|
||||
second-pass rendering result of "helmfile.yaml.part.0":
|
||||
0:
|
||||
1:
|
||||
2:
|
||||
3: releases:
|
||||
4: - name: kubernetes-external-secrets
|
||||
5: chart: incubator/raw
|
||||
6: namespace: kube-system
|
||||
7:
|
||||
8: - name: external-secrets
|
||||
9: chart: incubator/raw
|
||||
10: namespace: default
|
||||
11: labels:
|
||||
12: app: test
|
||||
13: needs:
|
||||
14: - kube-system/kubernetes-external-secrets
|
||||
15:
|
||||
16: - name: my-release
|
||||
17: chart: incubator/raw
|
||||
18: namespace: default
|
||||
19: labels:
|
||||
20: app: test
|
||||
21: needs:
|
||||
22: - default/external-secrets
|
||||
23:
|
||||
|
||||
merged environment: &{default map[] map[]}
|
||||
2 release(s) matching app=test found in helmfile.yaml
|
||||
|
||||
Affected releases are:
|
||||
external-secrets (incubator/raw) UPDATED
|
||||
kubernetes-external-secrets (incubator/raw) UPDATED
|
||||
my-release (incubator/raw) UPDATED
|
||||
|
||||
processing 3 groups of releases in this order:
|
||||
GROUP RELEASES
|
||||
1 default/kube-system/kubernetes-external-secrets
|
||||
2 default/default/external-secrets
|
||||
3 default/default/my-release
|
||||
|
||||
processing releases in group 1/3: default/kube-system/kubernetes-external-secrets
|
||||
processing releases in group 2/3: default/default/external-secrets
|
||||
processing releases in group 3/3: default/default/my-release
|
||||
|
||||
UPDATED RELEASES:
|
||||
NAME CHART VERSION
|
||||
kubernetes-external-secrets incubator/raw 3.1.0
|
||||
external-secrets incubator/raw 3.1.0
|
||||
my-release incubator/raw 3.1.0
|
||||
|
||||
changing working directory back to "/path/to"
|
||||
`,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("include-transitive-needs=true", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: false,
|
||||
includeTransitiveNeeds: true,
|
||||
},
|
||||
error: ``,
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
{{ $mark := "a" }}
|
||||
|
||||
releases:
|
||||
- name: serviceA
|
||||
chart: my/chart
|
||||
needs:
|
||||
- serviceB
|
||||
|
||||
- name: serviceB
|
||||
chart: my/chart
|
||||
needs:
|
||||
- serviceC
|
||||
|
||||
- name: serviceC
|
||||
chart: my/chart
|
||||
|
||||
- name: serviceD
|
||||
chart: my/chart
|
||||
`,
|
||||
},
|
||||
selectors: []string{"name=serviceA"},
|
||||
upgraded: []exectest.Release{},
|
||||
lists: map[exectest.ListKey]string{
|
||||
{Filter: "^serviceC$", Flags: listFlags("", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
serviceC 4 Fri Nov 1 08:40:07 2019 DEPLOYED chart-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^serviceB$", Flags: listFlags("", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
serviceB 4 Fri Nov 1 08:40:07 2019 DEPLOYED chart-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^serviceA$", Flags: listFlags("", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
serviceA 4 Fri Nov 1 08:40:07 2019 DEPLOYED chart-3.1.0 3.1.0 default
|
||||
`,
|
||||
},
|
||||
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
|
||||
concurrency: 1,
|
||||
log: `processing file "helmfile.yaml" in directory "."
|
||||
changing working directory to "/path/to"
|
||||
first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
|
||||
first-pass uses: &{default map[] map[]}
|
||||
first-pass rendering output of "helmfile.yaml.part.0":
|
||||
0:
|
||||
1:
|
||||
2:
|
||||
3: releases:
|
||||
4: - name: serviceA
|
||||
5: chart: my/chart
|
||||
6: needs:
|
||||
7: - serviceB
|
||||
8:
|
||||
9: - name: serviceB
|
||||
10: chart: my/chart
|
||||
11: needs:
|
||||
12: - serviceC
|
||||
13:
|
||||
14: - name: serviceC
|
||||
15: chart: my/chart
|
||||
16:
|
||||
17: - name: serviceD
|
||||
18: chart: my/chart
|
||||
19:
|
||||
|
||||
first-pass produced: &{default map[] map[]}
|
||||
first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]}
|
||||
vals:
|
||||
map[]
|
||||
defaultVals:[]
|
||||
second-pass rendering result of "helmfile.yaml.part.0":
|
||||
0:
|
||||
1:
|
||||
2:
|
||||
3: releases:
|
||||
4: - name: serviceA
|
||||
5: chart: my/chart
|
||||
6: needs:
|
||||
7: - serviceB
|
||||
8:
|
||||
9: - name: serviceB
|
||||
10: chart: my/chart
|
||||
11: needs:
|
||||
12: - serviceC
|
||||
13:
|
||||
14: - name: serviceC
|
||||
15: chart: my/chart
|
||||
16:
|
||||
17: - name: serviceD
|
||||
18: chart: my/chart
|
||||
19:
|
||||
|
||||
merged environment: &{default map[] map[]}
|
||||
3 release(s) matching name=serviceA found in helmfile.yaml
|
||||
|
||||
Affected releases are:
|
||||
serviceA (my/chart) UPDATED
|
||||
serviceB (my/chart) UPDATED
|
||||
serviceC (my/chart) UPDATED
|
||||
|
||||
processing 3 groups of releases in this order:
|
||||
GROUP RELEASES
|
||||
1 default//serviceC
|
||||
2 default//serviceB
|
||||
3 default//serviceA
|
||||
|
||||
processing releases in group 1/3: default//serviceC
|
||||
processing releases in group 2/3: default//serviceB
|
||||
processing releases in group 3/3: default//serviceA
|
||||
|
||||
UPDATED RELEASES:
|
||||
NAME CHART VERSION
|
||||
serviceC my/chart 3.1.0
|
||||
serviceB my/chart 3.1.0
|
||||
serviceA my/chart 3.1.0
|
||||
|
||||
changing working directory back to "/path/to"
|
||||
`,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("skip-needs=false include-needs=true with installed but disabled release", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: false,
|
||||
includeNeeds: true,
|
||||
includeTransitiveNeeds: false,
|
||||
},
|
||||
error: ``,
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
{{ $mark := "a" }}
|
||||
|
||||
releases:
|
||||
- name: kubernetes-external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: kube-system
|
||||
installed: false
|
||||
|
||||
- name: external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- kube-system/kubernetes-external-secrets
|
||||
|
||||
- name: my-release
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- default/external-secrets
|
||||
`,
|
||||
},
|
||||
selectors: []string{"app=test"},
|
||||
upgraded: []exectest.Release{},
|
||||
lists: map[exectest.ListKey]string{
|
||||
{Filter: "^kubernetes-external-secrets$", Flags: listFlags("kube-system", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
kubernetes-external-secrets 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^external-secrets$", Flags: listFlags("default", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
external-secrets 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^my-release$", Flags: listFlags("default", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
my-release 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
|
||||
`,
|
||||
},
|
||||
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
|
||||
concurrency: 1,
|
||||
log: `processing file "helmfile.yaml" in directory "."
|
||||
changing working directory to "/path/to"
|
||||
first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
|
||||
first-pass uses: &{default map[] map[]}
|
||||
first-pass rendering output of "helmfile.yaml.part.0":
|
||||
0:
|
||||
1:
|
||||
2:
|
||||
3: releases:
|
||||
4: - name: kubernetes-external-secrets
|
||||
5: chart: incubator/raw
|
||||
6: namespace: kube-system
|
||||
7: installed: false
|
||||
8:
|
||||
9: - name: external-secrets
|
||||
10: chart: incubator/raw
|
||||
11: namespace: default
|
||||
12: labels:
|
||||
13: app: test
|
||||
14: needs:
|
||||
15: - kube-system/kubernetes-external-secrets
|
||||
16:
|
||||
17: - name: my-release
|
||||
18: chart: incubator/raw
|
||||
19: namespace: default
|
||||
20: labels:
|
||||
21: app: test
|
||||
22: needs:
|
||||
23: - default/external-secrets
|
||||
24:
|
||||
|
||||
first-pass produced: &{default map[] map[]}
|
||||
first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]}
|
||||
vals:
|
||||
map[]
|
||||
defaultVals:[]
|
||||
second-pass rendering result of "helmfile.yaml.part.0":
|
||||
0:
|
||||
1:
|
||||
2:
|
||||
3: releases:
|
||||
4: - name: kubernetes-external-secrets
|
||||
5: chart: incubator/raw
|
||||
6: namespace: kube-system
|
||||
7: installed: false
|
||||
8:
|
||||
9: - name: external-secrets
|
||||
10: chart: incubator/raw
|
||||
11: namespace: default
|
||||
12: labels:
|
||||
13: app: test
|
||||
14: needs:
|
||||
15: - kube-system/kubernetes-external-secrets
|
||||
16:
|
||||
17: - name: my-release
|
||||
18: chart: incubator/raw
|
||||
19: namespace: default
|
||||
20: labels:
|
||||
21: app: test
|
||||
22: needs:
|
||||
23: - default/external-secrets
|
||||
24:
|
||||
|
||||
merged environment: &{default map[] map[]}
|
||||
2 release(s) matching app=test found in helmfile.yaml
|
||||
|
||||
Affected releases are:
|
||||
external-secrets (incubator/raw) UPDATED
|
||||
kubernetes-external-secrets (incubator/raw) DELETED
|
||||
my-release (incubator/raw) UPDATED
|
||||
|
||||
processing 1 groups of releases in this order:
|
||||
GROUP RELEASES
|
||||
1 default/kube-system/kubernetes-external-secrets
|
||||
|
||||
processing releases in group 1/1: default/kube-system/kubernetes-external-secrets
|
||||
processing 2 groups of releases in this order:
|
||||
GROUP RELEASES
|
||||
1 default/default/external-secrets
|
||||
2 default/default/my-release
|
||||
|
||||
processing releases in group 1/2: default/default/external-secrets
|
||||
processing releases in group 2/2: default/default/my-release
|
||||
|
||||
UPDATED RELEASES:
|
||||
NAME CHART VERSION
|
||||
external-secrets incubator/raw 3.1.0
|
||||
my-release incubator/raw 3.1.0
|
||||
|
||||
|
||||
DELETED RELEASES:
|
||||
NAME
|
||||
kubernetes-external-secrets
|
||||
changing working directory back to "/path/to"
|
||||
`,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("skip-needs=false include-needs=true with not installed and disabled release", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: false,
|
||||
includeTransitiveNeeds: false,
|
||||
includeNeeds: true,
|
||||
},
|
||||
error: ``,
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
{{ $mark := "a" }}
|
||||
|
||||
releases:
|
||||
- name: kubernetes-external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: kube-system
|
||||
installed: false
|
||||
|
||||
- name: external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- kube-system/kubernetes-external-secrets
|
||||
|
||||
- name: my-release
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- default/external-secrets
|
||||
`,
|
||||
},
|
||||
selectors: []string{"app=test"},
|
||||
upgraded: []exectest.Release{},
|
||||
lists: map[exectest.ListKey]string{
|
||||
// delete frontend-v1 and backend-v1
|
||||
{Filter: "^kubernetes-external-secrets$", Flags: listFlags("kube-system", "default")}: ``,
|
||||
{Filter: "^external-secrets$", Flags: listFlags("default", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
external-secrets 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^my-release$", Flags: listFlags("default", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
my-release 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
|
||||
`,
|
||||
},
|
||||
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
|
||||
concurrency: 1,
|
||||
log: `processing file "helmfile.yaml" in directory "."
|
||||
changing working directory to "/path/to"
|
||||
first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
|
||||
first-pass uses: &{default map[] map[]}
|
||||
first-pass rendering output of "helmfile.yaml.part.0":
|
||||
0:
|
||||
1:
|
||||
2:
|
||||
3: releases:
|
||||
4: - name: kubernetes-external-secrets
|
||||
5: chart: incubator/raw
|
||||
6: namespace: kube-system
|
||||
7: installed: false
|
||||
8:
|
||||
9: - name: external-secrets
|
||||
10: chart: incubator/raw
|
||||
11: namespace: default
|
||||
12: labels:
|
||||
13: app: test
|
||||
14: needs:
|
||||
15: - kube-system/kubernetes-external-secrets
|
||||
16:
|
||||
17: - name: my-release
|
||||
18: chart: incubator/raw
|
||||
19: namespace: default
|
||||
20: labels:
|
||||
21: app: test
|
||||
22: needs:
|
||||
23: - default/external-secrets
|
||||
24:
|
||||
|
||||
first-pass produced: &{default map[] map[]}
|
||||
first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]}
|
||||
vals:
|
||||
map[]
|
||||
defaultVals:[]
|
||||
second-pass rendering result of "helmfile.yaml.part.0":
|
||||
0:
|
||||
1:
|
||||
2:
|
||||
3: releases:
|
||||
4: - name: kubernetes-external-secrets
|
||||
5: chart: incubator/raw
|
||||
6: namespace: kube-system
|
||||
7: installed: false
|
||||
8:
|
||||
9: - name: external-secrets
|
||||
10: chart: incubator/raw
|
||||
11: namespace: default
|
||||
12: labels:
|
||||
13: app: test
|
||||
14: needs:
|
||||
15: - kube-system/kubernetes-external-secrets
|
||||
16:
|
||||
17: - name: my-release
|
||||
18: chart: incubator/raw
|
||||
19: namespace: default
|
||||
20: labels:
|
||||
21: app: test
|
||||
22: needs:
|
||||
23: - default/external-secrets
|
||||
24:
|
||||
|
||||
merged environment: &{default map[] map[]}
|
||||
2 release(s) matching app=test found in helmfile.yaml
|
||||
|
||||
Affected releases are:
|
||||
external-secrets (incubator/raw) UPDATED
|
||||
my-release (incubator/raw) UPDATED
|
||||
|
||||
processing 2 groups of releases in this order:
|
||||
GROUP RELEASES
|
||||
1 default/default/external-secrets
|
||||
2 default/default/my-release
|
||||
|
||||
processing releases in group 1/2: default/default/external-secrets
|
||||
processing releases in group 2/2: default/default/my-release
|
||||
|
||||
UPDATED RELEASES:
|
||||
NAME CHART VERSION
|
||||
external-secrets incubator/raw 3.1.0
|
||||
my-release incubator/raw 3.1.0
|
||||
|
||||
changing working directory back to "/path/to"
|
||||
`,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("bad --selector", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
{{ $mark := "a" }}
|
||||
|
||||
releases:
|
||||
- name: kubernetes-external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: kube-system
|
||||
|
||||
- name: external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- kube-system/kubernetes-external-secrets
|
||||
|
||||
- name: my-release
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- default/external-secrets
|
||||
`,
|
||||
},
|
||||
selectors: []string{"app=test_non_existent"},
|
||||
upgraded: []exectest.Release{},
|
||||
error: "err: no releases found that matches specified selector(app=test_non_existent) and environment(default), in any helmfile",
|
||||
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
|
||||
concurrency: 1,
|
||||
log: `processing file "helmfile.yaml" in directory "."
|
||||
changing working directory to "/path/to"
|
||||
first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
|
||||
first-pass uses: &{default map[] map[]}
|
||||
first-pass rendering output of "helmfile.yaml.part.0":
|
||||
0:
|
||||
1:
|
||||
2:
|
||||
3: releases:
|
||||
4: - name: kubernetes-external-secrets
|
||||
5: chart: incubator/raw
|
||||
6: namespace: kube-system
|
||||
7:
|
||||
8: - name: external-secrets
|
||||
9: chart: incubator/raw
|
||||
10: namespace: default
|
||||
11: labels:
|
||||
12: app: test
|
||||
13: needs:
|
||||
14: - kube-system/kubernetes-external-secrets
|
||||
15:
|
||||
16: - name: my-release
|
||||
17: chart: incubator/raw
|
||||
18: namespace: default
|
||||
19: labels:
|
||||
20: app: test
|
||||
21: needs:
|
||||
22: - default/external-secrets
|
||||
23:
|
||||
|
||||
first-pass produced: &{default map[] map[]}
|
||||
first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]}
|
||||
vals:
|
||||
map[]
|
||||
defaultVals:[]
|
||||
second-pass rendering result of "helmfile.yaml.part.0":
|
||||
0:
|
||||
1:
|
||||
2:
|
||||
3: releases:
|
||||
4: - name: kubernetes-external-secrets
|
||||
5: chart: incubator/raw
|
||||
6: namespace: kube-system
|
||||
7:
|
||||
8: - name: external-secrets
|
||||
9: chart: incubator/raw
|
||||
10: namespace: default
|
||||
11: labels:
|
||||
12: app: test
|
||||
13: needs:
|
||||
14: - kube-system/kubernetes-external-secrets
|
||||
15:
|
||||
16: - name: my-release
|
||||
17: chart: incubator/raw
|
||||
18: namespace: default
|
||||
19: labels:
|
||||
20: app: test
|
||||
21: needs:
|
||||
22: - default/external-secrets
|
||||
23:
|
||||
|
||||
merged environment: &{default map[] map[]}
|
||||
0 release(s) matching app=test_non_existent found in helmfile.yaml
|
||||
|
||||
changing working directory back to "/path/to"
|
||||
`,
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,497 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/helmfile/vals"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/exectest"
|
||||
ffs "github.com/helmfile/helmfile/pkg/filesystem"
|
||||
"github.com/helmfile/helmfile/pkg/helmexec"
|
||||
"github.com/helmfile/helmfile/pkg/runtime"
|
||||
"github.com/helmfile/helmfile/pkg/testhelper"
|
||||
)
|
||||
|
||||
func TestTemplate(t *testing.T) {
|
||||
type fields struct {
|
||||
skipNeeds bool
|
||||
includeNeeds bool
|
||||
includeTransitiveNeeds bool
|
||||
}
|
||||
|
||||
type testcase struct {
|
||||
fields fields
|
||||
ns string
|
||||
error string
|
||||
selectors []string
|
||||
templated []exectest.Release
|
||||
}
|
||||
|
||||
check := func(t *testing.T, tc testcase) {
|
||||
t.Helper()
|
||||
|
||||
wantTemplates := tc.templated
|
||||
|
||||
var helm = &exectest.Helm{
|
||||
FailOnUnexpectedList: true,
|
||||
FailOnUnexpectedDiff: true,
|
||||
DiffMutex: &sync.Mutex{},
|
||||
ChartsMutex: &sync.Mutex{},
|
||||
ReleasesMutex: &sync.Mutex{},
|
||||
Helm3: true,
|
||||
}
|
||||
|
||||
bs := runWithLogCapture(t, "debug", func(t *testing.T, logger *zap.SugaredLogger) {
|
||||
t.Helper()
|
||||
|
||||
valsRuntime, err := vals.New(vals.Options{CacheSize: 32})
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error creating vals runtime: %v", err)
|
||||
}
|
||||
|
||||
files := map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
releases:
|
||||
- name: logging
|
||||
chart: incubator/raw
|
||||
namespace: kube-system
|
||||
|
||||
- name: kubernetes-external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: kube-system
|
||||
needs:
|
||||
- kube-system/logging
|
||||
|
||||
- name: external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- kube-system/kubernetes-external-secrets
|
||||
|
||||
- name: my-release
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- default/external-secrets
|
||||
|
||||
|
||||
# Disabled releases are treated as missing
|
||||
- name: disabled
|
||||
chart: incubator/raw
|
||||
namespace: kube-system
|
||||
installed: false
|
||||
|
||||
- name: test2
|
||||
chart: incubator/raw
|
||||
needs:
|
||||
- kube-system/disabled
|
||||
|
||||
- name: test3
|
||||
chart: incubator/raw
|
||||
needs:
|
||||
- test2
|
||||
`,
|
||||
}
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: &ffs.FileSystem{Glob: filepath.Glob},
|
||||
OverrideKubeContext: "default",
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
helms: map[helmKey]helmexec.Interface{
|
||||
createHelmKey("helm", "default"): helm,
|
||||
},
|
||||
valsRuntime: valsRuntime,
|
||||
}, files)
|
||||
|
||||
if tc.ns != "" {
|
||||
app.Namespace = tc.ns
|
||||
}
|
||||
|
||||
if tc.selectors != nil {
|
||||
app.Selectors = tc.selectors
|
||||
}
|
||||
|
||||
tmplErr := app.Template(applyConfig{
|
||||
// if we check log output, concurrency must be 1. otherwise the test becomes non-deterministic.
|
||||
concurrency: 1,
|
||||
logger: logger,
|
||||
skipNeeds: tc.fields.skipNeeds,
|
||||
includeNeeds: tc.fields.includeNeeds,
|
||||
includeTransitiveNeeds: tc.fields.includeTransitiveNeeds,
|
||||
})
|
||||
|
||||
var gotErr string
|
||||
if tmplErr != nil {
|
||||
gotErr = tmplErr.Error()
|
||||
}
|
||||
|
||||
if d := cmp.Diff(tc.error, gotErr); d != "" {
|
||||
t.Fatalf("unexpected error: want (-), got (+): %s", d)
|
||||
}
|
||||
|
||||
require.Equal(t, wantTemplates, helm.Templated)
|
||||
})
|
||||
|
||||
testNameComponents := strings.Split(t.Name(), "/")
|
||||
testBaseName := strings.ToLower(
|
||||
strings.ReplaceAll(
|
||||
testNameComponents[len(testNameComponents)-1],
|
||||
" ",
|
||||
"_",
|
||||
),
|
||||
)
|
||||
wantLogFileDir := filepath.Join("testdata", "app_template_test")
|
||||
wantLogFile := filepath.Join(wantLogFileDir, testBaseName)
|
||||
wantLogData, err := os.ReadFile(wantLogFile)
|
||||
updateLogFile := err != nil
|
||||
wantLog := string(wantLogData)
|
||||
gotLog := bs.String()
|
||||
if updateLogFile {
|
||||
if err := os.MkdirAll(wantLogFileDir, 0755); err != nil {
|
||||
t.Fatalf("unable to create directory %q: %v", wantLogFileDir, err)
|
||||
}
|
||||
if err := os.WriteFile(wantLogFile, bs.Bytes(), 0644); err != nil {
|
||||
t.Fatalf("unable to update lint log snapshot: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
diff, exists := testhelper.Diff(wantLog, gotLog, 3)
|
||||
if exists {
|
||||
t.Errorf("unexpected log:\nDIFF\n%s\nEOD", diff)
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("fail on unselected need by default", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
selectors: []string{"app=test"},
|
||||
error: `in ./helmfile.yaml: release "default/default/external-secrets" depends on "default/kube-system/kubernetes-external-secrets" which does not match the selectors. Please add a selector like "--selector name=kubernetes-external-secrets", or indicate whether to skip (--skip-needs) or include (--include-needs) these dependencies`,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("skip-needs", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: true,
|
||||
},
|
||||
selectors: []string{"app=test"},
|
||||
templated: []exectest.Release{
|
||||
{Name: "external-secrets", Flags: []string{"--namespace", "default"}},
|
||||
{Name: "my-release", Flags: []string{"--namespace", "default"}},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("include-needs", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: false,
|
||||
includeNeeds: true,
|
||||
},
|
||||
error: ``,
|
||||
selectors: []string{"app=test"},
|
||||
templated: []exectest.Release{
|
||||
// TODO: Turned out we can't differentiate needs vs transitive needs in this case :thinking:
|
||||
{Name: "logging", Flags: []string{"--namespace", "kube-system"}},
|
||||
{Name: "kubernetes-external-secrets", Flags: []string{"--namespace", "kube-system"}},
|
||||
{Name: "external-secrets", Flags: []string{"--namespace", "default"}},
|
||||
{Name: "my-release", Flags: []string{"--namespace", "default"}},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("include-transitive-needs", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: false,
|
||||
includeTransitiveNeeds: true,
|
||||
},
|
||||
error: ``,
|
||||
selectors: []string{"app=test"},
|
||||
templated: []exectest.Release{
|
||||
{Name: "logging", Flags: []string{"--namespace", "kube-system"}},
|
||||
{Name: "kubernetes-external-secrets", Flags: []string{"--namespace", "kube-system"}},
|
||||
{Name: "external-secrets", Flags: []string{"--namespace", "default"}},
|
||||
{Name: "my-release", Flags: []string{"--namespace", "default"}},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("include-needs should not fail on disabled direct need", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: false,
|
||||
includeNeeds: true,
|
||||
},
|
||||
selectors: []string{"name=test2"},
|
||||
templated: []exectest.Release{
|
||||
{Name: "test2", Flags: []string{}},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("include-needs should not fail on disabled transitive need", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: false,
|
||||
includeNeeds: true,
|
||||
},
|
||||
selectors: []string{"name=test3"},
|
||||
templated: []exectest.Release{
|
||||
{Name: "test2", Flags: []string{}},
|
||||
{Name: "test3", Flags: []string{}},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("include-transitive-needs should not fail on disabled transitive need", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: false,
|
||||
includeNeeds: false,
|
||||
includeTransitiveNeeds: true,
|
||||
},
|
||||
selectors: []string{"name=test3"},
|
||||
templated: []exectest.Release{
|
||||
{Name: "test2", Flags: []string{}},
|
||||
{Name: "test3", Flags: []string{}},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("include-needs with include-transitive-needs should not fail on disabled direct need", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: false,
|
||||
includeNeeds: true,
|
||||
includeTransitiveNeeds: true,
|
||||
},
|
||||
selectors: []string{"name=test2"},
|
||||
templated: []exectest.Release{
|
||||
{Name: "test2", Flags: []string{}},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("include-needs with include-transitive-needs should not fail on disabled transitive need", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: false,
|
||||
includeNeeds: true,
|
||||
includeTransitiveNeeds: true,
|
||||
},
|
||||
selectors: []string{"name=test3"},
|
||||
templated: []exectest.Release{
|
||||
{Name: "test2", Flags: []string{}},
|
||||
{Name: "test3", Flags: []string{}},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("bad selector", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
selectors: []string{"app=test_non_existent"},
|
||||
templated: nil,
|
||||
error: "err: no releases found that matches specified selector(app=test_non_existent) and environment(default), in any helmfile",
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestTemplate_StrictParsing(t *testing.T) {
|
||||
type testcase struct {
|
||||
goccyGoYaml bool
|
||||
ns string
|
||||
error string
|
||||
}
|
||||
|
||||
check := func(t *testing.T, tc testcase) {
|
||||
t.Helper()
|
||||
|
||||
v := runtime.GoccyGoYaml
|
||||
runtime.GoccyGoYaml = tc.goccyGoYaml
|
||||
t.Cleanup(func() {
|
||||
runtime.GoccyGoYaml = v
|
||||
})
|
||||
|
||||
var helm = &exectest.Helm{
|
||||
FailOnUnexpectedList: true,
|
||||
FailOnUnexpectedDiff: true,
|
||||
DiffMutex: &sync.Mutex{},
|
||||
ChartsMutex: &sync.Mutex{},
|
||||
ReleasesMutex: &sync.Mutex{},
|
||||
Helm3: true,
|
||||
}
|
||||
|
||||
_ = runWithLogCapture(t, "debug", func(t *testing.T, logger *zap.SugaredLogger) {
|
||||
t.Helper()
|
||||
|
||||
valsRuntime, err := vals.New(vals.Options{CacheSize: 32})
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error creating vals runtime: %v", err)
|
||||
}
|
||||
|
||||
files := map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
releases:
|
||||
- name: app1
|
||||
foobar: FOOBAR
|
||||
chart: incubator/raw
|
||||
`,
|
||||
}
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: &ffs.FileSystem{Glob: filepath.Glob},
|
||||
OverrideKubeContext: "default",
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
helms: map[helmKey]helmexec.Interface{
|
||||
createHelmKey("helm", "default"): helm,
|
||||
},
|
||||
valsRuntime: valsRuntime,
|
||||
}, files)
|
||||
|
||||
if tc.ns != "" {
|
||||
app.Namespace = tc.ns
|
||||
}
|
||||
|
||||
tmplErr := app.Template(applyConfig{
|
||||
// if we check log output, concurrency must be 1. otherwise the test becomes non-deterministic.
|
||||
concurrency: 1,
|
||||
logger: logger,
|
||||
})
|
||||
|
||||
var gotErr string
|
||||
if tmplErr != nil {
|
||||
gotErr = tmplErr.Error()
|
||||
}
|
||||
|
||||
if d := cmp.Diff(tc.error, gotErr); d != "" {
|
||||
t.Fatalf("unexpected error: want (-), got (+): %s", d)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("fail due to unknown field with goccy/go-yaml", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
goccyGoYaml: true,
|
||||
error: `in ./helmfile.yaml: failed to read helmfile.yaml: reading document at index 1: [4:3] unknown field "foobar"
|
||||
2 | releases:
|
||||
3 | - name: app1
|
||||
> 4 | foobar: FOOBAR
|
||||
^
|
||||
5 | chart: incubator/raw`,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("fail due to unknown field with gopkg.in/yaml.v2", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
goccyGoYaml: false,
|
||||
error: `in ./helmfile.yaml: failed to read helmfile.yaml: reading document at index 1: yaml: unmarshal errors:
|
||||
line 4: field foobar not found in type state.ReleaseSpec`,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestTemplate_CyclicInheritance(t *testing.T) {
|
||||
type testcase struct {
|
||||
ns string
|
||||
error string
|
||||
}
|
||||
|
||||
check := func(t *testing.T, tc testcase) {
|
||||
t.Helper()
|
||||
|
||||
var helm = &exectest.Helm{
|
||||
FailOnUnexpectedList: true,
|
||||
FailOnUnexpectedDiff: true,
|
||||
DiffMutex: &sync.Mutex{},
|
||||
ChartsMutex: &sync.Mutex{},
|
||||
ReleasesMutex: &sync.Mutex{},
|
||||
}
|
||||
|
||||
_ = runWithLogCapture(t, "debug", func(t *testing.T, logger *zap.SugaredLogger) {
|
||||
t.Helper()
|
||||
|
||||
valsRuntime, err := vals.New(vals.Options{CacheSize: 32})
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error creating vals runtime: %v", err)
|
||||
}
|
||||
|
||||
files := map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
templates:
|
||||
a:
|
||||
inherit:
|
||||
- template: b
|
||||
values:
|
||||
- a.yaml
|
||||
b:
|
||||
inherit:
|
||||
- template: c
|
||||
values:
|
||||
- b.yaml
|
||||
c:
|
||||
inherit:
|
||||
- template: a
|
||||
values:
|
||||
- c.yaml
|
||||
releases:
|
||||
- name: app1
|
||||
inherit:
|
||||
- template: a
|
||||
chart: incubator/raw
|
||||
`,
|
||||
}
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: &ffs.FileSystem{Glob: filepath.Glob},
|
||||
OverrideKubeContext: "default",
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
helms: map[helmKey]helmexec.Interface{
|
||||
createHelmKey("helm", "default"): helm,
|
||||
},
|
||||
valsRuntime: valsRuntime,
|
||||
}, files)
|
||||
|
||||
if tc.ns != "" {
|
||||
app.Namespace = tc.ns
|
||||
}
|
||||
|
||||
tmplErr := app.Template(applyConfig{
|
||||
// if we check log output, concurrency must be 1. otherwise the test becomes non-deterministic.
|
||||
concurrency: 1,
|
||||
logger: logger,
|
||||
})
|
||||
|
||||
var gotErr string
|
||||
if tmplErr != nil {
|
||||
gotErr = tmplErr.Error()
|
||||
}
|
||||
|
||||
if d := cmp.Diff(tc.error, gotErr); d != "" {
|
||||
t.Fatalf("unexpected error: want (-), got (+): %s", d)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("fail due to cyclic inheritance", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
error: `in ./helmfile.yaml: failed executing release templates in "helmfile.yaml": unable to load release "app1" with template: cyclic inheritance detected: a->b->c->a`,
|
||||
})
|
||||
})
|
||||
}
|
||||
+697
-1104
File diff suppressed because it is too large
Load Diff
@@ -5,10 +5,12 @@ import "go.uber.org/zap"
|
||||
type ConfigProvider interface {
|
||||
Args() string
|
||||
HelmBinary() string
|
||||
EnableLiveOutput() bool
|
||||
|
||||
FileOrDir() string
|
||||
KubeContext() string
|
||||
Namespace() string
|
||||
Chart() string
|
||||
Selectors() []string
|
||||
StateValuesSet() map[string]interface{}
|
||||
StateValuesFiles() []string
|
||||
@@ -17,82 +19,131 @@ type ConfigProvider interface {
|
||||
loggingConfig
|
||||
}
|
||||
|
||||
// TODO: Remove this function once Helmfile v0.x
|
||||
type DeprecatedChartsConfigProvider interface {
|
||||
Values() []string
|
||||
|
||||
concurrencyConfig
|
||||
loggingConfig
|
||||
IncludeTransitiveNeeds() bool
|
||||
}
|
||||
|
||||
type DepsConfigProvider interface {
|
||||
Args() string
|
||||
SkipRepos() bool
|
||||
IncludeTransitiveNeeds() bool
|
||||
|
||||
concurrencyConfig
|
||||
}
|
||||
|
||||
type ReposConfigProvider interface {
|
||||
Args() string
|
||||
IncludeTransitiveNeeds() bool
|
||||
}
|
||||
|
||||
type ApplyConfigProvider interface {
|
||||
Args() string
|
||||
PostRenderer() string
|
||||
|
||||
Values() []string
|
||||
Set() []string
|
||||
SkipCRDs() bool
|
||||
SkipDeps() bool
|
||||
Wait() bool
|
||||
WaitForJobs() bool
|
||||
|
||||
IncludeTests() bool
|
||||
|
||||
Suppress() []string
|
||||
SuppressSecrets() bool
|
||||
ShowSecrets() bool
|
||||
NoHooks() bool
|
||||
SuppressDiff() bool
|
||||
|
||||
DetailedExitcode() bool
|
||||
|
||||
Color() bool
|
||||
NoColor() bool
|
||||
Context() int
|
||||
DiffOutput() string
|
||||
|
||||
// TODO: Remove this function once Helmfile v0.x
|
||||
RetainValuesFiles() bool
|
||||
|
||||
Validate() bool
|
||||
SkipCleanup() bool
|
||||
SkipDiffOnInstall() bool
|
||||
|
||||
DAGConfig
|
||||
|
||||
concurrencyConfig
|
||||
interactive
|
||||
loggingConfig
|
||||
valuesControlMode
|
||||
}
|
||||
|
||||
type SyncConfigProvider interface {
|
||||
Args() string
|
||||
PostRenderer() string
|
||||
|
||||
Values() []string
|
||||
Set() []string
|
||||
SkipCRDs() bool
|
||||
SkipDeps() bool
|
||||
Wait() bool
|
||||
WaitForJobs() bool
|
||||
|
||||
Validate() bool
|
||||
|
||||
SkipNeeds() bool
|
||||
IncludeNeeds() bool
|
||||
IncludeTransitiveNeeds() bool
|
||||
DAGConfig
|
||||
|
||||
concurrencyConfig
|
||||
interactive
|
||||
loggingConfig
|
||||
valuesControlMode
|
||||
}
|
||||
|
||||
type DiffConfigProvider interface {
|
||||
Args() string
|
||||
PostRenderer() string
|
||||
|
||||
Values() []string
|
||||
Set() []string
|
||||
Validate() bool
|
||||
SkipCRDs() bool
|
||||
SkipDeps() bool
|
||||
|
||||
IncludeTests() bool
|
||||
|
||||
Suppress() []string
|
||||
SuppressSecrets() bool
|
||||
ShowSecrets() bool
|
||||
NoHooks() bool
|
||||
SuppressDiff() bool
|
||||
SkipDiffOnInstall() bool
|
||||
|
||||
DAGConfig
|
||||
|
||||
DetailedExitcode() bool
|
||||
Color() bool
|
||||
NoColor() bool
|
||||
Context() int
|
||||
DiffOutput() string
|
||||
|
||||
concurrencyConfig
|
||||
valuesControlMode
|
||||
}
|
||||
|
||||
// TODO: Remove this function once Helmfile v0.x
|
||||
type DeleteConfigProvider interface {
|
||||
Args() string
|
||||
|
||||
Purge() bool
|
||||
SkipDeps() bool
|
||||
SkipCharts() bool
|
||||
|
||||
interactive
|
||||
loggingConfig
|
||||
@@ -102,6 +153,9 @@ type DeleteConfigProvider interface {
|
||||
type DestroyConfigProvider interface {
|
||||
Args() string
|
||||
|
||||
SkipDeps() bool
|
||||
SkipCharts() bool
|
||||
|
||||
interactive
|
||||
loggingConfig
|
||||
concurrencyConfig
|
||||
@@ -110,6 +164,7 @@ type DestroyConfigProvider interface {
|
||||
type TestConfigProvider interface {
|
||||
Args() string
|
||||
|
||||
SkipDeps() bool
|
||||
Timeout() int
|
||||
Cleanup() bool
|
||||
Logs() bool
|
||||
@@ -123,12 +178,24 @@ type LintConfigProvider interface {
|
||||
Values() []string
|
||||
Set() []string
|
||||
SkipDeps() bool
|
||||
SkipCleanup() bool
|
||||
|
||||
DAGConfig
|
||||
|
||||
concurrencyConfig
|
||||
}
|
||||
|
||||
type FetchConfigProvider interface {
|
||||
SkipDeps() bool
|
||||
OutputDir() string
|
||||
OutputDirTemplate() string
|
||||
|
||||
concurrencyConfig
|
||||
}
|
||||
|
||||
type TemplateConfigProvider interface {
|
||||
Args() string
|
||||
PostRenderer() string
|
||||
|
||||
Values() []string
|
||||
Set() []string
|
||||
@@ -136,17 +203,30 @@ type TemplateConfigProvider interface {
|
||||
Validate() bool
|
||||
SkipDeps() bool
|
||||
SkipCleanup() bool
|
||||
SkipTests() bool
|
||||
OutputDir() string
|
||||
IncludeCRDs() bool
|
||||
|
||||
DAGConfig
|
||||
|
||||
concurrencyConfig
|
||||
}
|
||||
|
||||
type DAGConfig interface {
|
||||
SkipNeeds() bool
|
||||
IncludeNeeds() bool
|
||||
IncludeTransitiveNeeds() bool
|
||||
}
|
||||
|
||||
type WriteValuesConfigProvider interface {
|
||||
Values() []string
|
||||
Set() []string
|
||||
OutputFileTemplate() string
|
||||
SkipDeps() bool
|
||||
SkipCleanup() bool
|
||||
IncludeTransitiveNeeds() bool
|
||||
|
||||
concurrencyConfig
|
||||
}
|
||||
|
||||
type StatusesConfigProvider interface {
|
||||
@@ -173,4 +253,17 @@ type interactive interface {
|
||||
|
||||
type ListConfigProvider interface {
|
||||
Output() string
|
||||
SkipCharts() bool
|
||||
}
|
||||
|
||||
type CacheConfigProvider interface{}
|
||||
|
||||
type InitConfigProvider interface {
|
||||
Force() bool
|
||||
}
|
||||
|
||||
// reset/reuse values helm cli flags handling for apply/sync/diff
|
||||
type valuesControlMode interface {
|
||||
ReuseValues() bool
|
||||
ResetValues() bool
|
||||
}
|
||||
|
||||
@@ -3,20 +3,23 @@ package app
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/envvar"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultHelmfile = "helmfile.yaml"
|
||||
DeprecatedHelmfile = "charts.yaml"
|
||||
DefaultHelmfile = "helmfile.yaml"
|
||||
// TODO: Remove this function once Helmfile v0.x
|
||||
DeprecatedHelmfile = "charts.yaml"
|
||||
|
||||
DefaultHelmfileDirectory = "helmfile.d"
|
||||
ExperimentalEnvVar = "HELMFILE_EXPERIMENTAL" // environment variable for experimental features, expecting "true" lower case
|
||||
ExperimentalSelectorExplicit = "explicit-selector-inheritance" // value to remove default selector inheritance to sub-helmfiles and use the explicit one
|
||||
)
|
||||
|
||||
func experimentalModeEnabled() bool {
|
||||
return os.Getenv(ExperimentalEnvVar) == "true"
|
||||
return os.Getenv(envvar.Experimental) == "true"
|
||||
}
|
||||
|
||||
func isExplicitSelectorInheritanceEnabled() bool {
|
||||
return experimentalModeEnabled() || strings.Contains(os.Getenv(ExperimentalEnvVar), ExperimentalSelectorExplicit)
|
||||
return experimentalModeEnabled() || strings.Contains(os.Getenv(envvar.Experimental), ExperimentalSelectorExplicit)
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user