mirror of
https://github.com/helmfile/helmfile.git
synced 2026-10-22 16:37:08 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
ad5fba56e0 | ||
|
|
0ad62b504a | ||
|
|
8487970a2e | ||
|
|
8212b630ff | ||
|
|
2a71640095 | ||
|
|
33880dab77 | ||
|
|
87fcd83275 | ||
|
|
600721ca09 | ||
|
|
f7a1d09be7 | ||
|
|
e5d982ed33 | ||
|
|
c815d0192a | ||
|
|
9569d73e2f | ||
|
|
dd5bc46ead | ||
|
|
fc3567ddda | ||
|
|
9b64d65d94 | ||
|
|
54eb73b423 | ||
|
|
4c0987a618 | ||
|
|
0637973e40 | ||
|
|
3690bde94c | ||
|
|
b92ff2baff | ||
|
|
afe82f6453 | ||
|
|
1c7b872476 | ||
|
|
28e7ebb4a4 | ||
|
|
1ef9b29f6d | ||
|
|
b910591e1d | ||
|
|
d807510dd7 | ||
|
|
4e485219d7 | ||
|
|
b0e042988c | ||
|
|
f877de7778 | ||
|
|
655d5934a2 | ||
|
|
f6bf885fb7 | ||
|
|
2857382006 | ||
|
|
972667f115 | ||
|
|
3c88145329 | ||
|
|
709913b5ea | ||
|
|
88884b68dc | ||
|
|
5a15b65b94 | ||
|
|
3899680672 | ||
|
|
6b86408500 | ||
|
|
bdbaa00628 | ||
|
|
87e86dcfd2 | ||
|
|
995b201e47 | ||
|
|
0663831dd5 | ||
|
|
afb2653452 | ||
|
|
9ec4a85821 | ||
|
|
4d868794fd | ||
|
|
1aca099457 | ||
|
|
f5d098876a | ||
|
|
080080f57f | ||
|
|
cd4afc3269 | ||
|
|
8f8669778c | ||
|
|
563fce4adf | ||
|
|
b287101625 | ||
|
|
ab9fb2c9dc | ||
|
|
c170b5a621 | ||
|
|
d9286ed8f6 | ||
|
|
3018e82902 | ||
|
|
34acf14d7c | ||
|
|
5d8eba9b29 | ||
|
|
b284b7be83 | ||
|
|
b293ac1f83 | ||
|
|
8d9d635b8b | ||
|
|
954673d742 | ||
|
|
942b9a64c0 | ||
|
|
b176408eb2 | ||
|
|
ff81b2f279 | ||
|
|
8db03f979c | ||
|
|
988c218096 | ||
|
|
028bcc51dc | ||
|
|
5dd65e8d9a | ||
|
|
134d5beba2 | ||
|
|
87d04f0202 | ||
|
|
2d7c8e7525 | ||
|
|
0fad9f0544 | ||
|
|
832dcf47a5 | ||
|
|
1d2c8b8a3a | ||
|
|
0482ba3b6f | ||
|
|
9d2c0d4285 | ||
|
|
efd26f288a | ||
|
|
5f1698d0ed | ||
|
|
2ecf5780f3 | ||
|
|
3e6542e341 | ||
|
|
d3daea3137 | ||
|
|
19d79427a1 | ||
|
|
cd0ecc5263 | ||
|
|
dc6c59dc14 | ||
|
|
0fc0869671 | ||
|
|
5ca7ce15bc | ||
|
|
6b4b76e2bc | ||
|
|
7b11ce851a | ||
|
|
c575587e1f | ||
|
|
f2dfa7ce64 | ||
|
|
41cd1fe4a6 | ||
|
|
9e54af5c2c | ||
|
|
8a43e8f782 | ||
|
|
94e01b79e0 | ||
|
|
85e77982bf | ||
|
|
b42e847154 | ||
|
|
14e2b9ed93 | ||
|
|
90a41222e6 | ||
|
|
a9aa7af572 | ||
|
|
738409ee0e | ||
|
|
ec87a39015 | ||
|
|
ab1c118022 | ||
|
|
c8c444debc | ||
|
|
9a03d79a84 | ||
|
|
0ef7e65f02 | ||
|
|
61b61d3009 | ||
|
|
2710cb382f | ||
|
|
4d7fcd846e | ||
|
|
b85243a6b4 | ||
|
|
53c3fe9f37 | ||
|
|
2ce57ba2ee | ||
|
|
cc9b7f3942 | ||
|
|
85a2024669 | ||
|
|
87573089e8 | ||
|
|
4fb47a833a | ||
|
|
9cffef50b0 | ||
|
|
b4857937fb | ||
|
|
1e260e4a5e | ||
|
|
ee8ea507a6 | ||
|
|
b5830a3011 | ||
|
|
4fde6e13db | ||
|
|
a5e790caf1 | ||
|
|
1e956ae8a5 | ||
|
|
1671b8ba7d | ||
|
|
608ba19c3c | ||
|
|
daf7d3b876 | ||
|
|
34f6d4d568 | ||
|
|
1784b02d03 | ||
|
|
68c51d8c16 | ||
|
|
4bbb1699a3 | ||
|
|
753f65a56d | ||
|
|
43adbeaaca | ||
|
|
df6489ab43 | ||
|
|
31d0fce6fd | ||
|
|
cd427d9714 | ||
|
|
6d4f0984b6 | ||
|
|
ac40eec423 | ||
|
|
8de9b70b33 | ||
|
|
face92536c | ||
|
|
3a2a460fe7 | ||
|
|
f16d96bc8f | ||
|
|
0f86cc9b87 | ||
|
|
ca844aa044 | ||
|
|
604af3f1d2 | ||
|
|
28e13c3a18 | ||
|
|
7db31846cf | ||
|
|
63c8df9873 | ||
|
|
eff2a7bf84 | ||
|
|
3284df2752 | ||
|
|
e607bae5ff | ||
|
|
930f91a70d | ||
|
|
1cfce32e0d | ||
|
|
29fdb57add | ||
|
|
68432b1848 | ||
|
|
16288dfa7d | ||
|
|
969de1ebc9 | ||
|
|
ceb108a24f | ||
|
|
576a07f497 | ||
|
|
eb8ab54865 | ||
|
|
9e4bc6e7fc | ||
|
|
ef2d8853c2 | ||
|
|
66f34580c3 | ||
|
|
6d5f8c71cb | ||
|
|
23be9af2db | ||
|
|
1e9e228a9a | ||
|
|
c72cab80b5 | ||
|
|
923bd54db0 | ||
|
|
a0e30c5193 | ||
|
|
daf8c2f12b | ||
|
|
d71ac15748 | ||
|
|
eeb61e6174 | ||
|
|
b1190508b2 | ||
|
|
3a19a394ab | ||
|
|
dcb19f942b | ||
|
|
458385d971 | ||
|
|
b89fba1cc2 | ||
|
|
6b6b86a0b5 | ||
|
|
e0a793b7c5 | ||
|
|
71bb7354e7 | ||
|
|
3a34d28d7c | ||
|
|
f39d7928bf | ||
|
|
e0811dffa2 | ||
|
|
7d11f5dedc | ||
|
|
870cc03c70 | ||
|
|
98886df5d2 | ||
|
|
71635caace | ||
|
|
486be0970d | ||
|
|
9d7d2de6f5 | ||
|
|
04c963d050 | ||
|
|
a2c62c9e73 | ||
|
|
1654ce4c18 | ||
|
|
25aae679b1 | ||
|
|
d404356230 | ||
|
|
51ecd12360 | ||
|
|
6643a41ea3 | ||
|
|
35e5454994 | ||
|
|
69feadc360 | ||
|
|
f676c61425 | ||
|
|
20a39e9412 | ||
|
|
048c79ad34 | ||
|
|
994e4b66fc | ||
|
|
499f6ec6e3 | ||
|
|
bf22502a2f | ||
|
|
7c80a859fa | ||
|
|
00c4422a64 | ||
|
|
af44965949 | ||
|
|
0186254e79 | ||
|
|
8acbbc596d | ||
|
|
f1bdb65777 | ||
|
|
322ed4d1d8 | ||
|
|
c4e6160220 | ||
|
|
10f2a576c5 | ||
|
|
2579a876a6 | ||
|
|
6b3277ce71 | ||
|
|
27425bc4d2 | ||
|
|
05add478c1 | ||
|
|
757e748e3f | ||
|
|
e6f1db128c | ||
|
|
5fb037c8e3 | ||
|
|
5abbdea059 | ||
|
|
9fab6851f0 | ||
|
|
c8ba676e19 | ||
|
|
fc75f25293 | ||
|
|
b044820607 | ||
|
|
0e1aeb43ce | ||
|
|
88adc56551 | ||
|
|
9cf6b59cd8 | ||
|
|
fb7cc60afe | ||
|
|
90c7b04dbf | ||
|
|
ac534a897b | ||
|
|
314557c824 | ||
|
|
4b2d6946be |
@@ -1,207 +0,0 @@
|
||||
version: 2
|
||||
|
||||
jobs:
|
||||
|
||||
build:
|
||||
docker:
|
||||
- image: circleci/golang:1.13.4
|
||||
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
|
||||
- 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.13.4
|
||||
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.13.4
|
||||
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.13.4
|
||||
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: cp ~/build/helmfile ~/project/helmfile
|
||||
- run:
|
||||
name: Install helm
|
||||
environment:
|
||||
HELM_VERSION: v2.15.2
|
||||
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: 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: cp ~/build/helmfile ~/project/helmfile
|
||||
- run:
|
||||
name: Install helm
|
||||
environment:
|
||||
HELM_VERSION: v3.0.0
|
||||
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: 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.13.4
|
||||
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,5 @@
|
||||
root = true
|
||||
|
||||
[*.md]
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
@@ -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,35 @@
|
||||
name: "Setup Image Variables"
|
||||
|
||||
inputs:
|
||||
dockerfile:
|
||||
required: true
|
||||
|
||||
outputs:
|
||||
tag:
|
||||
description: "The tag if run against a tag, otherwise 'canary'"
|
||||
value: ${{ steps.vars.outputs.tag }}
|
||||
suffix:
|
||||
description: "The suffix for the image package name (if any)"
|
||||
value: ${{ steps.vars.outputs.suffix }}
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Get reference and suffix
|
||||
id: vars
|
||||
shell: bash
|
||||
env:
|
||||
DOCKERFILE: ${{ inputs.dockerfile }}
|
||||
run: |
|
||||
TAG=${GITHUB_REF##*/}
|
||||
if [[ $GITHUB_REF == refs/heads/main ]]; then
|
||||
TAG=canary
|
||||
elif [[ $GITHUB_REF == refs/pull/*/merge ]]; then
|
||||
TAG=pullrequest # this 'tag' is just used for caching
|
||||
fi
|
||||
echo ::set-output name=tag::${TAG:-canary}
|
||||
SUFFIX=${DOCKERFILE##Dockerfile}
|
||||
if [[ "${SUFFIX}" == "."* ]]; then
|
||||
SUFFIX=${SUFFIX//./-} # convert dots into dashes
|
||||
fi
|
||||
echo ::set-output name=suffix::${SUFFIX}
|
||||
@@ -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,17 @@
|
||||
# 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:
|
||||
- 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 ?= v3.8.8
|
||||
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://storage.googleapis.com/kubernetes-release/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,101 @@
|
||||
name: Go
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
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@v2
|
||||
with:
|
||||
go-version: 1.18
|
||||
- name: Env
|
||||
run: go env
|
||||
- name: Build
|
||||
run: make build build-test-tools
|
||||
- name: Test
|
||||
run: make check pristine test
|
||||
|
||||
- name: Prepare tar to upload built binaries
|
||||
run: tar -cvf built-binaries.tar helmfile diff-yamls yamldiff
|
||||
- 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 yamldiff
|
||||
|
||||
integration_tests:
|
||||
needs: tests
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- helm-version: v3.4.2
|
||||
- helm-version: v3.5.4
|
||||
- helm-version: v3.6.3
|
||||
- helm-version: v3.7.2
|
||||
- helm-version: v3.8.2
|
||||
- helm-version: v3.9.0
|
||||
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@v2
|
||||
with:
|
||||
go-version: 1.18
|
||||
- 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 yamldiff
|
||||
|
||||
- name: Install test dependencies
|
||||
env:
|
||||
HELM_VERSION: ${{ matrix.helm-version }}
|
||||
run: make -C .github/workflows helm vault sops kustomize
|
||||
- name: Start minikube
|
||||
uses: medyagh/setup-minikube@master
|
||||
- name: Execute integration tests
|
||||
env:
|
||||
HELMFILE_HELM3: 1
|
||||
TERM: xterm
|
||||
run: make integration
|
||||
@@ -0,0 +1,74 @@
|
||||
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:
|
||||
dockerfile:
|
||||
- Dockerfile
|
||||
- Dockerfile.debian-stable-slim
|
||||
- Dockerfile.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: Set up Image Variables
|
||||
id: vars
|
||||
uses: ./.github/actions/setup-image-vars
|
||||
with:
|
||||
dockerfile: ${{ matrix.dockerfile }}
|
||||
|
||||
- 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.dockerfile }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') }}
|
||||
tags: |
|
||||
ghcr.io/${{ github.repository }}${{ steps.vars.outputs.suffix }}:${{ steps.vars.outputs.tag }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
@@ -0,0 +1,28 @@
|
||||
name: Lint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
env:
|
||||
GO_VERSION: 1.18
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.18
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Golangci lint
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
version: v1.48.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 Binaries
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "!*"
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
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.18.1
|
||||
-
|
||||
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.18.1
|
||||
-
|
||||
name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v1
|
||||
with:
|
||||
version: latest
|
||||
args: release --rm-dist --snapshot
|
||||
+10
-2
@@ -1,5 +1,13 @@
|
||||
dist/
|
||||
.idea/
|
||||
helmfile
|
||||
helmfile.lock
|
||||
/helmfile
|
||||
/helmfile.lock
|
||||
/diff-yamls
|
||||
/yamldiff
|
||||
test/integration/tmp$
|
||||
vendor/
|
||||
*.log
|
||||
.vagrant/
|
||||
*.lock
|
||||
test/integration/.gnupg/
|
||||
.vscode/launch.json
|
||||
|
||||
+359
@@ -0,0 +1,359 @@
|
||||
# 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
|
||||
# - dogsled
|
||||
# - dupl
|
||||
- errcheck
|
||||
- funlen
|
||||
- gocognit
|
||||
- goconst
|
||||
# - gocritic
|
||||
# - godox
|
||||
- gofmt
|
||||
- goimports
|
||||
- revive
|
||||
# - gosec
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
# - interfacer
|
||||
- misspell
|
||||
- nakedret
|
||||
- exportloopref
|
||||
- staticcheck
|
||||
- structcheck
|
||||
# - stylecheck
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unparam
|
||||
- unused
|
||||
- whitespace
|
||||
- gci
|
||||
# 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,18 @@
|
||||
project_name: helmfile
|
||||
builds:
|
||||
- id: helmfile
|
||||
main: .
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
ldflags:
|
||||
- -s -w -X github.com/helmfile/helmfile/pkg/app/version.Version={{.Version}}
|
||||
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
|
||||
+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
|
||||
|
||||
|
||||
+51
-16
@@ -1,21 +1,29 @@
|
||||
FROM golang:1.13.4-alpine3.10 as builder
|
||||
FROM golang:1.18.0-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.10
|
||||
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.15.2
|
||||
ARG HELM_LOCATION="https://kubernetes-helm.storage.googleapis.com"
|
||||
RUN apk add --no-cache ca-certificates git bash curl jq openssh-client
|
||||
|
||||
ARG HELM_VERSION="v3.9.0"
|
||||
ARG HELM_SHA256="1484ffb0c7a608d8069470f48b88d729e88c41a1b6602f145231e8ea7b43b50a"
|
||||
ARG HELM_LOCATION="https://get.helm.sh"
|
||||
ARG HELM_FILENAME="helm-${HELM_VERSION}-linux-amd64.tar.gz"
|
||||
ARG HELM_SHA256="a9d2db920bd4b3d824729bbe1ff3fa57ad27760487581af6e5d3156d1b3c2511"
|
||||
RUN wget ${HELM_LOCATION}/${HELM_FILENAME} && \
|
||||
|
||||
RUN set -x && \
|
||||
wget ${HELM_LOCATION}/${HELM_FILENAME} && \
|
||||
echo Verifying ${HELM_FILENAME}... && \
|
||||
sha256sum ${HELM_FILENAME} | grep -q "${HELM_SHA256}" && \
|
||||
echo Extracting ${HELM_FILENAME}... && \
|
||||
@@ -25,19 +33,46 @@ RUN wget ${HELM_LOCATION}/${HELM_FILENAME} && \
|
||||
# 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.14.5"
|
||||
ENV KUBECTL_SHA256="26681319de56820a8467c9407e9203d5b15fb010ffc75ac5b99c9945ad0bd28c"
|
||||
RUN curl -LO "https://storage.googleapis.com/kubernetes-release/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl" && \
|
||||
# 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.21.4"
|
||||
ENV KUBECTL_SHA256="9410572396fb31e49d088f9816beaebad7420c7686697578691be1651d3bf85a"
|
||||
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
|
||||
|
||||
RUN ["helm", "init", "--client-only"]
|
||||
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="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 zxvf kustomize_${KUSTOMIZE_VERSION}_linux_amd64.tar.gz && \
|
||||
rm kustomize_${KUSTOMIZE_VERSION}_linux_amd64.tar.gz && \
|
||||
mv kustomize /usr/local/bin/kustomize
|
||||
|
||||
ENV SOPS_VERSION="v3.7.3"
|
||||
RUN set -x && \
|
||||
curl --retry 5 --retry-connrefused -LO https://github.com/mozilla/sops/releases/download/${SOPS_VERSION}/sops-${SOPS_VERSION}.linux.amd64 && \
|
||||
chmod +x sops-${SOPS_VERSION}.linux.amd64 && \
|
||||
mv sops-${SOPS_VERSION}.linux.amd64 /usr/local/bin/sops
|
||||
|
||||
ENV AGE_VERSION="v1.0.0"
|
||||
RUN set -x && \
|
||||
curl --retry 5 --retry-connrefused -LO https://github.com/FiloSottile/age/releases/download/${AGE_VERSION}/age-${AGE_VERSION}-linux-amd64.tar.gz && \
|
||||
tar zxvf age-${AGE_VERSION}-linux-amd64.tar.gz && \
|
||||
mv age/age /usr/local/bin/age && \
|
||||
mv age/age-keygen /usr/local/bin/age-keygen && \
|
||||
rm -rf age-${AGE_VERSION}-linux-amd64.tar.gz age
|
||||
|
||||
RUN helm plugin install https://github.com/databus23/helm-diff --version v3.5.0 && \
|
||||
helm plugin install https://github.com/jkroepke/helm-secrets --version v3.5.0 && \
|
||||
helm plugin install https://github.com/hypnoglow/helm-s3.git --version v0.10.0 && \
|
||||
helm plugin install https://github.com/aslafy-z/helm-git.git --version v0.10.0
|
||||
|
||||
# Allow users other than root to use helm plugins located in root home
|
||||
RUN chmod 751 /root
|
||||
|
||||
COPY --from=builder /workspace/helmfile/dist/helmfile_linux_amd64 /usr/local/bin/helmfile
|
||||
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
FROM golang:1.18.0-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 \
|
||||
git bash curl jq wget openssh-client && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ARG HELM_VERSION="v3.9.0"
|
||||
ARG HELM_SHA256="1484ffb0c7a608d8069470f48b88d729e88c41a1b6602f145231e8ea7b43b50a"
|
||||
ARG HELM_LOCATION="https://get.helm.sh"
|
||||
ARG HELM_FILENAME="helm-${HELM_VERSION}-linux-amd64.tar.gz"
|
||||
|
||||
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.21.4"
|
||||
ENV KUBECTL_SHA256="9410572396fb31e49d088f9816beaebad7420c7686697578691be1651d3bf85a"
|
||||
RUN set -x && \
|
||||
wget "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 zxvf kustomize_${KUSTOMIZE_VERSION}_linux_amd64.tar.gz && \
|
||||
rm kustomize_${KUSTOMIZE_VERSION}_linux_amd64.tar.gz && \
|
||||
mv kustomize /usr/local/bin/kustomize
|
||||
|
||||
ENV SOPS_VERSION="v3.7.3"
|
||||
RUN set -x && \
|
||||
curl --retry 5 --retry-connrefused -LO https://github.com/mozilla/sops/releases/download/${SOPS_VERSION}/sops-${SOPS_VERSION}.linux.amd64 && \
|
||||
chmod +x sops-${SOPS_VERSION}.linux.amd64 && \
|
||||
mv sops-${SOPS_VERSION}.linux.amd64 /usr/local/bin/sops
|
||||
|
||||
ENV AGE_VERSION="v1.0.0"
|
||||
RUN set -x && \
|
||||
curl --retry 5 --retry-connrefused -LO https://github.com/FiloSottile/age/releases/download/${AGE_VERSION}/age-${AGE_VERSION}-linux-amd64.tar.gz && \
|
||||
tar zxvf age-${AGE_VERSION}-linux-amd64.tar.gz && \
|
||||
mv age/age /usr/local/bin/age && \
|
||||
mv age/age-keygen /usr/local/bin/age-keygen && \
|
||||
rm -rf age-${AGE_VERSION}-linux-amd64.tar.gz age
|
||||
|
||||
RUN helm plugin install https://github.com/databus23/helm-diff --version v3.5.0 && \
|
||||
helm plugin install https://github.com/jkroepke/helm-secrets --version v3.5.0 && \
|
||||
helm plugin install https://github.com/hypnoglow/helm-s3.git --version v0.10.0 && \
|
||||
helm plugin install https://github.com/aslafy-z/helm-git.git --version v0.10.0
|
||||
|
||||
# Allow users other than root to use helm plugins located in root home
|
||||
RUN chmod 751 /root
|
||||
|
||||
COPY --from=builder /workspace/helmfile/dist/helmfile_linux_amd64 /usr/local/bin/helmfile
|
||||
|
||||
CMD ["/usr/local/bin/helmfile"]
|
||||
@@ -1,44 +0,0 @@
|
||||
FROM golang:1.13.4-alpine3.10 as builder
|
||||
|
||||
RUN apk add --no-cache make git
|
||||
WORKDIR /workspace/helmfile
|
||||
COPY . /workspace/helmfile
|
||||
RUN make static-linux
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
FROM alpine:3.10
|
||||
|
||||
RUN apk add --no-cache ca-certificates git bash curl jq
|
||||
|
||||
ARG HELM_VERSION=v3.0.0
|
||||
ARG HELM_LOCATION="https://get.helm.sh"
|
||||
ARG HELM_FILENAME="helm-${HELM_VERSION}-linux-amd64.tar.gz"
|
||||
ARG HELM_SHA256="10e1fdcca263062b1d7b2cb93a924be1ef3dd6c381263d8151dd1a20a3d8c0dc"
|
||||
RUN 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.
|
||||
ENV KUBECTL_VERSION="v1.14.5"
|
||||
ENV KUBECTL_SHA256="26681319de56820a8467c9407e9203d5b15fb010ffc75ac5b99c9945ad0bd28c"
|
||||
RUN curl -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
|
||||
|
||||
RUN helm plugin install https://github.com/databus23/helm-diff --version v3.0.0-rc.7 && \
|
||||
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
|
||||
|
||||
ENV HELMFILE_HELM3="1"
|
||||
|
||||
CMD ["/usr/local/bin/helmfile"]
|
||||
@@ -0,0 +1,83 @@
|
||||
FROM golang:1.18.0-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-get update && \
|
||||
apt-get install --no-install-recommends -y \
|
||||
ca-certificates \
|
||||
git bash curl jq wget openssh-client && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ARG HELM_VERSION="v3.9.0"
|
||||
ARG HELM_SHA256="1484ffb0c7a608d8069470f48b88d729e88c41a1b6602f145231e8ea7b43b50a"
|
||||
ARG HELM_LOCATION="https://get.helm.sh"
|
||||
ARG HELM_FILENAME="helm-${HELM_VERSION}-linux-amd64.tar.gz"
|
||||
|
||||
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.21.4"
|
||||
ENV KUBECTL_SHA256="9410572396fb31e49d088f9816beaebad7420c7686697578691be1651d3bf85a"
|
||||
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 zxvf kustomize_${KUSTOMIZE_VERSION}_linux_amd64.tar.gz && \
|
||||
rm kustomize_${KUSTOMIZE_VERSION}_linux_amd64.tar.gz && \
|
||||
mv kustomize /usr/local/bin/kustomize
|
||||
|
||||
ENV SOPS_VERSION="v3.7.3"
|
||||
RUN set -x && \
|
||||
curl --retry 5 --retry-connrefused -LO https://github.com/mozilla/sops/releases/download/${SOPS_VERSION}/sops-${SOPS_VERSION}.linux.amd64 && \
|
||||
chmod +x sops-${SOPS_VERSION}.linux.amd64 && \
|
||||
mv sops-${SOPS_VERSION}.linux.amd64 /usr/local/bin/sops
|
||||
|
||||
ENV AGE_VERSION="v1.0.0"
|
||||
RUN set -x && \
|
||||
curl --retry 5 --retry-connrefused -LO https://github.com/FiloSottile/age/releases/download/${AGE_VERSION}/age-${AGE_VERSION}-linux-amd64.tar.gz && \
|
||||
tar zxvf age-${AGE_VERSION}-linux-amd64.tar.gz && \
|
||||
mv age/age /usr/local/bin/age && \
|
||||
mv age/age-keygen /usr/local/bin/age-keygen && \
|
||||
rm -rf age-${AGE_VERSION}-linux-amd64.tar.gz age
|
||||
|
||||
RUN helm plugin install https://github.com/databus23/helm-diff --version v3.5.0 && \
|
||||
helm plugin install https://github.com/jkroepke/helm-secrets --version v3.5.0 && \
|
||||
helm plugin install https://github.com/hypnoglow/helm-s3.git --version v0.10.0 && \
|
||||
helm plugin install https://github.com/aslafy-z/helm-git.git --version v0.10.0
|
||||
|
||||
# Allow users other than root to use helm plugins located in root home
|
||||
RUN chmod 751 /root
|
||||
|
||||
COPY --from=builder /workspace/helmfile/dist/helmfile_linux_amd64 /usr/local/bin/helmfile
|
||||
|
||||
CMD ["/usr/local/bin/helmfile"]
|
||||
@@ -2,7 +2,7 @@ ORG ?= $(shell basename $(realpath ..))
|
||||
PKGS := $(shell go list ./... | grep -v /vendor/)
|
||||
|
||||
build:
|
||||
go build ${TARGETS}
|
||||
go build -ldflags '-X github.com/helmfile/helmfile/pkg/app/version.Version=${TAG}' ${TARGETS}
|
||||
.PHONY: build
|
||||
|
||||
generate:
|
||||
@@ -17,7 +17,14 @@ check:
|
||||
go vet ${PKGS}
|
||||
.PHONY: check
|
||||
|
||||
build-test-tools:
|
||||
go build test/diff-yamls/diff-yamls.go
|
||||
go build test/yamldiff/yamldiff.go
|
||||
.PHONY: build-test-tools
|
||||
|
||||
test:
|
||||
@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} -cover -race -p=1
|
||||
.PHONY: test
|
||||
|
||||
@@ -25,16 +32,23 @@ 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 '!openbsd !freebsd !netbsd' -arch '!arm !mips !mipsle !mips64 !mips64le !s390x' -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 '-X github.com/helmfile/helmfile/pkg/app/version.Version=${TAG}' ${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 '-X github.com/helmfile/helmfile/pkg/app/version.Version=${TAG}' ${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 '-X github.com/helmfile/helmfile/pkg/app/version.Version=${TAG}' ${TARGETS}
|
||||
.PHONY: install
|
||||
|
||||
clean:
|
||||
@@ -47,7 +61,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:
|
||||
@@ -59,18 +73,17 @@ 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)
|
||||
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})'`"
|
||||
|
||||
|
||||
+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.
|
||||
@@ -11,11 +11,25 @@ information to this file.
|
||||
|---|---|---|---|---|
|
||||
| [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 |
|
||||
| [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 |
|
||||
| [Sight Machine](https://sightmachine.com/) | production | We don't have anything publicly posted about it, but have been using it for quite a while in production. | San Francisco, CA and Ann Arbor, MI | December 2018 |
|
||||
| [TailorMed](https://tailormed.co/) | production | We're deploying all of our platform and all K8s resources using Helmfile. The power of Helmfile are more then amazing. Thank you! | Tel-Aviv, Israel | September 2020 |
|
||||
| [VSHN – The DevOps Company](https://vshn.ch) | production | | Zurich, Switzerland | March 2019 |
|
||||
| [Vlocity](https://vlocity.com/) | proof-of-concept | | Melbourne, Australia | March 2019 |
|
||||
| [transit](https://transit.app/) | production | [Blog post](https://medium.com/@naseem_60378/helmfile-its-like-a-helm-for-your-helm-74a908581599). | Montreal, Canada | March 2019 |
|
||||
| [uniqkey](https://uniqkey.eu/) | production | [Wiki Page](https://ocd-scm.github.io/ocd-meta/) | Copenhagen, Denmark | April 2019 |
|
||||
| [bitsofinfo](https://github.com/bitsofinfo/helmfile-deploy) | production | Used with [helmfile-deploy](https://github.com/bitsofinfo/helmfile-deploy) to manage releases for dozens of apps | USA | July 2019 |
|
||||
| [kloeckner-i](https://www.kloeckner-i.com/) | production | We are deploying our standard tools via helmfile to all our clusters. | Berlin, Germany | September 2019 |
|
||||
| [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 |
|
||||
| [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 |
|
||||
<!-- 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
|
||||
@@ -0,0 +1,59 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/app"
|
||||
"github.com/helmfile/helmfile/pkg/config"
|
||||
)
|
||||
|
||||
// NewApplyCmd returns apply subcmd
|
||||
func NewApplyCmd(globalCfg *config.GlobalImpl) *cobra.Command {
|
||||
applyOptions := &config.ApplyOptions{}
|
||||
applyImpl := config.NewApplyImpl(globalCfg, 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 {
|
||||
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(&applyImpl.ApplyOptions.Set, "set", nil, "additional values to be merged into the command")
|
||||
f.StringArrayVar(&applyImpl.ApplyOptions.Values, "values", nil, "additional value files to be merged into the command")
|
||||
f.IntVar(&applyImpl.ApplyOptions.Concurrency, "concurrency", 0, "maximum number of concurrent helm processes to run, 0 is unlimited")
|
||||
f.BoolVar(&applyImpl.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(&applyImpl.ApplyOptions.Context, "context", 0, "output NUM lines of context around changes")
|
||||
f.StringVar(&applyImpl.ApplyOptions.Output, "output", "", "output format for diff plugin")
|
||||
f.BoolVar(&applyImpl.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(&applyImpl.ApplyOptions.Args, "args", "", "pass args to helm exec")
|
||||
f.BoolVar(&applyImpl.ApplyOptions.RetainValuesFiles, "retain-values-files", false, "DEPRECATED: Use skip-cleanup instead")
|
||||
f.BoolVar(&applyImpl.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(&applyImpl.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(&applyImpl.ApplyOptions.SkipNeeds, "skip-needs", false, `do not automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when when --selector/-l flag is not provided. Defaults to true when --include-needs or --include-transitive-needs is not provided`)
|
||||
f.BoolVar(&applyImpl.ApplyOptions.IncludeNeeds, "include-needs", false, `automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when when --selector/-l flag is not provided`)
|
||||
f.BoolVar(&applyImpl.ApplyOptions.IncludeTransitiveNeeds, "include-transitive-needs", false, `like --include-needs, but also includes transitive needs (needs of needs). Does nothing when when --selector/-l flag is not provided. Overrides exclusions of other selectors and conditions.`)
|
||||
f.BoolVar(&applyImpl.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 all0")
|
||||
f.BoolVar(&applyImpl.ApplyOptions.IncludeTests, "include-tests", false, "enable the diffing of the helm test hooks")
|
||||
f.StringArrayVar(&applyImpl.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(&applyImpl.ApplyOptions.SuppressSecrets, "suppress-secrets", false, "suppress secrets in the diff output. highly recommended to specify on CI/CD use-cases")
|
||||
f.BoolVar(&applyImpl.ApplyOptions.ShowSecrets, "show-secrets", false, "do not redact secret values in the diff output. should be used for debug purpose only")
|
||||
f.BoolVar(&applyImpl.ApplyOptions.SuppressDiff, "suppress-diff", false, "suppress diff in the output. Usable in new installs")
|
||||
f.BoolVar(&applyImpl.ApplyOptions.SkipDeps, "skip-deps", false, `skip running "helm repo update" and "helm dependency build"`)
|
||||
f.Bool("wait", false, `Override helmDefaults.wait setting "helm upgrade --install --wait"`)
|
||||
f.Bool("wait-for-jobs", false, `Override helmDefaults.waitForJobs setting "helm upgrade --install --wait-for-jobs"`)
|
||||
|
||||
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()
|
||||
buildImpl := config.NewBuildImpl(globalCfg, buildOptions)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "build",
|
||||
Short: "Build all resources from state file only when there are changes",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
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,40 @@
|
||||
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()
|
||||
chartsImpl := config.NewChartsImpl(globalCfg, chartsOptions)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "charts",
|
||||
Short: "DEPRECATED: sync releases from state file (helm upgrade --install)",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
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(&chartsOptions.Args, "args", "", "pass args to helm exec")
|
||||
f.StringArrayVar(&chartsOptions.Set, "set", nil, "additional values to be merged into the command")
|
||||
f.StringArrayVar(&chartsOptions.Values, "values", nil, "additional value files to be merged into the command")
|
||||
f.IntVar(&chartsOptions.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"
|
||||
)
|
||||
|
||||
// NewDeleteCmd returns delete subcmd
|
||||
func NewDeleteCmd(globalCfg *config.GlobalImpl) *cobra.Command {
|
||||
deleteOptions := config.NewDeleteOptions()
|
||||
deleteImpl := config.NewDeleteImpl(globalCfg, deleteOptions)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "delete",
|
||||
Short: "DEPRECATED: delete releases from state file (helm delete)",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
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(&deleteOptions.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"`)
|
||||
|
||||
return cmd
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
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()
|
||||
depsImpl := config.NewDepsImpl(globalCfg, depsOptions)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "deps",
|
||||
Short: "Update charts based on their requirements",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
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(&depsOptions.Args, "args", "", "pass args to helm exec")
|
||||
f.BoolVar(&depsOptions.SkipRepos, "skip-deps", false, `skip running "helm repo update" and "helm dependency build"`)
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
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()
|
||||
destroyImpl := config.NewDestroyImpl(globalCfg, destroyOptions)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "destroy",
|
||||
Short: "Destroys and then purges releases",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
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(&destroyOptions.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"`)
|
||||
|
||||
return cmd
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
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()
|
||||
diffImpl := config.NewDiffImpl(globalCfg, diffOptions)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "diff",
|
||||
Short: "Diff releases defined in state file",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
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(&diffOptions.Args, "args", "", "pass args to helm diff")
|
||||
f.StringArrayVar(&diffOptions.Set, "set", nil, "additional values to be merged into the command")
|
||||
f.StringArrayVar(&diffOptions.Values, "values", nil, "additional value files to be merged into the command")
|
||||
f.IntVar(&diffOptions.Concurrency, "concurrency", 0, "maximum number of concurrent downloads of release charts")
|
||||
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", false, `do not automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when 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 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 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.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")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/app"
|
||||
"github.com/helmfile/helmfile/pkg/config"
|
||||
)
|
||||
|
||||
// NewFetchCmd returns diff subcmd
|
||||
func NewFetchCmd(globalCfg *config.GlobalImpl) *cobra.Command {
|
||||
fetchOptions := config.NewFetchOptions()
|
||||
fetchImpl := config.NewFetchImpl(globalCfg, fetchOptions)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "fetch",
|
||||
Short: "Fetch charts from state file",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
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)")
|
||||
|
||||
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()
|
||||
lintImpl := config.NewLintImpl(globalCfg, lintOptions)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "lint",
|
||||
Short: "Lint charts from state file (helm lint)",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
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 downloads of release charts")
|
||||
f.BoolVar(&lintOptions.SkipDeps, "skip-deps", false, `skip running "helm repo update" and "helm dependency build"`)
|
||||
f.StringVar(&lintOptions.Args, "args", "", "pass args to helm exec")
|
||||
f.StringArrayVar(&lintOptions.Set, "set", nil, "additional values to be merged into the command")
|
||||
f.StringArrayVar(&lintOptions.Values, "values", nil, "additional value files to be merged into the command")
|
||||
f.BoolVar(&lintOptions.SkipNeeds, "skip-needs", false, `do not automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when 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 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 when --selector/-l flag is not provided. Overrides exclusions of other selectors and conditions.`)
|
||||
|
||||
return cmd
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
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()
|
||||
listImpl := config.NewListImpl(globalCfg, listOptions)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List releases defined in state file",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
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.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()
|
||||
reposImpl := config.NewReposImpl(globalCfg, reposOptions)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "repos",
|
||||
Short: "Repos releases defined in state file",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
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(&reposOptions.Args, "args", "", "pass args to helm exec")
|
||||
|
||||
return cmd
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/urfave/cli"
|
||||
"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/helmexec"
|
||||
)
|
||||
|
||||
var logger *zap.SugaredLogger
|
||||
var globalUsage = "Declaratively deploy your Kubernetes manifests, Kustomize configs, and Charts as Helm releases in one shot"
|
||||
|
||||
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 cli.NewExitError(e.Error(), noMatchingExitCode)
|
||||
case *app.MultiError:
|
||||
return cli.NewExitError(e.Error(), 1)
|
||||
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
|
||||
}
|
||||
|
||||
// NewRootCmd creates the root command for the CLI.
|
||||
func NewRootCmd(globalConfig *config.GlobalOptions, args []string) (*cobra.Command, error) {
|
||||
cmd := &cobra.Command{
|
||||
Use: "helmfile",
|
||||
Short: globalUsage,
|
||||
Long: globalUsage,
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Version: version.GetVersion(),
|
||||
SilenceUsage: 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)
|
||||
|
||||
// We can safely ignore any errors that flags.Parse encounters since
|
||||
// those errors will be caught later during the call to cmd.Execution.
|
||||
// This call is required to gather configuration information prior to
|
||||
// execution.
|
||||
flags.ParseErrorsWhitelist.UnknownFlags = true
|
||||
|
||||
err := flags.Parse(args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
globalImpl := config.NewGlobalImpl(globalConfig)
|
||||
cmd.AddCommand(
|
||||
NewApplyCmd(globalImpl),
|
||||
NewBuildCmd(globalImpl),
|
||||
NewCacheCmd(globalImpl),
|
||||
NewChartsCmd(globalImpl),
|
||||
NewDeleteCmd(globalImpl),
|
||||
NewDepsCmd(globalImpl),
|
||||
NewDestroyCmd(globalImpl),
|
||||
NewFetchCmd(globalImpl),
|
||||
NewListCmd(globalImpl),
|
||||
NewReposCmd(globalImpl),
|
||||
NewVersionCmd(),
|
||||
NewLintCmd(globalImpl),
|
||||
NewWriteValuesCmd(globalImpl),
|
||||
NewTestCmd(globalImpl),
|
||||
NewTemplateCmd(globalImpl),
|
||||
NewSyncCmd(globalImpl),
|
||||
NewDiffCmd(globalImpl),
|
||||
NewStatusCmd(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.d`(means `helmfile.d/*.yaml`) in this preference")
|
||||
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)")
|
||||
fs.StringArrayVar(&globalOptions.StateValuesFile, "state-values-file", nil, "specify state values in a YAML file")
|
||||
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.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()
|
||||
statusImpl := config.NewStatusImpl(globalCfg, statusOptions)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "status",
|
||||
Short: "Retrieve status of releases in state file",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
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(&statusOptions.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
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
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()
|
||||
syncImpl := config.NewSyncImpl(globalCfg, syncOptions)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "sync",
|
||||
Short: "Sync releases defined in state file",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
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(&syncOptions.Args, "args", "", "pass args to helm sync")
|
||||
f.StringArrayVar(&syncOptions.Set, "set", nil, "additional values to be merged into the command")
|
||||
f.StringArrayVar(&syncOptions.Values, "values", nil, "additional value files to be merged into the command")
|
||||
f.IntVar(&syncOptions.Concurrency, "concurrency", 0, "maximum number of concurrent downloads of release charts")
|
||||
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", false, `do not automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when 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 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 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"`)
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
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()
|
||||
templateImpl := config.NewTemplateImpl(globalCfg, templateOptions)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "template",
|
||||
Short: "Template releases defined in state file",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
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(&templateOptions.Args, "args", "", "pass args to helm template")
|
||||
f.StringArrayVar(&templateOptions.Set, "set", nil, "additional values to be merged into the command")
|
||||
f.StringArrayVar(&templateOptions.Values, "values", nil, "additional value files to be merged into the command")
|
||||
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 downloads of release charts")
|
||||
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", false, `do not automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when 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 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 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")
|
||||
|
||||
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 downloads of release charts")
|
||||
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(&testOptions.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,23 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/app/version"
|
||||
)
|
||||
|
||||
// NewVersionCmd returns version subcmd
|
||||
func NewVersionCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Show the version for Helmfile.",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
fmt.Println("Helmfile version " + version.GetVersion())
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
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 subcmd
|
||||
func NewWriteValuesCmd(globalCfg *config.GlobalImpl) *cobra.Command {
|
||||
writeValuesOptions := config.NewWriteValuesOptions()
|
||||
writeValuesImpl := config.NewWriteValuesImpl(globalCfg, writeValuesOptions)
|
||||
|
||||
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 {
|
||||
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 downloads of release charts")
|
||||
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 command")
|
||||
f.StringArrayVar(&writeValuesOptions.Values, "values", nil, "additional value files to be merged into the command")
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,314 @@
|
||||
## Advanced Features
|
||||
|
||||
- [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
|
||||
|
||||
Helmfile integrates [vals]() to import configuration parameters from following backends:
|
||||
|
||||
- AWS SSM Parameter Store
|
||||
- AWS SecretsManager
|
||||
- Vault
|
||||
- SOPS
|
||||
|
||||
See [Vals "Suported Backends"](https://github.com/variantdev/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.
|
||||
|
||||
### Deploy Kustomizations with Helmfile
|
||||
|
||||
You can deploy [kustomize](https://github.com/kubernetes-sigs/kustomize) "kustomization"s with Helmfile.
|
||||
|
||||
Most of Kustomize operations that is usually done with `kustomize edit` can be done declaratively via Helm values.yaml files.
|
||||
|
||||
Under the hood, Helmfile transforms the kustomization into a local chart in a temporary directory so that it can be `helm upgrade --install`ed.
|
||||
|
||||
The transformation is done by generating (1)a temporary kustomization from various options and (2)temporary chart from the temporary kustomization.
|
||||
|
||||
An example pseudo code for the transformation logic can be written as:
|
||||
|
||||
```console
|
||||
$ TMPCHART=/tmp/sometmpdir
|
||||
$ mkdir -p ${TMPCHART}/templates
|
||||
$ somehow_generate_chart_yaml ${TMPCHART}/Chart.yaml
|
||||
|
||||
$ TMPKUSTOMIZATION=/tmp/sometmpdir2
|
||||
$ somehow_generate_temp_kustomization_yaml ${TMPKUSTOMIZATION}/kustomization.yaml
|
||||
$ kustomize build ${TMPKUSTOMIZATION}/kustomization.yaml > ${TMPCHART}/templates/all.yaml
|
||||
```
|
||||
|
||||
Let's say you have a `helmfile.yaml` that looks like the below:
|
||||
|
||||
```yaml
|
||||
releases:
|
||||
- name: myapp
|
||||
chart: mykustomization
|
||||
values:
|
||||
- values.yaml
|
||||
```
|
||||
|
||||
Helmfile firstly generates a temporary `kustomization.yaml` that looks like:
|
||||
|
||||
```yaml
|
||||
bases:
|
||||
- $(ABS_PATH_TO_HELMFILE_YAML}/mykustomization
|
||||
```
|
||||
|
||||
Followed by the below steps:
|
||||
|
||||
- Running `kustomize edit set image $IMAGE` for every `$IMAGE` generated from your values.yaml
|
||||
- Running `kustomize edit set nameprefix $NAMEPREFIX` with the nameprefix specified in your values.yaml
|
||||
- Running `kustomize edit set namesuffix $NAMESUFFIX` with the namesuffix specified in your values.yaml
|
||||
- Running `kustomize edit set namespace $NS` with the namespace specified in your values.yaml
|
||||
|
||||
A `values.yaml` file for kustomization would look like the below:
|
||||
|
||||
```yaml
|
||||
images:
|
||||
# kustomize edit set image mysql=eu.gcr.io/my-project/mysql@canary
|
||||
- name: mysql
|
||||
newName: eu.gcr.io/my-project/mysql
|
||||
newTag: canary
|
||||
# kustomize edit set image myapp=my-registry/my-app@sha256:24a0c4b4a4c0eb97a1aabb8e29f18e917d05abfe1b7a7c07857230879ce7d3d3
|
||||
- name: myapp
|
||||
digest: sha256:24a0c4b4a4c0eb97a1aabb8e29f18e917d05abfe1b7a7c07857230879ce7d3d3
|
||||
newName: my-registry/my-app
|
||||
|
||||
# kustomize edit set nameprefix foo-
|
||||
namePrefix: foo-
|
||||
|
||||
# kustomize edit set namesuffix -bar
|
||||
nameSuffix: -bar
|
||||
|
||||
# kustomize edit set namespace myapp
|
||||
namespace: myapp
|
||||
```
|
||||
|
||||
At this point, Helmfile can generate a complete kustomization from the base kustomization you specified in `releases[].chart` of your helmfile.yaml and `values.yaml`,
|
||||
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/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.
|
||||
|
||||
Currently, Helmfile allows you to set the following fields for kustomizing the chart:
|
||||
|
||||
- [`releases[].strategicMergePatches`](#strategicmergepatches)
|
||||
- `releases[].jsonPatches`
|
||||
- [`releases[].transformers`](#transformers)
|
||||
|
||||
#### `strategicMergePatches`
|
||||
|
||||
You can add/update any Kubernetes resource field rendered from a Helm chart by specifying `releases[].strategicMergePatches`:
|
||||
|
||||
```
|
||||
repositories:
|
||||
- name: incubator
|
||||
url: https://kubernetes-charts-incubator.storage.googleapis.com
|
||||
|
||||
releases:
|
||||
- name: raw1
|
||||
chart: incubator/raw
|
||||
values:
|
||||
- resources:
|
||||
- apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: raw1
|
||||
namespace: default
|
||||
data:
|
||||
foo: FOO
|
||||
strategicMergePatches:
|
||||
- apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: raw1
|
||||
namespace: default
|
||||
data:
|
||||
bar: BAR
|
||||
```
|
||||
|
||||
Running `helmfile template` on the above example results in a ConfigMap called `raw` whose `data` is:
|
||||
|
||||
```yaml
|
||||
foo: FOO
|
||||
bar: BAR
|
||||
```
|
||||
|
||||
Please note that the second `data` field `bar` is coming from the strategic-merge patch defined in the above helmfile.yaml.
|
||||
|
||||
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/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.
|
||||
|
||||
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:
|
||||
|
||||
```yaml
|
||||
releases:
|
||||
- name: "aws-load-balancer-controller"
|
||||
namespace: "kube-system"
|
||||
forceNamespace: "kube-system"
|
||||
chart: "center/aws/aws-load-balancer-controller"
|
||||
transformers:
|
||||
- apiVersion: builtin
|
||||
kind: AnnotationsTransformer
|
||||
metadata:
|
||||
name: notImportantHere
|
||||
annotations:
|
||||
area: 51
|
||||
greeting: take me to your leader
|
||||
fieldSpecs:
|
||||
- path: metadata/annotations
|
||||
create: true
|
||||
- apiVersion: builtin
|
||||
kind: LabelTransformer
|
||||
metadata:
|
||||
name: notImportantHere
|
||||
labels:
|
||||
foo: bar
|
||||
fieldSpecs:
|
||||
- path: metadata/labels
|
||||
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.
|
||||
|
||||
```yaml
|
||||
transformers:
|
||||
# Embedded transformer
|
||||
- apiVersion: builtin
|
||||
kind: AnnotationsTransformer
|
||||
metadata:
|
||||
name: notImportantHere
|
||||
annotations:
|
||||
area: 51
|
||||
greeting: take me to your leader
|
||||
fieldSpecs:
|
||||
- path: metadata/annotations
|
||||
create: true
|
||||
# YAML file
|
||||
- path/to/transformer.yaml
|
||||
# Go template
|
||||
# The same set of template parameters as release values files templates is available.
|
||||
- path/to/transformer.yaml.gotmpl
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```
|
||||
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 a 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.
|
||||
|
||||
```
|
||||
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`, defien it like you would do in a standard `Chart.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:
|
||||
|
||||
|
||||
```
|
||||
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`:
|
||||
|
||||
```
|
||||
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:
|
||||
|
||||
```
|
||||
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:
|
||||
|
||||
```
|
||||
releases:
|
||||
- name: foo
|
||||
chart: ./path/to/foo
|
||||
dependencies:
|
||||
- chart: oci://my-oci-registry/helm-repo/envoy
|
||||
version: 1.5
|
||||
```
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../CONTRIBUTING.md
|
||||
+1493
File diff suppressed because it is too large
Load Diff
Symlink
+1
@@ -0,0 +1 @@
|
||||
../LICENSE
|
||||
+12
-10
@@ -1,25 +1,27 @@
|
||||
# Paths Overivew
|
||||
Using manifest files in conjunction with command line argument can be a bit confusing.
|
||||
# Paths Overivew
|
||||
|
||||
A few rules to clear up this ambiguity:
|
||||
Using manifest files in conjunction with command line argument can be a bit confusing.
|
||||
|
||||
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
|
||||
|
||||
### Examples
|
||||
There are several examples that we can go through in the `/examples` folder which demonstrate this.
|
||||
|
||||
There are several examples that we can go through in the [`/examples`](../examples/) folder which demonstrate this.
|
||||
|
||||
**Local Execution**
|
||||
|
||||
This is an example of a Helmfile manifest referencing a local value directly.
|
||||
This is an example of a Helmfile manifest referencing a local value directly.
|
||||
|
||||
Indirect:
|
||||
```
|
||||
helmfile -f examples/deployments/local/charts.yaml sync
|
||||
```
|
||||
|
||||
Direct:
|
||||
Direct:
|
||||
```
|
||||
cd examples/deployments/local/
|
||||
helmfile sync
|
||||
@@ -27,14 +29,14 @@ helmfile sync
|
||||
|
||||
**Relative Paths in Helmfile**
|
||||
|
||||
This is an example of a Helmfile manifest using relative paths for values.
|
||||
This is an example of a Helmfile manifest using relative paths for values.
|
||||
|
||||
Indirect:
|
||||
```
|
||||
helmfile -f examples/deployments/dev/charts.yaml sync
|
||||
```
|
||||
|
||||
Direct:
|
||||
Direct:
|
||||
```
|
||||
cd examples/deployments/dev/
|
||||
helmfile sync
|
||||
@@ -42,7 +44,7 @@ helmfile sync
|
||||
|
||||
**Relative Paths in Helmfile w/ --values overrides**
|
||||
|
||||
This is an example of a Helmfile manifest using relative paths for values including an additional `--values` from the command line.
|
||||
This is an example of a Helmfile manifest using relative paths for values including an additional `--values` from the command line.
|
||||
|
||||
NOTE: The `--values` is resolved relative to the CWD of the terminal *not* the Helmfile manifest. You can see this with the `replicas` being adjusted to 3 now for the deployment.
|
||||
|
||||
@@ -51,7 +53,7 @@ Indirect:
|
||||
helmfile -f examples/deployments/dev/charts.yaml sync --values values/replica-values.yaml
|
||||
```
|
||||
|
||||
Direct:
|
||||
Direct:
|
||||
```
|
||||
cd examples/deployments/dev/
|
||||
helmfile sync --values ../../values/replica-values.yaml
|
||||
@@ -0,0 +1,70 @@
|
||||
# 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 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 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 }}
|
||||
```
|
||||
2. Force `.gotmpl` (or `.tpl`) 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.
|
||||
|
||||
3. 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, but out 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.
|
||||
|
||||
## 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.
|
||||
@@ -0,0 +1,58 @@
|
||||
# Secrets
|
||||
|
||||
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/variantdev/vals) can handle vault, AWS SSM etc)
|
||||
This section will describe the second use case.
|
||||
|
||||
# Remote secrets
|
||||
|
||||
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
|
||||
|
||||
repositories:
|
||||
- name: stable
|
||||
url: https://kubernetes-charts.storage.googleapis.com
|
||||
|
||||
environments:
|
||||
default:
|
||||
values:
|
||||
- service:
|
||||
password: ref+vault://svc/#pass
|
||||
login: ref+vault://svc/#login
|
||||
releases:
|
||||
- name: service
|
||||
namespace: default
|
||||
labels:
|
||||
cluster: services
|
||||
secrets: vault
|
||||
chart: stable/svc
|
||||
version: 0.1.0
|
||||
values:
|
||||
- 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
|
||||
```
|
||||
## Fetching multiple keys
|
||||
Alternatively you can use `expandSecretRefs` to fetch a map of secrets
|
||||
```yaml
|
||||
# values/service.yaml.gotmpl
|
||||
service:
|
||||
{{ .Values.service | expandSecretRefs | toYaml | nindent 2 }}
|
||||
```
|
||||
|
||||
This will produce
|
||||
```yaml
|
||||
# values/service.yaml
|
||||
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.27
|
||||
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,93 @@
|
||||
# Template Functions
|
||||
|
||||
#### `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" }}
|
||||
```
|
||||
|
||||
#### `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" }}
|
||||
```
|
||||
|
||||
#### `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/variantdev/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/variantdev/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
|
||||
+73
-15
@@ -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.
|
||||
@@ -14,10 +31,10 @@ That is, the following example let `helmfile` fail when you have no `eventApi.re
|
||||
{{ .Values.eventApi.replicas | default 1 }}
|
||||
```
|
||||
|
||||
In case it isn't a mistake and you do want to allow missing keys, use the `getOrNil` template function:
|
||||
In case it isn't a mistake and you do want to allow missing keys, use the `get` template function:
|
||||
|
||||
```
|
||||
{{ .Values | getOrNil "eventApi.replicas" }}
|
||||
{{ .Values | get "eventApi.replicas" nil }}
|
||||
```
|
||||
|
||||
This result in printing `<no value` in your template, that may or may not result in a failure.
|
||||
@@ -25,14 +42,14 @@ This result in printing `<no value` in your template, that may or may not result
|
||||
If you want a kind of default values that is used when a missing key was referenced, use `default` like:
|
||||
|
||||
```
|
||||
{{ .Values | getOrNil "eventApi.replicas" | default 1 }}
|
||||
{{ .Values | get "eventApi.replicas" 1 }}
|
||||
```
|
||||
|
||||
Now, you get `1` when there is no `eventApi.replicas` defined in environment values.
|
||||
|
||||
## Release Template / Conventional Directory Structure
|
||||
|
||||
Introducing helmfile into a large-scale project that involes dozens of releases often results in a lot of repetitions in `helmfile.yaml` files.
|
||||
Introducing helmfile into a large-scale project that involves dozens of releases often results in a lot of repetitions in `helmfile.yaml` files.
|
||||
|
||||
The example below shows repetitions in `namespace`, `chart`, `values`, and `secrets`:
|
||||
|
||||
@@ -84,14 +101,16 @@ templates:
|
||||
|
||||
releases:
|
||||
- name: heapster
|
||||
version: 0.3.2
|
||||
<<: *default
|
||||
- name: kubernetes-dashboard
|
||||
version: 0.10.0
|
||||
<<: *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
|
||||
- boolean fields: `installed`, `wait`, `waitForJobs`, `tillerless`, `verify` by the means of additional text
|
||||
fields designed for templating only: `installedTemplate`, `waitTemplate`, `tillerlessTemplate`, `verifyTemplate`
|
||||
```yaml
|
||||
# ...
|
||||
@@ -135,18 +154,18 @@ Please note, that it is not possible to layer `values` sections. If `values` is
|
||||
|
||||
> See **Layering State Template Files** if you're layering templates.
|
||||
|
||||
You may occasionally end up with many helmfiles that shares common parts like which repositories to use, and whichi release to be bundled by default.
|
||||
You may occasionally end up with many helmfiles that shares common parts like which repositories to use, and which release to be bundled by default.
|
||||
|
||||
Use Layering to extract the common parts into a dedicated *library helmfile*s, so that each helmfile becomes DRY.
|
||||
|
||||
Let's assume that your `helmfile.yaml` looks like:
|
||||
|
||||
```
|
||||
```yaml
|
||||
bases:
|
||||
- environments.yaml
|
||||
|
||||
releases:
|
||||
- name: metricbaet
|
||||
- name: metricbeat
|
||||
chart: stable/metricbeat
|
||||
- name: myapp
|
||||
chart: mychart
|
||||
@@ -173,7 +192,7 @@ environments:
|
||||
releases:
|
||||
- name: myapp
|
||||
chart: mychart
|
||||
- name: metricbaet
|
||||
- name: metricbeat
|
||||
chart: stable/metricbeat
|
||||
```
|
||||
|
||||
@@ -186,7 +205,7 @@ environments:
|
||||
production:
|
||||
|
||||
releases:
|
||||
- name: metricbaet
|
||||
- name: metricbeat
|
||||
chart: stable/metricbeat
|
||||
- name: myapp
|
||||
chart: mychart
|
||||
@@ -204,7 +223,7 @@ Helmfile doesn't merge arrays across layers. That is, the below example doesn't
|
||||
|
||||
```yaml
|
||||
releases:
|
||||
- name: metricbaet
|
||||
- name: metricbeat
|
||||
chart: stable/metricbeat
|
||||
---
|
||||
releases:
|
||||
@@ -216,7 +235,7 @@ Helmfile overrides the `releases` array with the latest layer so the resulting s
|
||||
|
||||
```yaml
|
||||
releases:
|
||||
# metricbeat release disappeared! but that's how helmfiel works
|
||||
# metricbeat release disappeared! but that's how helmfile works
|
||||
- name: myapp
|
||||
chart: mychart
|
||||
```
|
||||
@@ -228,7 +247,7 @@ A work-around is to treat the state file as a go template and use `readFile` tem
|
||||
```yaml
|
||||
templates:
|
||||
metricbeat: &metricbeat
|
||||
- name: metricbeat
|
||||
name: metricbeat
|
||||
chart: stable/metricbeat
|
||||
```
|
||||
|
||||
@@ -256,7 +275,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
|
||||
---
|
||||
@@ -314,7 +333,7 @@ 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:
|
||||
|
||||
@@ -356,3 +375,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
|
||||
```
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ You would then start a database migration job by executing:
|
||||
|
||||
```console
|
||||
# Start a database migration for the prod environment
|
||||
$ RAILS_ENV=prod helmfile sync --selector job=dbmigrator
|
||||
$ RAILS_ENV=prod helmfile --selector job=dbmigrator sync
|
||||
|
||||
# Tail log until you are satisfied
|
||||
$ kubectl logs -l job=dbmigrator
|
||||
|
||||
@@ -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
|
||||
@@ -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,48 +1,205 @@
|
||||
module github.com/roboll/helmfile
|
||||
module github.com/helmfile/helmfile
|
||||
|
||||
go 1.13
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.47.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go v35.0.0+incompatible // indirect
|
||||
github.com/Masterminds/goutils v1.1.0 // indirect
|
||||
github.com/Masterminds/semver v1.4.2
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible
|
||||
github.com/Masterminds/semver/v3 v3.1.1
|
||||
github.com/Masterminds/sprig/v3 v3.2.2
|
||||
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a
|
||||
github.com/aws/aws-sdk-go v1.25.22 // indirect
|
||||
github.com/go-test/deep v1.0.3
|
||||
github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 // indirect
|
||||
github.com/google/go-cmp v0.3.1
|
||||
github.com/gosuri/uitable v0.0.3
|
||||
github.com/hashicorp/go-getter v1.3.0
|
||||
github.com/hashicorp/go-retryablehttp v0.6.3 // indirect
|
||||
github.com/hashicorp/go-version v1.2.0
|
||||
github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c // indirect
|
||||
github.com/huandu/xstrings v1.2.0 // indirect
|
||||
github.com/imdario/mergo v0.3.6
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
|
||||
github.com/mattn/go-colorable v0.1.4 // indirect
|
||||
github.com/mattn/go-isatty v0.0.10 // indirect
|
||||
github.com/pierrec/lz4 v2.3.0+incompatible // indirect
|
||||
github.com/r3labs/diff v0.0.0-20190801153147-a71de73c46ad
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32
|
||||
github.com/go-test/deep v1.0.8
|
||||
github.com/golang/mock v1.6.0
|
||||
github.com/google/go-cmp v0.5.8
|
||||
github.com/gosuri/uitable v0.0.4
|
||||
github.com/hashicorp/go-getter v1.6.2
|
||||
github.com/hashicorp/go-version v1.6.0
|
||||
github.com/imdario/mergo v0.3.13
|
||||
github.com/jessevdk/go-flags v1.4.0
|
||||
github.com/kylelemons/godebug v1.1.0
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible
|
||||
github.com/mattn/go-isatty v0.0.16
|
||||
github.com/r3labs/diff v1.1.0
|
||||
github.com/spf13/cobra v1.5.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/tatsushid/go-prettytable v0.0.0-20141013043238-ed2d14c29939
|
||||
github.com/urfave/cli v1.20.0
|
||||
github.com/variantdev/dag v0.0.0-20191028002400-bb0b3c785363
|
||||
github.com/variantdev/vals v0.0.0-20191123015158-21b16052816c
|
||||
go.mozilla.org/sops v0.0.0-20190912205235-14a22d7a7060 // indirect
|
||||
go.opencensus.io v0.22.1 // indirect
|
||||
go.uber.org/multierr v1.1.0
|
||||
go.uber.org/zap v1.9.1
|
||||
golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf // indirect
|
||||
golang.org/x/net v0.0.0-20191028085509-fe3aa8a45271 // indirect
|
||||
golang.org/x/sys v0.0.0-20191029155521-f43be2a4598c // indirect
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
|
||||
google.golang.org/api v0.13.0 // indirect
|
||||
google.golang.org/appengine v1.6.5 // indirect
|
||||
google.golang.org/genproto v0.0.0-20191028173616-919d9bdd9fe6 // indirect
|
||||
google.golang.org/grpc v1.24.0 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.4
|
||||
gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d // indirect
|
||||
github.com/urfave/cli v1.22.9
|
||||
github.com/variantdev/chartify v0.10.2
|
||||
github.com/variantdev/dag v1.1.0
|
||||
github.com/variantdev/vals v0.18.0
|
||||
go.uber.org/multierr v1.6.0
|
||||
go.uber.org/zap v1.22.0
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
gotest.tools v2.2.0+incompatible
|
||||
k8s.io/apimachinery v0.24.4
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.100.2 // indirect
|
||||
cloud.google.com/go/compute v1.3.0 // indirect
|
||||
cloud.google.com/go/iam v0.1.0 // indirect
|
||||
cloud.google.com/go/storage v1.15.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 v56.3.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.20 // indirect
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.15 // 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.40.28 // indirect
|
||||
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
|
||||
github.com/blang/semver v3.5.1+incompatible // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // 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.0.0 // 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.2.0 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.1.1 // indirect
|
||||
github.com/goware/prefixer v0.0.0-20160118172347-395022866408 // indirect
|
||||
github.com/hashicorp/errwrap v1.0.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.0 // 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.3.2 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/itchyny/gojq v0.9.0 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/klauspost/compress v1.13.6 // indirect
|
||||
github.com/lestrrat-go/strftime v1.0.1 // indirect
|
||||
github.com/lib/pq v1.10.4 // indirect
|
||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||
github.com/mattn/go-ieproxy v0.0.1 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.9 // 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.0.0 // 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/russross/blackfriday/v2 v2.1.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.2.0 // indirect
|
||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||
github.com/spf13/cast v1.4.1 // indirect
|
||||
github.com/ulikunitz/xz v0.5.8 // 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.0.0-20220127200216-cd36cc0744dd // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
google.golang.org/api v0.70.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf // indirect
|
||||
google.golang.org/grpc v1.44.0 // indirect
|
||||
google.golang.org/protobuf v1.27.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
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect
|
||||
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/secretmanager v1.3.0 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/containerd/containerd v1.5.9 // indirect
|
||||
github.com/docker/cli v20.10.11+incompatible // indirect
|
||||
github.com/docker/distribution v2.7.1+incompatible // indirect
|
||||
github.com/docker/docker v20.10.12+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.6.4 // 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/evanphx/json-patch v4.12.0+incompatible // indirect
|
||||
github.com/frankban/quicktest v1.14.3 // indirect
|
||||
github.com/go-errors/errors v1.0.1 // indirect
|
||||
github.com/go-logr/logr v1.2.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.19.5 // 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/gofuzz v1.1.0 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/googleapis/gnostic v0.5.5 // 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/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/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
|
||||
github.com/mailru/easyjson v0.7.6 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||
github.com/moby/locker v1.0.1 // indirect
|
||||
github.com/moby/term v0.0.0-20210610120745-9d4ed1856297 // 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/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.2 // indirect
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
||||
github.com/pierrec/lz4 v2.3.0+incompatible // indirect
|
||||
github.com/prometheus/client_golang v1.11.0 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.28.0 // indirect
|
||||
github.com/prometheus/procfs v0.6.0 // indirect
|
||||
github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect
|
||||
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
|
||||
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
helm.sh/helm/v3 v3.8.1 // indirect
|
||||
k8s.io/api v0.23.4 // indirect
|
||||
k8s.io/cli-runtime v0.23.4 // indirect
|
||||
k8s.io/client-go v0.23.4 // indirect
|
||||
k8s.io/klog/v2 v2.60.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 // indirect
|
||||
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect
|
||||
oras.land/oras-go v1.1.0 // indirect
|
||||
sigs.k8s.io/kustomize/api v0.10.1 // indirect
|
||||
sigs.k8s.io/kustomize/kyaml v0.13.0 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
|
||||
)
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "=> Building container"
|
||||
docker build \
|
||||
--tag "$IMAGE_NAME" \
|
||||
--file Dockerfile .
|
||||
@@ -0,0 +1,9 @@
|
||||
#!/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
@@ -0,0 +1,11 @@
|
||||
#!/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 |
@@ -2,642 +2,27 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/roboll/helmfile/pkg/app/version"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
"go.uber.org/zap/zapcore"
|
||||
|
||||
"github.com/helmfile/helmfile/cmd"
|
||||
"github.com/helmfile/helmfile/pkg/config"
|
||||
)
|
||||
|
||||
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("quiet") {
|
||||
logLevel = "warn"
|
||||
}
|
||||
var level zapcore.Level
|
||||
err := level.Set(logLevel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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 warning(format string, v ...interface{}) {
|
||||
format = fmt.Sprintf("WARNING: %s\n", format)
|
||||
fmt.Fprintf(os.Stderr, format, v...)
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
cliApp := cli.NewApp()
|
||||
cliApp.Name = "helmfile"
|
||||
cliApp.Usage = ""
|
||||
cliApp.Version = version.Version
|
||||
cliApp.Flags = []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "helm-binary, b",
|
||||
Usage: "path to helm binary",
|
||||
Value: "helm",
|
||||
},
|
||||
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: "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",
|
||||
},
|
||||
}
|
||||
|
||||
cliApp.Before = configureLogging
|
||||
cliApp.Commands = []cli.Command{
|
||||
{
|
||||
Name: "deps",
|
||||
Usage: "update charts based on the contents of requirements.yaml",
|
||||
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: "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.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.Template(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.StringFlag{
|
||||
Name: "args",
|
||||
Value: "",
|
||||
Usage: "pass args to helm exec",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "suppress-secrets",
|
||||
Usage: "suppress secrets in the diff output. highly recommended to specify on CI/CD use-cases",
|
||||
},
|
||||
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.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{},
|
||||
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{},
|
||||
Action: action(func(run *app.App, c configImpl) error {
|
||||
return run.ListReleases(c)
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
err := cliApp.Run(os.Args)
|
||||
globalConfig := new(config.GlobalOptions)
|
||||
rootCmd, err := cmd.NewRootCmd(globalConfig, os.Args[1:])
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||
os.Exit(3)
|
||||
warning("%+v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
cli.HandleExitCoder(err)
|
||||
}
|
||||
}
|
||||
|
||||
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 := strings.Split(op[0], ".")
|
||||
v := op[1]
|
||||
|
||||
set = maputil.Set(set, k, v)
|
||||
}
|
||||
}
|
||||
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 {
|
||||
return c.c.String("args")
|
||||
}
|
||||
|
||||
func (c configImpl) OutputDir() string {
|
||||
return c.c.String("output-dir")
|
||||
}
|
||||
|
||||
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) SuppressSecrets() bool {
|
||||
return c.c.Bool("suppress-secrets")
|
||||
}
|
||||
|
||||
// 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) Timeout() int {
|
||||
return c.c.Int("timeout")
|
||||
}
|
||||
|
||||
// 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) Logger() *zap.SugaredLogger {
|
||||
return c.c.App.Metadata["logger"].(*zap.SugaredLogger)
|
||||
}
|
||||
|
||||
func (c configImpl) Env() string {
|
||||
env := c.c.GlobalString("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)
|
||||
|
||||
a.ErrorHandler = func(err error) error {
|
||||
return toCliError(implCtx, err)
|
||||
}
|
||||
|
||||
return do(a, conf)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
# 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
|
||||
- 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
|
||||
+1329
-495
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,307 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/variantdev/vals"
|
||||
|
||||
"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
|
||||
}
|
||||
|
||||
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{},
|
||||
}
|
||||
|
||||
bs := &bytes.Buffer{}
|
||||
|
||||
func() {
|
||||
t.Helper()
|
||||
|
||||
logReader, logWriter := io.Pipe()
|
||||
|
||||
logFlushed := &sync.WaitGroup{}
|
||||
// Ensure all the log is consumed into `bs` by calling `logWriter.Close()` followed by `logFlushed.Wait()`
|
||||
logFlushed.Add(1)
|
||||
go func() {
|
||||
scanner := bufio.NewScanner(logReader)
|
||||
for scanner.Scan() {
|
||||
bs.Write(scanner.Bytes())
|
||||
bs.WriteString("\n")
|
||||
}
|
||||
logFlushed.Done()
|
||||
}()
|
||||
|
||||
defer func() {
|
||||
// This is here to avoid data-trace on bytes buffer `bs` to capture logs
|
||||
if err := logWriter.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
logFlushed.Wait()
|
||||
}()
|
||||
|
||||
logger := helmexec.NewLogger(logWriter, "debug")
|
||||
|
||||
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,
|
||||
})
|
||||
|
||||
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"}},
|
||||
{Name: "my-release", Flags: []string{"--kube-context", "default", "--namespace", "default"}},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
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"}},
|
||||
{Name: "kubernetes-external-secrets", Flags: []string{"--kube-context", "default", "--namespace", "kube-system"}},
|
||||
{Name: "external-secrets", Flags: []string{"--kube-context", "default", "--namespace", "default"}},
|
||||
{Name: "my-release", Flags: []string{"--kube-context", "default", "--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"},
|
||||
diffed: []exectest.Release{
|
||||
{Name: "logging", Flags: []string{"--kube-context", "default", "--namespace", "kube-system"}},
|
||||
{Name: "kubernetes-external-secrets", Flags: []string{"--kube-context", "default", "--namespace", "kube-system"}},
|
||||
{Name: "external-secrets", Flags: []string{"--kube-context", "default", "--namespace", "default"}},
|
||||
{Name: "my-release", Flags: []string{"--kube-context", "default", "--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"},
|
||||
diffed: []exectest.Release{
|
||||
{Name: "test2", Flags: []string{"--kube-context", "default"}},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
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"}},
|
||||
{Name: "test3", Flags: []string{"--kube-context", "default"}},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
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"}},
|
||||
{Name: "test3", Flags: []string{"--kube-context", "default"}},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
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"}},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
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"}},
|
||||
{Name: "test3", Flags: []string{"--kube-context", "default"}},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
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",
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,336 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/variantdev/vals"
|
||||
|
||||
"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{},
|
||||
}
|
||||
|
||||
bs := &bytes.Buffer{}
|
||||
|
||||
func() {
|
||||
t.Helper()
|
||||
|
||||
logReader, logWriter := io.Pipe()
|
||||
|
||||
logFlushed := &sync.WaitGroup{}
|
||||
// Ensure all the log is consumed into `bs` by calling `logWriter.Close()` followed by `logFlushed.Wait()`
|
||||
logFlushed.Add(1)
|
||||
go func() {
|
||||
scanner := bufio.NewScanner(logReader)
|
||||
for scanner.Scan() {
|
||||
bs.Write(scanner.Bytes())
|
||||
bs.WriteString("\n")
|
||||
}
|
||||
logFlushed.Done()
|
||||
}()
|
||||
|
||||
defer func() {
|
||||
// This is here to avoid data-trace on bytes buffer `bs` to capture logs
|
||||
if err := logWriter.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
logFlushed.Wait()
|
||||
}()
|
||||
|
||||
logger := helmexec.NewLogger(logWriter, "debug")
|
||||
|
||||
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,248 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/variantdev/vals"
|
||||
|
||||
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) {
|
||||
type testcase struct {
|
||||
environment string
|
||||
ns string
|
||||
error string
|
||||
selectors []string
|
||||
expected string
|
||||
}
|
||||
|
||||
check := func(t *testing.T, tc testcase) {
|
||||
t.Helper()
|
||||
|
||||
bs := &bytes.Buffer{}
|
||||
|
||||
func() {
|
||||
t.Helper()
|
||||
|
||||
logReader, logWriter := io.Pipe()
|
||||
|
||||
logFlushed := &sync.WaitGroup{}
|
||||
// Ensure all the log is consumed into `bs` by calling `logWriter.Close()` followed by `logFlushed.Wait()`
|
||||
logFlushed.Add(1)
|
||||
go func() {
|
||||
scanner := bufio.NewScanner(logReader)
|
||||
for scanner.Scan() {
|
||||
bs.Write(scanner.Bytes())
|
||||
bs.WriteString("\n")
|
||||
}
|
||||
logFlushed.Done()
|
||||
}()
|
||||
|
||||
defer func() {
|
||||
// This is here to avoid data-trace on bytes buffer `bs` to capture logs
|
||||
if err := logWriter.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
logFlushed.Wait()
|
||||
}()
|
||||
|
||||
logger := helmexec.NewLogger(logWriter, "debug")
|
||||
|
||||
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(configImpl{})
|
||||
})
|
||||
|
||||
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
|
||||
`,
|
||||
})
|
||||
})
|
||||
|
||||
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`,
|
||||
})
|
||||
})
|
||||
|
||||
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
|
||||
`,
|
||||
})
|
||||
})
|
||||
|
||||
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
|
||||
`,
|
||||
})
|
||||
})
|
||||
|
||||
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
|
||||
`,
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,951 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/variantdev/vals"
|
||||
|
||||
"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{},
|
||||
}
|
||||
|
||||
bs := &bytes.Buffer{}
|
||||
|
||||
func() {
|
||||
t.Helper()
|
||||
|
||||
logReader, logWriter := io.Pipe()
|
||||
|
||||
logFlushed := &sync.WaitGroup{}
|
||||
// Ensure all the log is consumed into `bs` by calling `logWriter.Close()` followed by `logFlushed.Wait()`
|
||||
logFlushed.Add(1)
|
||||
go func() {
|
||||
scanner := bufio.NewScanner(logReader)
|
||||
for scanner.Scan() {
|
||||
bs.Write(scanner.Bytes())
|
||||
bs.WriteString("\n")
|
||||
}
|
||||
logFlushed.Done()
|
||||
}()
|
||||
|
||||
defer func() {
|
||||
// This is here to avoid data-trace on bytes buffer `bs` to capture logs
|
||||
if err := logWriter.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
logFlushed.Wait()
|
||||
}()
|
||||
|
||||
logger := helmexec.NewLogger(logWriter, "debug")
|
||||
|
||||
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: helmV2ListFlags}: `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: helmV2ListFlags}: `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: helmV2ListFlags}: `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: helmV2ListFlags}: `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: helmV2ListFlags}: `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: helmV2ListFlags}: `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: helmV2ListFlags}: `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: helmV2ListFlags}: `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: helmV2ListFlags}: `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: helmV2ListFlags}: `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: helmV2ListFlags}: `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: helmV2ListFlags}: ``,
|
||||
{Filter: "^external-secrets$", Flags: helmV2ListFlags}: `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: helmV2ListFlags}: `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,336 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/variantdev/vals"
|
||||
|
||||
"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 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{},
|
||||
}
|
||||
|
||||
bs := &bytes.Buffer{}
|
||||
|
||||
func() {
|
||||
t.Helper()
|
||||
|
||||
logReader, logWriter := io.Pipe()
|
||||
|
||||
logFlushed := &sync.WaitGroup{}
|
||||
// Ensure all the log is consumed into `bs` by calling `logWriter.Close()` followed by `logFlushed.Wait()`
|
||||
logFlushed.Add(1)
|
||||
go func() {
|
||||
scanner := bufio.NewScanner(logReader)
|
||||
for scanner.Scan() {
|
||||
bs.Write(scanner.Bytes())
|
||||
bs.WriteString("\n")
|
||||
}
|
||||
logFlushed.Done()
|
||||
}()
|
||||
|
||||
defer func() {
|
||||
// This is here to avoid data-trace on bytes buffer `bs` to capture logs
|
||||
if err := logWriter.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
logFlushed.Wait()
|
||||
}()
|
||||
|
||||
logger := helmexec.NewLogger(logWriter, "debug")
|
||||
|
||||
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(nil)},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
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(nil)},
|
||||
{Name: "test3", Flags: []string(nil)},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
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(nil)},
|
||||
{Name: "test3", Flags: []string(nil)},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
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(nil)},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
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(nil)},
|
||||
{Name: "test3", Flags: []string(nil)},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
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",
|
||||
})
|
||||
})
|
||||
}
|
||||
+1873
-823
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,7 @@ type ConfigProvider interface {
|
||||
FileOrDir() string
|
||||
KubeContext() string
|
||||
Namespace() string
|
||||
Chart() string
|
||||
Selectors() []string
|
||||
StateValuesSet() map[string]interface{}
|
||||
StateValuesFiles() []string
|
||||
@@ -22,15 +23,18 @@ type DeprecatedChartsConfigProvider interface {
|
||||
|
||||
concurrencyConfig
|
||||
loggingConfig
|
||||
IncludeTransitiveNeeds() bool
|
||||
}
|
||||
|
||||
type DepsConfigProvider interface {
|
||||
Args() string
|
||||
SkipRepos() bool
|
||||
IncludeTransitiveNeeds() bool
|
||||
}
|
||||
|
||||
type ReposConfigProvider interface {
|
||||
Args() string
|
||||
IncludeTransitiveNeeds() bool
|
||||
}
|
||||
|
||||
type ApplyConfigProvider interface {
|
||||
@@ -38,12 +42,31 @@ type ApplyConfigProvider interface {
|
||||
|
||||
Values() []string
|
||||
Set() []string
|
||||
SkipCRDs() bool
|
||||
SkipDeps() bool
|
||||
Wait() bool
|
||||
WaitForJobs() bool
|
||||
|
||||
IncludeTests() bool
|
||||
|
||||
Suppress() []string
|
||||
SuppressSecrets() bool
|
||||
ShowSecrets() bool
|
||||
SuppressDiff() bool
|
||||
|
||||
DetailedExitcode() bool
|
||||
|
||||
Color() bool
|
||||
NoColor() bool
|
||||
Context() int
|
||||
DiffOutput() string
|
||||
|
||||
RetainValuesFiles() bool
|
||||
Validate() bool
|
||||
SkipCleanup() bool
|
||||
SkipDiffOnInstall() bool
|
||||
|
||||
DAGConfig
|
||||
|
||||
concurrencyConfig
|
||||
interactive
|
||||
@@ -55,7 +78,17 @@ type SyncConfigProvider interface {
|
||||
|
||||
Values() []string
|
||||
Set() []string
|
||||
SkipCRDs() bool
|
||||
SkipDeps() bool
|
||||
Wait() bool
|
||||
WaitForJobs() bool
|
||||
|
||||
Validate() bool
|
||||
|
||||
SkipNeeds() bool
|
||||
IncludeNeeds() bool
|
||||
IncludeTransitiveNeeds() bool
|
||||
DAGConfig
|
||||
|
||||
concurrencyConfig
|
||||
loggingConfig
|
||||
@@ -66,13 +99,25 @@ type DiffConfigProvider interface {
|
||||
|
||||
Values() []string
|
||||
Set() []string
|
||||
Validate() bool
|
||||
SkipCRDs() bool
|
||||
SkipDeps() bool
|
||||
|
||||
IncludeTests() bool
|
||||
|
||||
Suppress() []string
|
||||
SuppressSecrets() bool
|
||||
ShowSecrets() bool
|
||||
SuppressDiff() bool
|
||||
SkipDiffOnInstall() bool
|
||||
|
||||
DAGConfig
|
||||
|
||||
DetailedExitcode() bool
|
||||
Color() bool
|
||||
NoColor() bool
|
||||
Context() int
|
||||
DiffOutput() string
|
||||
|
||||
concurrencyConfig
|
||||
}
|
||||
@@ -81,6 +126,7 @@ type DeleteConfigProvider interface {
|
||||
Args() string
|
||||
|
||||
Purge() bool
|
||||
SkipDeps() bool
|
||||
|
||||
interactive
|
||||
loggingConfig
|
||||
@@ -90,6 +136,8 @@ type DeleteConfigProvider interface {
|
||||
type DestroyConfigProvider interface {
|
||||
Args() string
|
||||
|
||||
SkipDeps() bool
|
||||
|
||||
interactive
|
||||
loggingConfig
|
||||
concurrencyConfig
|
||||
@@ -98,8 +146,10 @@ type DestroyConfigProvider interface {
|
||||
type TestConfigProvider interface {
|
||||
Args() string
|
||||
|
||||
SkipDeps() bool
|
||||
Timeout() int
|
||||
Cleanup() bool
|
||||
Logs() bool
|
||||
|
||||
concurrencyConfig
|
||||
}
|
||||
@@ -110,6 +160,16 @@ type LintConfigProvider interface {
|
||||
Values() []string
|
||||
Set() []string
|
||||
SkipDeps() bool
|
||||
SkipCleanup() bool
|
||||
|
||||
DAGConfig
|
||||
|
||||
concurrencyConfig
|
||||
}
|
||||
|
||||
type FetchConfigProvider interface {
|
||||
SkipDeps() bool
|
||||
OutputDir() string
|
||||
|
||||
concurrencyConfig
|
||||
}
|
||||
@@ -119,12 +179,34 @@ type TemplateConfigProvider interface {
|
||||
|
||||
Values() []string
|
||||
Set() []string
|
||||
OutputDirTemplate() string
|
||||
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
|
||||
}
|
||||
|
||||
type StatusesConfigProvider interface {
|
||||
Args() string
|
||||
|
||||
@@ -132,6 +214,7 @@ type StatusesConfigProvider interface {
|
||||
}
|
||||
|
||||
type StateConfigProvider interface {
|
||||
EmbedValues() bool
|
||||
}
|
||||
|
||||
type concurrencyConfig interface {
|
||||
@@ -145,3 +228,9 @@ type loggingConfig interface {
|
||||
type interactive interface {
|
||||
Interactive() bool
|
||||
}
|
||||
|
||||
type ListConfigProvider interface {
|
||||
Output() string
|
||||
}
|
||||
|
||||
type CacheConfigProvider interface{}
|
||||
|
||||
@@ -3,20 +3,21 @@ package app
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/envvar"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultHelmfile = "helmfile.yaml"
|
||||
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)
|
||||
}
|
||||
|
||||
+37
-15
@@ -1,27 +1,49 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"gotest.tools/assert"
|
||||
is "gotest.tools/assert/cmp"
|
||||
"gotest.tools/env"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/envvar"
|
||||
)
|
||||
|
||||
// TestIsExplicitSelectorInheritanceEnabled tests the isExplicitSelectorInheritanceEnabled function
|
||||
func TestIsExplicitSelectorInheritanceEnabled(t *testing.T) {
|
||||
//env var ExperimentalEnvVar is set
|
||||
assert.Assert(t, is.Equal(os.Getenv(ExperimentalEnvVar), ""))
|
||||
assert.Check(t, !isExplicitSelectorInheritanceEnabled())
|
||||
// env var ExperimentalEnvVar is not set
|
||||
require.Empty(t, os.Getenv(envvar.Experimental))
|
||||
require.False(t, isExplicitSelectorInheritanceEnabled())
|
||||
|
||||
//check for env var ExperimentalEnvVar set to true
|
||||
defer env.Patch(t, ExperimentalEnvVar, "true")()
|
||||
assert.Check(t, isExplicitSelectorInheritanceEnabled())
|
||||
// check for env var ExperimentalEnvVar set to true
|
||||
t.Setenv(envvar.Experimental, "true")
|
||||
require.True(t, isExplicitSelectorInheritanceEnabled())
|
||||
|
||||
//check for env var ExperimentalEnvVar set to anything
|
||||
defer env.Patch(t, ExperimentalEnvVar, "foo")()
|
||||
assert.Check(t, !isExplicitSelectorInheritanceEnabled())
|
||||
// check for env var ExperimentalEnvVar set to anything
|
||||
t.Setenv(envvar.Experimental, "anything")
|
||||
require.False(t, isExplicitSelectorInheritanceEnabled())
|
||||
|
||||
//check for env var ExperimentalEnvVar set to ExperimentalSelectorExplicit
|
||||
defer env.Patch(t, ExperimentalEnvVar, ExperimentalSelectorExplicit)()
|
||||
assert.Check(t, isExplicitSelectorInheritanceEnabled())
|
||||
// check for env var ExperimentalEnvVar set to ExperimentalSelectorExplicit
|
||||
t.Setenv(envvar.Experimental, ExperimentalSelectorExplicit)
|
||||
require.True(t, isExplicitSelectorInheritanceEnabled())
|
||||
|
||||
// check for env var ExperimentalEnvVar set to a string that contains ExperimentalSelectorExplicit and ExperimentalEnvVar set to true
|
||||
t.Setenv(envvar.Experimental, fmt.Sprintf("%s-%s-%s", "a", ExperimentalSelectorExplicit, "b"))
|
||||
require.True(t, isExplicitSelectorInheritanceEnabled())
|
||||
}
|
||||
|
||||
// TestExperimentalModeEnabled tests the experimentalModeEnabled function
|
||||
func TestExperimentalModeEnabled(t *testing.T) {
|
||||
// env var ExperimentalEnvVar is not set
|
||||
require.Empty(t, os.Getenv(envvar.Experimental))
|
||||
require.False(t, experimentalModeEnabled())
|
||||
|
||||
// check for env var ExperimentalEnvVar set to anything
|
||||
t.Setenv(envvar.Experimental, "anything")
|
||||
require.False(t, experimentalModeEnabled())
|
||||
|
||||
// check for env var ExperimentalEnvVar set to true
|
||||
t.Setenv(envvar.Experimental, "true")
|
||||
require.True(t, experimentalModeEnabled())
|
||||
}
|
||||
|
||||
+17
-16
@@ -1,35 +1,36 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"github.com/roboll/helmfile/pkg/state"
|
||||
"github.com/helmfile/helmfile/pkg/state"
|
||||
)
|
||||
|
||||
type Context struct {
|
||||
updatedRepos map[string]struct{}
|
||||
updatedRepos map[string]bool
|
||||
updatedReposV2 map[string]bool
|
||||
}
|
||||
|
||||
func NewContext() Context {
|
||||
return Context{
|
||||
updatedRepos: map[string]struct{}{},
|
||||
updatedRepos: map[string]bool{},
|
||||
updatedReposV2: map[string]bool{},
|
||||
}
|
||||
}
|
||||
|
||||
func (ctx Context) SyncReposOnce(st *state.HelmState, helm state.RepoUpdater) []error {
|
||||
var errs []error
|
||||
func (ctx Context) SyncReposOnce(st *state.HelmState, helm state.RepoUpdater) error {
|
||||
var (
|
||||
updated []string
|
||||
err error
|
||||
)
|
||||
|
||||
allUpdated := true
|
||||
for _, r := range st.Repositories {
|
||||
_, exists := ctx.updatedRepos[r.Name]
|
||||
allUpdated = allUpdated && exists
|
||||
if helm.IsHelm3() {
|
||||
updated, err = st.SyncRepos(helm, ctx.updatedRepos)
|
||||
} else {
|
||||
updated, err = st.SyncRepos(helm, ctx.updatedReposV2)
|
||||
}
|
||||
|
||||
if !allUpdated {
|
||||
errs = st.SyncRepos(helm)
|
||||
|
||||
for _, r := range st.Repositories {
|
||||
ctx.updatedRepos[r.Name] = struct{}{}
|
||||
}
|
||||
for _, r := range updated {
|
||||
ctx.updatedRepos[r] = true
|
||||
}
|
||||
|
||||
return errs
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -5,30 +5,35 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"github.com/imdario/mergo"
|
||||
"github.com/roboll/helmfile/pkg/environment"
|
||||
"github.com/roboll/helmfile/pkg/helmexec"
|
||||
"github.com/roboll/helmfile/pkg/state"
|
||||
"github.com/variantdev/vals"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/environment"
|
||||
"github.com/helmfile/helmfile/pkg/filesystem"
|
||||
"github.com/helmfile/helmfile/pkg/helmexec"
|
||||
"github.com/helmfile/helmfile/pkg/remote"
|
||||
"github.com/helmfile/helmfile/pkg/state"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultHelmBinary = state.DefaultHelmBinary
|
||||
)
|
||||
|
||||
type desiredStateLoader struct {
|
||||
KubeContext string
|
||||
Reverse bool
|
||||
overrideKubeContext string
|
||||
overrideHelmBinary string
|
||||
|
||||
env string
|
||||
namespace string
|
||||
chart string
|
||||
fs *filesystem.FileSystem
|
||||
|
||||
readFile func(string) ([]byte, error)
|
||||
fileExists func(string) (bool, error)
|
||||
abs func(string) (string, error)
|
||||
glob func(string) ([]string, error)
|
||||
getHelm func(*state.HelmState) helmexec.Interface
|
||||
|
||||
remote *remote.Remote
|
||||
logger *zap.SugaredLogger
|
||||
helm helmexec.Interface
|
||||
valsRuntime vals.Evaluator
|
||||
}
|
||||
|
||||
@@ -41,10 +46,10 @@ func (ld *desiredStateLoader) Load(f string, opts LoadOpts) (*state.HelmState, e
|
||||
if opts.CalleePath == "" {
|
||||
return nil, fmt.Errorf("bug: opts.CalleePath was nil: f=%s, opts=%v", f, opts)
|
||||
}
|
||||
storage := state.NewStorage(opts.CalleePath, ld.logger, ld.glob)
|
||||
envld := state.NewEnvironmentValuesLoader(storage, ld.readFile, ld.logger)
|
||||
storage := state.NewStorage(opts.CalleePath, ld.logger, ld.fs)
|
||||
envld := state.NewEnvironmentValuesLoader(storage, ld.fs, ld.logger, ld.remote)
|
||||
handler := state.MissingFileHandlerError
|
||||
vals, err := envld.LoadEnvironmentValues(&handler, args)
|
||||
vals, err := envld.LoadEnvironmentValues(&handler, args, &environment.EmptyEnvironment)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -60,19 +65,18 @@ func (ld *desiredStateLoader) Load(f string, opts LoadOpts) (*state.HelmState, e
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ld.Reverse {
|
||||
rev := func(i, j int) bool {
|
||||
return j < i
|
||||
}
|
||||
sort.Slice(st.Releases, rev)
|
||||
sort.Slice(st.Helmfiles, rev)
|
||||
if opts.Reverse {
|
||||
st.Reverse()
|
||||
}
|
||||
|
||||
if ld.KubeContext != "" {
|
||||
if st.HelmDefaults.KubeContext != "" {
|
||||
return nil, errors.New("err: Cannot use option --kube-context and set attribute helmDefaults.kubeContext.")
|
||||
if ld.overrideKubeContext != "" {
|
||||
if st.OverrideKubeContext != "" {
|
||||
return nil, errors.New("err: Cannot use option --kube-context and set attribute kubeContext.")
|
||||
}
|
||||
st.HelmDefaults.KubeContext = ld.KubeContext
|
||||
st.OverrideKubeContext = ld.overrideKubeContext
|
||||
// HelmDefaults.KubeContext is also overridden in here
|
||||
// to set default release value properly.
|
||||
st.HelmDefaults.KubeContext = ld.overrideKubeContext
|
||||
}
|
||||
|
||||
if ld.namespace != "" {
|
||||
@@ -82,10 +86,25 @@ func (ld *desiredStateLoader) Load(f string, opts LoadOpts) (*state.HelmState, e
|
||||
st.OverrideNamespace = ld.namespace
|
||||
}
|
||||
|
||||
if ld.chart != "" {
|
||||
if st.OverrideChart != "" {
|
||||
return nil, errors.New("err: Cannot use option --chart and set attribute chart.")
|
||||
}
|
||||
st.OverrideChart = ld.chart
|
||||
}
|
||||
|
||||
return st, nil
|
||||
}
|
||||
|
||||
func (ld *desiredStateLoader) loadFile(inheritedEnv *environment.Environment, baseDir, file string, evaluateBases bool) (*state.HelmState, error) {
|
||||
path, err := ld.remote.Locate(file)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("locate: %v", err)
|
||||
}
|
||||
if file != path {
|
||||
ld.logger.Debugf("fetched remote \"%s\" to local cache \"%s\" and loading the latter...", file, path)
|
||||
}
|
||||
file = path
|
||||
return ld.loadFileWithOverrides(inheritedEnv, nil, baseDir, file, evaluateBases)
|
||||
}
|
||||
|
||||
@@ -97,7 +116,7 @@ func (ld *desiredStateLoader) loadFileWithOverrides(inheritedEnv, overrodeEnv *e
|
||||
f = filepath.Join(baseDir, file)
|
||||
}
|
||||
|
||||
fileBytes, err := ld.readFile(f)
|
||||
fileBytes, err := ld.fs.ReadFile(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -143,7 +162,7 @@ func (ld *desiredStateLoader) loadFileWithOverrides(inheritedEnv, overrodeEnv *e
|
||||
}
|
||||
|
||||
func (a *desiredStateLoader) underlying() *state.StateCreator {
|
||||
c := state.NewCreator(a.logger, a.readFile, a.fileExists, a.abs, a.glob, a.helm, a.valsRuntime)
|
||||
c := state.NewCreator(a.logger, a.fs, a.valsRuntime, a.getHelm, a.overrideHelmBinary, a.remote)
|
||||
c.LoadFile = a.loadFile
|
||||
return c
|
||||
}
|
||||
@@ -169,7 +188,9 @@ func (a *desiredStateLoader) load(yaml []byte, baseDir, file string, evaluateBas
|
||||
}
|
||||
|
||||
func (ld *desiredStateLoader) renderAndLoad(env, overrodeEnv *environment.Environment, baseDir, filename string, content []byte, evaluateBases bool) (*state.HelmState, error) {
|
||||
parts := bytes.Split(content, []byte("\n---\n"))
|
||||
// Allows part-splitting to work with CLRF-ed content
|
||||
normalizedContent := bytes.ReplaceAll(content, []byte("\r\n"), []byte("\n"))
|
||||
parts := bytes.Split(normalizedContent, []byte("\n---\n"))
|
||||
|
||||
var finalState *state.HelmState
|
||||
|
||||
@@ -203,12 +224,20 @@ func (ld *desiredStateLoader) renderAndLoad(env, overrodeEnv *environment.Enviro
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i, r := range currentState.Releases {
|
||||
if r.Chart == "" {
|
||||
return nil, fmt.Errorf("error during %s parsing: encountered empty chart while reading release %q at index %d", id, r.Name, i)
|
||||
}
|
||||
}
|
||||
|
||||
if finalState == nil {
|
||||
finalState = currentState
|
||||
} else {
|
||||
if err := mergo.Merge(finalState, currentState, mergo.WithOverride); err != nil {
|
||||
if err := mergo.Merge(&finalState.ReleaseSetSpec, ¤tState.ReleaseSetSpec, mergo.WithOverride); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
finalState.RenderedValues = currentState.RenderedValues
|
||||
}
|
||||
|
||||
env = &finalState.Env
|
||||
|
||||
@@ -0,0 +1,775 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/variantdev/vals"
|
||||
|
||||
"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 TestDestroy_2(t *testing.T) {
|
||||
type testcase struct {
|
||||
helm3 bool
|
||||
ns string
|
||||
concurrency int
|
||||
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{
|
||||
Helm3: tc.helm3,
|
||||
FailOnUnexpectedList: true,
|
||||
FailOnUnexpectedDiff: true,
|
||||
Lists: tc.lists,
|
||||
Diffs: tc.diffs,
|
||||
DiffMutex: &sync.Mutex{},
|
||||
ChartsMutex: &sync.Mutex{},
|
||||
ReleasesMutex: &sync.Mutex{},
|
||||
}
|
||||
|
||||
bs := &bytes.Buffer{}
|
||||
|
||||
func() {
|
||||
t.Helper()
|
||||
|
||||
logReader, logWriter := io.Pipe()
|
||||
|
||||
logFlushed := &sync.WaitGroup{}
|
||||
// Ensure all the log is consumed into `bs` by calling `logWriter.Close()` followed by `logFlushed.Wait()`
|
||||
logFlushed.Add(1)
|
||||
go func() {
|
||||
scanner := bufio.NewScanner(logReader)
|
||||
for scanner.Scan() {
|
||||
bs.Write(scanner.Bytes())
|
||||
bs.WriteString("\n")
|
||||
}
|
||||
logFlushed.Done()
|
||||
}()
|
||||
|
||||
defer func() {
|
||||
// This is here to avoid data-trace on bytes buffer `bs` to capture logs
|
||||
if err := logWriter.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
logFlushed.Wait()
|
||||
}()
|
||||
|
||||
logger := helmexec.NewLogger(logWriter, "debug")
|
||||
|
||||
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: "",
|
||||
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
|
||||
}
|
||||
|
||||
destroyErr := app.Destroy(destroyConfig{
|
||||
// if we check log output, concurrency must be 1. otherwise the test becomes non-deterministic.
|
||||
concurrency: tc.concurrency,
|
||||
logger: logger,
|
||||
includeTransitiveNeeds: false,
|
||||
})
|
||||
|
||||
switch {
|
||||
case tc.error == "" && destroyErr != nil:
|
||||
t.Fatalf("unexpected error: %v", destroyErr)
|
||||
case tc.error != "" && destroyErr == nil:
|
||||
t.Fatal("expected error did not occur")
|
||||
case tc.error != "" && destroyErr != nil && tc.error != destroyErr.Error():
|
||||
t.Fatalf("invalid error: expected %q, got %q", tc.error, destroyErr.Error())
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
files := map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
releases:
|
||||
- name: database
|
||||
chart: charts/mysql
|
||||
needs:
|
||||
- logging
|
||||
- name: frontend-v1
|
||||
chart: charts/frontend
|
||||
installed: false
|
||||
needs:
|
||||
- servicemesh
|
||||
- logging
|
||||
- backend-v1
|
||||
- name: frontend-v2
|
||||
chart: charts/frontend
|
||||
needs:
|
||||
- servicemesh
|
||||
- logging
|
||||
- backend-v2
|
||||
- name: frontend-v3
|
||||
chart: charts/frontend
|
||||
needs:
|
||||
- servicemesh
|
||||
- logging
|
||||
- backend-v2
|
||||
- name: backend-v1
|
||||
chart: charts/backend
|
||||
installed: false
|
||||
needs:
|
||||
- servicemesh
|
||||
- logging
|
||||
- database
|
||||
- anotherbackend
|
||||
- name: backend-v2
|
||||
chart: charts/backend
|
||||
needs:
|
||||
- servicemesh
|
||||
- logging
|
||||
- database
|
||||
- anotherbackend
|
||||
- name: anotherbackend
|
||||
chart: charts/anotherbackend
|
||||
needs:
|
||||
- servicemesh
|
||||
- logging
|
||||
- database
|
||||
- name: servicemesh
|
||||
chart: charts/istio
|
||||
needs:
|
||||
- logging
|
||||
- name: logging
|
||||
chart: charts/fluent-bit
|
||||
- name: front-proxy
|
||||
chart: stable/envoy
|
||||
`,
|
||||
}
|
||||
|
||||
filesForTwoReleases := map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
releases:
|
||||
- name: backend-v1
|
||||
chart: charts/backend
|
||||
installed: false
|
||||
- name: frontend-v1
|
||||
chart: charts/frontend
|
||||
needs:
|
||||
- backend-v1
|
||||
`,
|
||||
}
|
||||
|
||||
t.Run("smoke", func(t *testing.T) {
|
||||
//
|
||||
// complex test cases for smoke testing
|
||||
//
|
||||
check(t, testcase{
|
||||
files: files,
|
||||
diffs: map[exectest.DiffKey]error{},
|
||||
lists: map[exectest.ListKey]string{
|
||||
{Filter: "^frontend-v1$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
`,
|
||||
{Filter: "^frontend-v2$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
frontend-v2 4 Fri Nov 1 08:40:07 2019 DEPLOYED frontend-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^frontend-v3$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
frontend-v3 4 Fri Nov 1 08:40:07 2019 DEPLOYED frontend-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^backend-v1$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
`,
|
||||
{Filter: "^backend-v2$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
backend-v2 4 Fri Nov 1 08:40:07 2019 DEPLOYED backend-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^logging$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
logging 4 Fri Nov 1 08:40:07 2019 DEPLOYED fluent-bit-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^front-proxy$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
front-proxy 4 Fri Nov 1 08:40:07 2019 DEPLOYED envoy-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^servicemesh$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
servicemesh 4 Fri Nov 1 08:40:07 2019 DEPLOYED istio-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^database$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
database 4 Fri Nov 1 08:40:07 2019 DEPLOYED mysql-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^anotherbackend$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
anotherbackend 4 Fri Nov 1 08:40:07 2019 DEPLOYED anotherbackend-3.1.0 3.1.0 default
|
||||
`,
|
||||
},
|
||||
// Disable concurrency to avoid in-deterministic result
|
||||
concurrency: 1,
|
||||
upgraded: []exectest.Release{},
|
||||
deleted: []exectest.Release{
|
||||
{Name: "frontend-v3", Flags: []string{}},
|
||||
{Name: "frontend-v2", Flags: []string{}},
|
||||
{Name: "frontend-v1", Flags: []string{}},
|
||||
{Name: "backend-v2", Flags: []string{}},
|
||||
{Name: "backend-v1", Flags: []string{}},
|
||||
{Name: "anotherbackend", Flags: []string{}},
|
||||
{Name: "servicemesh", Flags: []string{}},
|
||||
{Name: "database", Flags: []string{}},
|
||||
{Name: "front-proxy", Flags: []string{}},
|
||||
{Name: "logging", Flags: []string{}},
|
||||
},
|
||||
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: releases:
|
||||
2: - name: database
|
||||
3: chart: charts/mysql
|
||||
4: needs:
|
||||
5: - logging
|
||||
6: - name: frontend-v1
|
||||
7: chart: charts/frontend
|
||||
8: installed: false
|
||||
9: needs:
|
||||
10: - servicemesh
|
||||
11: - logging
|
||||
12: - backend-v1
|
||||
13: - name: frontend-v2
|
||||
14: chart: charts/frontend
|
||||
15: needs:
|
||||
16: - servicemesh
|
||||
17: - logging
|
||||
18: - backend-v2
|
||||
19: - name: frontend-v3
|
||||
20: chart: charts/frontend
|
||||
21: needs:
|
||||
22: - servicemesh
|
||||
23: - logging
|
||||
24: - backend-v2
|
||||
25: - name: backend-v1
|
||||
26: chart: charts/backend
|
||||
27: installed: false
|
||||
28: needs:
|
||||
29: - servicemesh
|
||||
30: - logging
|
||||
31: - database
|
||||
32: - anotherbackend
|
||||
33: - name: backend-v2
|
||||
34: chart: charts/backend
|
||||
35: needs:
|
||||
36: - servicemesh
|
||||
37: - logging
|
||||
38: - database
|
||||
39: - anotherbackend
|
||||
40: - name: anotherbackend
|
||||
41: chart: charts/anotherbackend
|
||||
42: needs:
|
||||
43: - servicemesh
|
||||
44: - logging
|
||||
45: - database
|
||||
46: - name: servicemesh
|
||||
47: chart: charts/istio
|
||||
48: needs:
|
||||
49: - logging
|
||||
50: - name: logging
|
||||
51: chart: charts/fluent-bit
|
||||
52: - name: front-proxy
|
||||
53: chart: stable/envoy
|
||||
54:
|
||||
|
||||
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: releases:
|
||||
2: - name: database
|
||||
3: chart: charts/mysql
|
||||
4: needs:
|
||||
5: - logging
|
||||
6: - name: frontend-v1
|
||||
7: chart: charts/frontend
|
||||
8: installed: false
|
||||
9: needs:
|
||||
10: - servicemesh
|
||||
11: - logging
|
||||
12: - backend-v1
|
||||
13: - name: frontend-v2
|
||||
14: chart: charts/frontend
|
||||
15: needs:
|
||||
16: - servicemesh
|
||||
17: - logging
|
||||
18: - backend-v2
|
||||
19: - name: frontend-v3
|
||||
20: chart: charts/frontend
|
||||
21: needs:
|
||||
22: - servicemesh
|
||||
23: - logging
|
||||
24: - backend-v2
|
||||
25: - name: backend-v1
|
||||
26: chart: charts/backend
|
||||
27: installed: false
|
||||
28: needs:
|
||||
29: - servicemesh
|
||||
30: - logging
|
||||
31: - database
|
||||
32: - anotherbackend
|
||||
33: - name: backend-v2
|
||||
34: chart: charts/backend
|
||||
35: needs:
|
||||
36: - servicemesh
|
||||
37: - logging
|
||||
38: - database
|
||||
39: - anotherbackend
|
||||
40: - name: anotherbackend
|
||||
41: chart: charts/anotherbackend
|
||||
42: needs:
|
||||
43: - servicemesh
|
||||
44: - logging
|
||||
45: - database
|
||||
46: - name: servicemesh
|
||||
47: chart: charts/istio
|
||||
48: needs:
|
||||
49: - logging
|
||||
50: - name: logging
|
||||
51: chart: charts/fluent-bit
|
||||
52: - name: front-proxy
|
||||
53: chart: stable/envoy
|
||||
54:
|
||||
|
||||
merged environment: &{default map[] map[]}
|
||||
10 release(s) found in helmfile.yaml
|
||||
|
||||
processing 5 groups of releases in this order:
|
||||
GROUP RELEASES
|
||||
1 frontend-v3, frontend-v2, frontend-v1
|
||||
2 backend-v2, backend-v1
|
||||
3 anotherbackend
|
||||
4 servicemesh, database
|
||||
5 front-proxy, logging
|
||||
|
||||
processing releases in group 1/5: frontend-v3, frontend-v2, frontend-v1
|
||||
release "frontend-v3" processed
|
||||
release "frontend-v2" processed
|
||||
release "frontend-v1" processed
|
||||
processing releases in group 2/5: backend-v2, backend-v1
|
||||
release "backend-v2" processed
|
||||
release "backend-v1" processed
|
||||
processing releases in group 3/5: anotherbackend
|
||||
release "anotherbackend" processed
|
||||
processing releases in group 4/5: servicemesh, database
|
||||
release "servicemesh" processed
|
||||
release "database" processed
|
||||
processing releases in group 5/5: front-proxy, logging
|
||||
release "front-proxy" processed
|
||||
release "logging" processed
|
||||
|
||||
DELETED RELEASES:
|
||||
NAME
|
||||
frontend-v3
|
||||
frontend-v2
|
||||
frontend-v1
|
||||
backend-v2
|
||||
backend-v1
|
||||
anotherbackend
|
||||
servicemesh
|
||||
database
|
||||
front-proxy
|
||||
logging
|
||||
changing working directory back to "/path/to"
|
||||
`,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("destroy only one release with selector", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
files: files,
|
||||
selectors: []string{"name=logging"},
|
||||
diffs: map[exectest.DiffKey]error{},
|
||||
lists: map[exectest.ListKey]string{
|
||||
{Filter: "^frontend-v1$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
`,
|
||||
{Filter: "^frontend-v2$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
frontend-v2 4 Fri Nov 1 08:40:07 2019 DEPLOYED frontend-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^frontend-v3$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
frontend-v3 4 Fri Nov 1 08:40:07 2019 DEPLOYED frontend-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^backend-v1$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
`,
|
||||
{Filter: "^backend-v2$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
backend-v2 4 Fri Nov 1 08:40:07 2019 DEPLOYED backend-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^logging$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
logging 4 Fri Nov 1 08:40:07 2019 DEPLOYED fluent-bit-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^front-proxy$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
front-proxy 4 Fri Nov 1 08:40:07 2019 DEPLOYED envoy-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^servicemesh$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
servicemesh 4 Fri Nov 1 08:40:07 2019 DEPLOYED istio-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^database$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
database 4 Fri Nov 1 08:40:07 2019 DEPLOYED mysql-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^anotherbackend$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
anotherbackend 4 Fri Nov 1 08:40:07 2019 DEPLOYED anotherbackend-3.1.0 3.1.0 default
|
||||
`,
|
||||
},
|
||||
// Disable concurrency to avoid in-deterministic result
|
||||
concurrency: 1,
|
||||
upgraded: []exectest.Release{},
|
||||
deleted: []exectest.Release{
|
||||
{Name: "logging", Flags: []string{}},
|
||||
},
|
||||
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: releases:
|
||||
2: - name: database
|
||||
3: chart: charts/mysql
|
||||
4: needs:
|
||||
5: - logging
|
||||
6: - name: frontend-v1
|
||||
7: chart: charts/frontend
|
||||
8: installed: false
|
||||
9: needs:
|
||||
10: - servicemesh
|
||||
11: - logging
|
||||
12: - backend-v1
|
||||
13: - name: frontend-v2
|
||||
14: chart: charts/frontend
|
||||
15: needs:
|
||||
16: - servicemesh
|
||||
17: - logging
|
||||
18: - backend-v2
|
||||
19: - name: frontend-v3
|
||||
20: chart: charts/frontend
|
||||
21: needs:
|
||||
22: - servicemesh
|
||||
23: - logging
|
||||
24: - backend-v2
|
||||
25: - name: backend-v1
|
||||
26: chart: charts/backend
|
||||
27: installed: false
|
||||
28: needs:
|
||||
29: - servicemesh
|
||||
30: - logging
|
||||
31: - database
|
||||
32: - anotherbackend
|
||||
33: - name: backend-v2
|
||||
34: chart: charts/backend
|
||||
35: needs:
|
||||
36: - servicemesh
|
||||
37: - logging
|
||||
38: - database
|
||||
39: - anotherbackend
|
||||
40: - name: anotherbackend
|
||||
41: chart: charts/anotherbackend
|
||||
42: needs:
|
||||
43: - servicemesh
|
||||
44: - logging
|
||||
45: - database
|
||||
46: - name: servicemesh
|
||||
47: chart: charts/istio
|
||||
48: needs:
|
||||
49: - logging
|
||||
50: - name: logging
|
||||
51: chart: charts/fluent-bit
|
||||
52: - name: front-proxy
|
||||
53: chart: stable/envoy
|
||||
54:
|
||||
|
||||
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: releases:
|
||||
2: - name: database
|
||||
3: chart: charts/mysql
|
||||
4: needs:
|
||||
5: - logging
|
||||
6: - name: frontend-v1
|
||||
7: chart: charts/frontend
|
||||
8: installed: false
|
||||
9: needs:
|
||||
10: - servicemesh
|
||||
11: - logging
|
||||
12: - backend-v1
|
||||
13: - name: frontend-v2
|
||||
14: chart: charts/frontend
|
||||
15: needs:
|
||||
16: - servicemesh
|
||||
17: - logging
|
||||
18: - backend-v2
|
||||
19: - name: frontend-v3
|
||||
20: chart: charts/frontend
|
||||
21: needs:
|
||||
22: - servicemesh
|
||||
23: - logging
|
||||
24: - backend-v2
|
||||
25: - name: backend-v1
|
||||
26: chart: charts/backend
|
||||
27: installed: false
|
||||
28: needs:
|
||||
29: - servicemesh
|
||||
30: - logging
|
||||
31: - database
|
||||
32: - anotherbackend
|
||||
33: - name: backend-v2
|
||||
34: chart: charts/backend
|
||||
35: needs:
|
||||
36: - servicemesh
|
||||
37: - logging
|
||||
38: - database
|
||||
39: - anotherbackend
|
||||
40: - name: anotherbackend
|
||||
41: chart: charts/anotherbackend
|
||||
42: needs:
|
||||
43: - servicemesh
|
||||
44: - logging
|
||||
45: - database
|
||||
46: - name: servicemesh
|
||||
47: chart: charts/istio
|
||||
48: needs:
|
||||
49: - logging
|
||||
50: - name: logging
|
||||
51: chart: charts/fluent-bit
|
||||
52: - name: front-proxy
|
||||
53: chart: stable/envoy
|
||||
54:
|
||||
|
||||
merged environment: &{default map[] map[]}
|
||||
1 release(s) matching name=logging found in helmfile.yaml
|
||||
|
||||
processing 1 groups of releases in this order:
|
||||
GROUP RELEASES
|
||||
1 logging
|
||||
|
||||
processing releases in group 1/1: logging
|
||||
release "logging" processed
|
||||
|
||||
DELETED RELEASES:
|
||||
NAME
|
||||
logging
|
||||
changing working directory back to "/path/to"
|
||||
`,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("destroy installed but disabled release", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
files: filesForTwoReleases,
|
||||
diffs: map[exectest.DiffKey]error{},
|
||||
lists: map[exectest.ListKey]string{
|
||||
{Filter: "^frontend-v1$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
`,
|
||||
{Filter: "^backend-v1$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
`,
|
||||
},
|
||||
// Disable concurrency to avoid in-deterministic result
|
||||
concurrency: 1,
|
||||
upgraded: []exectest.Release{},
|
||||
deleted: []exectest.Release{
|
||||
{Name: "frontend-v1", Flags: []string{}},
|
||||
},
|
||||
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: releases:
|
||||
2: - name: backend-v1
|
||||
3: chart: charts/backend
|
||||
4: installed: false
|
||||
5: - name: frontend-v1
|
||||
6: chart: charts/frontend
|
||||
7: needs:
|
||||
8: - backend-v1
|
||||
9:
|
||||
|
||||
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: releases:
|
||||
2: - name: backend-v1
|
||||
3: chart: charts/backend
|
||||
4: installed: false
|
||||
5: - name: frontend-v1
|
||||
6: chart: charts/frontend
|
||||
7: needs:
|
||||
8: - backend-v1
|
||||
9:
|
||||
|
||||
merged environment: &{default map[] map[]}
|
||||
2 release(s) found in helmfile.yaml
|
||||
|
||||
processing 2 groups of releases in this order:
|
||||
GROUP RELEASES
|
||||
1 frontend-v1
|
||||
2 backend-v1
|
||||
|
||||
processing releases in group 1/2: frontend-v1
|
||||
release "frontend-v1" processed
|
||||
processing releases in group 2/2: backend-v1
|
||||
release "backend-v1" processed
|
||||
|
||||
DELETED RELEASES:
|
||||
NAME
|
||||
frontend-v1
|
||||
backend-v1
|
||||
changing working directory back to "/path/to"
|
||||
`,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("helm3", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
helm3: true,
|
||||
files: filesForTwoReleases,
|
||||
diffs: map[exectest.DiffKey]error{},
|
||||
lists: map[exectest.ListKey]string{
|
||||
{Filter: "^frontend-v1$", Flags: helmV3ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
`,
|
||||
{Filter: "^backend-v1$", Flags: helmV3ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
`,
|
||||
},
|
||||
// Disable concurrency to avoid in-deterministic result
|
||||
concurrency: 1,
|
||||
upgraded: []exectest.Release{},
|
||||
deleted: []exectest.Release{
|
||||
{Name: "frontend-v1", Flags: []string{}},
|
||||
},
|
||||
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: releases:
|
||||
2: - name: backend-v1
|
||||
3: chart: charts/backend
|
||||
4: installed: false
|
||||
5: - name: frontend-v1
|
||||
6: chart: charts/frontend
|
||||
7: needs:
|
||||
8: - backend-v1
|
||||
9:
|
||||
|
||||
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: releases:
|
||||
2: - name: backend-v1
|
||||
3: chart: charts/backend
|
||||
4: installed: false
|
||||
5: - name: frontend-v1
|
||||
6: chart: charts/frontend
|
||||
7: needs:
|
||||
8: - backend-v1
|
||||
9:
|
||||
|
||||
merged environment: &{default map[] map[]}
|
||||
2 release(s) found in helmfile.yaml
|
||||
|
||||
processing 2 groups of releases in this order:
|
||||
GROUP RELEASES
|
||||
1 frontend-v1
|
||||
2 backend-v1
|
||||
|
||||
processing releases in group 1/2: frontend-v1
|
||||
release "frontend-v1" processed
|
||||
processing releases in group 2/2: backend-v1
|
||||
release "backend-v1" processed
|
||||
|
||||
DELETED RELEASES:
|
||||
NAME
|
||||
frontend-v1
|
||||
backend-v1
|
||||
changing working directory back to "/path/to"
|
||||
`,
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,815 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/variantdev/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"
|
||||
)
|
||||
|
||||
const (
|
||||
helmV2ListFlags = "--kube-contextdefault--deleting--deployed--failed--pending"
|
||||
helmV2ListFlagsWithoutKubeContext = "--deleting--deployed--failed--pending"
|
||||
helmV3ListFlags = "--kube-contextdefault--uninstalling--deployed--failed--pending"
|
||||
helmV3ListFlagsWithoutKubeContext = "--uninstalling--deployed--failed--pending"
|
||||
)
|
||||
|
||||
type destroyConfig struct {
|
||||
args string
|
||||
concurrency int
|
||||
interactive bool
|
||||
skipDeps bool
|
||||
logger *zap.SugaredLogger
|
||||
includeTransitiveNeeds bool
|
||||
}
|
||||
|
||||
func (d destroyConfig) Args() string {
|
||||
return d.args
|
||||
}
|
||||
|
||||
func (d destroyConfig) Interactive() bool {
|
||||
return d.interactive
|
||||
}
|
||||
|
||||
func (d destroyConfig) Logger() *zap.SugaredLogger {
|
||||
return d.logger
|
||||
}
|
||||
|
||||
func (d destroyConfig) Concurrency() int {
|
||||
return d.concurrency
|
||||
}
|
||||
|
||||
func (d destroyConfig) SkipDeps() bool {
|
||||
return d.skipDeps
|
||||
}
|
||||
|
||||
func (d destroyConfig) IncludeTransitiveNeeds() bool {
|
||||
return d.includeTransitiveNeeds
|
||||
}
|
||||
|
||||
func TestDestroy(t *testing.T) {
|
||||
type testcase struct {
|
||||
helm3 bool
|
||||
ns string
|
||||
concurrency int
|
||||
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{
|
||||
Helm3: tc.helm3,
|
||||
FailOnUnexpectedList: true,
|
||||
FailOnUnexpectedDiff: true,
|
||||
Lists: tc.lists,
|
||||
Diffs: tc.diffs,
|
||||
DiffMutex: &sync.Mutex{},
|
||||
ChartsMutex: &sync.Mutex{},
|
||||
ReleasesMutex: &sync.Mutex{},
|
||||
}
|
||||
|
||||
bs := &bytes.Buffer{}
|
||||
|
||||
func() {
|
||||
t.Helper()
|
||||
|
||||
logReader, logWriter := io.Pipe()
|
||||
|
||||
logFlushed := &sync.WaitGroup{}
|
||||
// Ensure all the log is consumed into `bs` by calling `logWriter.Close()` followed by `logFlushed.Wait()`
|
||||
logFlushed.Add(1)
|
||||
go func() {
|
||||
scanner := bufio.NewScanner(logReader)
|
||||
for scanner.Scan() {
|
||||
bs.Write(scanner.Bytes())
|
||||
bs.WriteString("\n")
|
||||
}
|
||||
logFlushed.Done()
|
||||
}()
|
||||
|
||||
defer func() {
|
||||
// This is here to avoid data-trace on bytes buffer `bs` to capture logs
|
||||
if err := logWriter.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
logFlushed.Wait()
|
||||
}()
|
||||
|
||||
logger := helmexec.NewLogger(logWriter, "debug")
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
destroyErr := app.Destroy(destroyConfig{
|
||||
// if we check log output, concurrency must be 1. otherwise the test becomes non-deterministic.
|
||||
concurrency: tc.concurrency,
|
||||
logger: logger,
|
||||
})
|
||||
|
||||
switch {
|
||||
case tc.error == "" && destroyErr != nil:
|
||||
t.Fatalf("unexpected error: %v", destroyErr)
|
||||
case tc.error != "" && destroyErr == nil:
|
||||
t.Fatal("expected error did not occur")
|
||||
case tc.error != "" && destroyErr != nil && tc.error != destroyErr.Error():
|
||||
t.Fatalf("invalid error: expected %q, got %q", tc.error, destroyErr.Error())
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
files := map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
releases:
|
||||
- name: database
|
||||
chart: charts/mysql
|
||||
needs:
|
||||
- logging
|
||||
- name: frontend-v1
|
||||
chart: charts/frontend
|
||||
installed: false
|
||||
needs:
|
||||
- servicemesh
|
||||
- logging
|
||||
- backend-v1
|
||||
- name: frontend-v2
|
||||
chart: charts/frontend
|
||||
needs:
|
||||
- servicemesh
|
||||
- logging
|
||||
- backend-v2
|
||||
- name: frontend-v3
|
||||
chart: charts/frontend
|
||||
needs:
|
||||
- servicemesh
|
||||
- logging
|
||||
- backend-v2
|
||||
- name: backend-v1
|
||||
chart: charts/backend
|
||||
installed: false
|
||||
needs:
|
||||
- servicemesh
|
||||
- logging
|
||||
- database
|
||||
- anotherbackend
|
||||
- name: backend-v2
|
||||
chart: charts/backend
|
||||
needs:
|
||||
- servicemesh
|
||||
- logging
|
||||
- database
|
||||
- anotherbackend
|
||||
- name: anotherbackend
|
||||
chart: charts/anotherbackend
|
||||
needs:
|
||||
- servicemesh
|
||||
- logging
|
||||
- database
|
||||
- name: servicemesh
|
||||
chart: charts/istio
|
||||
needs:
|
||||
- logging
|
||||
- name: logging
|
||||
chart: charts/fluent-bit
|
||||
- name: front-proxy
|
||||
chart: stable/envoy
|
||||
`,
|
||||
}
|
||||
|
||||
filesForTwoReleases := map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
releases:
|
||||
- name: backend-v1
|
||||
chart: charts/backend
|
||||
installed: false
|
||||
- name: frontend-v1
|
||||
chart: charts/frontend
|
||||
needs:
|
||||
- backend-v1
|
||||
`,
|
||||
}
|
||||
|
||||
t.Run("smoke", func(t *testing.T) {
|
||||
//
|
||||
// complex test cases for smoke testing
|
||||
//
|
||||
check(t, testcase{
|
||||
files: files,
|
||||
diffs: map[exectest.DiffKey]error{},
|
||||
lists: map[exectest.ListKey]string{
|
||||
{Filter: "^frontend-v1$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
`,
|
||||
{Filter: "^frontend-v2$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
frontend-v2 4 Fri Nov 1 08:40:07 2019 DEPLOYED frontend-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^frontend-v3$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
frontend-v3 4 Fri Nov 1 08:40:07 2019 DEPLOYED frontend-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^backend-v1$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
`,
|
||||
{Filter: "^backend-v2$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
backend-v2 4 Fri Nov 1 08:40:07 2019 DEPLOYED backend-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^logging$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
logging 4 Fri Nov 1 08:40:07 2019 DEPLOYED fluent-bit-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^front-proxy$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
front-proxy 4 Fri Nov 1 08:40:07 2019 DEPLOYED envoy-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^servicemesh$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
servicemesh 4 Fri Nov 1 08:40:07 2019 DEPLOYED istio-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^database$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
database 4 Fri Nov 1 08:40:07 2019 DEPLOYED mysql-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^anotherbackend$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
anotherbackend 4 Fri Nov 1 08:40:07 2019 DEPLOYED anotherbackend-3.1.0 3.1.0 default
|
||||
`,
|
||||
},
|
||||
// Disable concurrency to avoid in-deterministic result
|
||||
concurrency: 1,
|
||||
upgraded: []exectest.Release{},
|
||||
deleted: []exectest.Release{
|
||||
{Name: "frontend-v3", Flags: []string{}},
|
||||
{Name: "frontend-v2", Flags: []string{}},
|
||||
{Name: "frontend-v1", Flags: []string{}},
|
||||
{Name: "backend-v2", Flags: []string{}},
|
||||
{Name: "backend-v1", Flags: []string{}},
|
||||
{Name: "anotherbackend", Flags: []string{}},
|
||||
{Name: "servicemesh", Flags: []string{}},
|
||||
{Name: "database", Flags: []string{}},
|
||||
{Name: "front-proxy", Flags: []string{}},
|
||||
{Name: "logging", Flags: []string{}},
|
||||
},
|
||||
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: releases:
|
||||
2: - name: database
|
||||
3: chart: charts/mysql
|
||||
4: needs:
|
||||
5: - logging
|
||||
6: - name: frontend-v1
|
||||
7: chart: charts/frontend
|
||||
8: installed: false
|
||||
9: needs:
|
||||
10: - servicemesh
|
||||
11: - logging
|
||||
12: - backend-v1
|
||||
13: - name: frontend-v2
|
||||
14: chart: charts/frontend
|
||||
15: needs:
|
||||
16: - servicemesh
|
||||
17: - logging
|
||||
18: - backend-v2
|
||||
19: - name: frontend-v3
|
||||
20: chart: charts/frontend
|
||||
21: needs:
|
||||
22: - servicemesh
|
||||
23: - logging
|
||||
24: - backend-v2
|
||||
25: - name: backend-v1
|
||||
26: chart: charts/backend
|
||||
27: installed: false
|
||||
28: needs:
|
||||
29: - servicemesh
|
||||
30: - logging
|
||||
31: - database
|
||||
32: - anotherbackend
|
||||
33: - name: backend-v2
|
||||
34: chart: charts/backend
|
||||
35: needs:
|
||||
36: - servicemesh
|
||||
37: - logging
|
||||
38: - database
|
||||
39: - anotherbackend
|
||||
40: - name: anotherbackend
|
||||
41: chart: charts/anotherbackend
|
||||
42: needs:
|
||||
43: - servicemesh
|
||||
44: - logging
|
||||
45: - database
|
||||
46: - name: servicemesh
|
||||
47: chart: charts/istio
|
||||
48: needs:
|
||||
49: - logging
|
||||
50: - name: logging
|
||||
51: chart: charts/fluent-bit
|
||||
52: - name: front-proxy
|
||||
53: chart: stable/envoy
|
||||
54:
|
||||
|
||||
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: releases:
|
||||
2: - name: database
|
||||
3: chart: charts/mysql
|
||||
4: needs:
|
||||
5: - logging
|
||||
6: - name: frontend-v1
|
||||
7: chart: charts/frontend
|
||||
8: installed: false
|
||||
9: needs:
|
||||
10: - servicemesh
|
||||
11: - logging
|
||||
12: - backend-v1
|
||||
13: - name: frontend-v2
|
||||
14: chart: charts/frontend
|
||||
15: needs:
|
||||
16: - servicemesh
|
||||
17: - logging
|
||||
18: - backend-v2
|
||||
19: - name: frontend-v3
|
||||
20: chart: charts/frontend
|
||||
21: needs:
|
||||
22: - servicemesh
|
||||
23: - logging
|
||||
24: - backend-v2
|
||||
25: - name: backend-v1
|
||||
26: chart: charts/backend
|
||||
27: installed: false
|
||||
28: needs:
|
||||
29: - servicemesh
|
||||
30: - logging
|
||||
31: - database
|
||||
32: - anotherbackend
|
||||
33: - name: backend-v2
|
||||
34: chart: charts/backend
|
||||
35: needs:
|
||||
36: - servicemesh
|
||||
37: - logging
|
||||
38: - database
|
||||
39: - anotherbackend
|
||||
40: - name: anotherbackend
|
||||
41: chart: charts/anotherbackend
|
||||
42: needs:
|
||||
43: - servicemesh
|
||||
44: - logging
|
||||
45: - database
|
||||
46: - name: servicemesh
|
||||
47: chart: charts/istio
|
||||
48: needs:
|
||||
49: - logging
|
||||
50: - name: logging
|
||||
51: chart: charts/fluent-bit
|
||||
52: - name: front-proxy
|
||||
53: chart: stable/envoy
|
||||
54:
|
||||
|
||||
merged environment: &{default map[] map[]}
|
||||
10 release(s) found in helmfile.yaml
|
||||
|
||||
processing 5 groups of releases in this order:
|
||||
GROUP RELEASES
|
||||
1 default//frontend-v3, default//frontend-v2, default//frontend-v1
|
||||
2 default//backend-v2, default//backend-v1
|
||||
3 default//anotherbackend
|
||||
4 default//servicemesh, default//database
|
||||
5 default//front-proxy, default//logging
|
||||
|
||||
processing releases in group 1/5: default//frontend-v3, default//frontend-v2, default//frontend-v1
|
||||
release "frontend-v3" processed
|
||||
release "frontend-v2" processed
|
||||
release "frontend-v1" processed
|
||||
processing releases in group 2/5: default//backend-v2, default//backend-v1
|
||||
release "backend-v2" processed
|
||||
release "backend-v1" processed
|
||||
processing releases in group 3/5: default//anotherbackend
|
||||
release "anotherbackend" processed
|
||||
processing releases in group 4/5: default//servicemesh, default//database
|
||||
release "servicemesh" processed
|
||||
release "database" processed
|
||||
processing releases in group 5/5: default//front-proxy, default//logging
|
||||
release "front-proxy" processed
|
||||
release "logging" processed
|
||||
|
||||
DELETED RELEASES:
|
||||
NAME
|
||||
frontend-v3
|
||||
frontend-v2
|
||||
frontend-v1
|
||||
backend-v2
|
||||
backend-v1
|
||||
anotherbackend
|
||||
servicemesh
|
||||
database
|
||||
front-proxy
|
||||
logging
|
||||
changing working directory back to "/path/to"
|
||||
`,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("destroy only one release with selector", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
files: files,
|
||||
selectors: []string{"name=logging"},
|
||||
diffs: map[exectest.DiffKey]error{},
|
||||
lists: map[exectest.ListKey]string{
|
||||
{Filter: "^frontend-v1$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
`,
|
||||
{Filter: "^frontend-v2$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
frontend-v2 4 Fri Nov 1 08:40:07 2019 DEPLOYED frontend-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^frontend-v3$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
frontend-v3 4 Fri Nov 1 08:40:07 2019 DEPLOYED frontend-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^backend-v1$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
`,
|
||||
{Filter: "^backend-v2$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
backend-v2 4 Fri Nov 1 08:40:07 2019 DEPLOYED backend-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^logging$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
logging 4 Fri Nov 1 08:40:07 2019 DEPLOYED fluent-bit-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^front-proxy$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
front-proxy 4 Fri Nov 1 08:40:07 2019 DEPLOYED envoy-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^servicemesh$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
servicemesh 4 Fri Nov 1 08:40:07 2019 DEPLOYED istio-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^database$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
database 4 Fri Nov 1 08:40:07 2019 DEPLOYED mysql-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^anotherbackend$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
anotherbackend 4 Fri Nov 1 08:40:07 2019 DEPLOYED anotherbackend-3.1.0 3.1.0 default
|
||||
`,
|
||||
},
|
||||
// Disable concurrency to avoid in-deterministic result
|
||||
concurrency: 1,
|
||||
upgraded: []exectest.Release{},
|
||||
deleted: []exectest.Release{
|
||||
{Name: "logging", Flags: []string{}},
|
||||
},
|
||||
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: releases:
|
||||
2: - name: database
|
||||
3: chart: charts/mysql
|
||||
4: needs:
|
||||
5: - logging
|
||||
6: - name: frontend-v1
|
||||
7: chart: charts/frontend
|
||||
8: installed: false
|
||||
9: needs:
|
||||
10: - servicemesh
|
||||
11: - logging
|
||||
12: - backend-v1
|
||||
13: - name: frontend-v2
|
||||
14: chart: charts/frontend
|
||||
15: needs:
|
||||
16: - servicemesh
|
||||
17: - logging
|
||||
18: - backend-v2
|
||||
19: - name: frontend-v3
|
||||
20: chart: charts/frontend
|
||||
21: needs:
|
||||
22: - servicemesh
|
||||
23: - logging
|
||||
24: - backend-v2
|
||||
25: - name: backend-v1
|
||||
26: chart: charts/backend
|
||||
27: installed: false
|
||||
28: needs:
|
||||
29: - servicemesh
|
||||
30: - logging
|
||||
31: - database
|
||||
32: - anotherbackend
|
||||
33: - name: backend-v2
|
||||
34: chart: charts/backend
|
||||
35: needs:
|
||||
36: - servicemesh
|
||||
37: - logging
|
||||
38: - database
|
||||
39: - anotherbackend
|
||||
40: - name: anotherbackend
|
||||
41: chart: charts/anotherbackend
|
||||
42: needs:
|
||||
43: - servicemesh
|
||||
44: - logging
|
||||
45: - database
|
||||
46: - name: servicemesh
|
||||
47: chart: charts/istio
|
||||
48: needs:
|
||||
49: - logging
|
||||
50: - name: logging
|
||||
51: chart: charts/fluent-bit
|
||||
52: - name: front-proxy
|
||||
53: chart: stable/envoy
|
||||
54:
|
||||
|
||||
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: releases:
|
||||
2: - name: database
|
||||
3: chart: charts/mysql
|
||||
4: needs:
|
||||
5: - logging
|
||||
6: - name: frontend-v1
|
||||
7: chart: charts/frontend
|
||||
8: installed: false
|
||||
9: needs:
|
||||
10: - servicemesh
|
||||
11: - logging
|
||||
12: - backend-v1
|
||||
13: - name: frontend-v2
|
||||
14: chart: charts/frontend
|
||||
15: needs:
|
||||
16: - servicemesh
|
||||
17: - logging
|
||||
18: - backend-v2
|
||||
19: - name: frontend-v3
|
||||
20: chart: charts/frontend
|
||||
21: needs:
|
||||
22: - servicemesh
|
||||
23: - logging
|
||||
24: - backend-v2
|
||||
25: - name: backend-v1
|
||||
26: chart: charts/backend
|
||||
27: installed: false
|
||||
28: needs:
|
||||
29: - servicemesh
|
||||
30: - logging
|
||||
31: - database
|
||||
32: - anotherbackend
|
||||
33: - name: backend-v2
|
||||
34: chart: charts/backend
|
||||
35: needs:
|
||||
36: - servicemesh
|
||||
37: - logging
|
||||
38: - database
|
||||
39: - anotherbackend
|
||||
40: - name: anotherbackend
|
||||
41: chart: charts/anotherbackend
|
||||
42: needs:
|
||||
43: - servicemesh
|
||||
44: - logging
|
||||
45: - database
|
||||
46: - name: servicemesh
|
||||
47: chart: charts/istio
|
||||
48: needs:
|
||||
49: - logging
|
||||
50: - name: logging
|
||||
51: chart: charts/fluent-bit
|
||||
52: - name: front-proxy
|
||||
53: chart: stable/envoy
|
||||
54:
|
||||
|
||||
merged environment: &{default map[] map[]}
|
||||
1 release(s) matching name=logging found in helmfile.yaml
|
||||
|
||||
processing 1 groups of releases in this order:
|
||||
GROUP RELEASES
|
||||
1 default//logging
|
||||
|
||||
processing releases in group 1/1: default//logging
|
||||
release "logging" processed
|
||||
|
||||
DELETED RELEASES:
|
||||
NAME
|
||||
logging
|
||||
changing working directory back to "/path/to"
|
||||
`,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("destroy installed but disabled release", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
files: filesForTwoReleases,
|
||||
diffs: map[exectest.DiffKey]error{},
|
||||
lists: map[exectest.ListKey]string{
|
||||
{Filter: "^frontend-v1$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
`,
|
||||
{Filter: "^backend-v1$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
`,
|
||||
},
|
||||
// Disable concurrency to avoid in-deterministic result
|
||||
concurrency: 1,
|
||||
upgraded: []exectest.Release{},
|
||||
deleted: []exectest.Release{
|
||||
{Name: "frontend-v1", Flags: []string{}},
|
||||
},
|
||||
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: releases:
|
||||
2: - name: backend-v1
|
||||
3: chart: charts/backend
|
||||
4: installed: false
|
||||
5: - name: frontend-v1
|
||||
6: chart: charts/frontend
|
||||
7: needs:
|
||||
8: - backend-v1
|
||||
9:
|
||||
|
||||
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: releases:
|
||||
2: - name: backend-v1
|
||||
3: chart: charts/backend
|
||||
4: installed: false
|
||||
5: - name: frontend-v1
|
||||
6: chart: charts/frontend
|
||||
7: needs:
|
||||
8: - backend-v1
|
||||
9:
|
||||
|
||||
merged environment: &{default map[] map[]}
|
||||
2 release(s) found in helmfile.yaml
|
||||
|
||||
processing 2 groups of releases in this order:
|
||||
GROUP RELEASES
|
||||
1 default//frontend-v1
|
||||
2 default//backend-v1
|
||||
|
||||
processing releases in group 1/2: default//frontend-v1
|
||||
release "frontend-v1" processed
|
||||
processing releases in group 2/2: default//backend-v1
|
||||
release "backend-v1" processed
|
||||
|
||||
DELETED RELEASES:
|
||||
NAME
|
||||
frontend-v1
|
||||
backend-v1
|
||||
changing working directory back to "/path/to"
|
||||
`,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("helm3", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
helm3: true,
|
||||
files: filesForTwoReleases,
|
||||
diffs: map[exectest.DiffKey]error{},
|
||||
lists: map[exectest.ListKey]string{
|
||||
{Filter: "^frontend-v1$", Flags: helmV3ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
`,
|
||||
{Filter: "^backend-v1$", Flags: helmV3ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
`,
|
||||
},
|
||||
// Disable concurrency to avoid in-deterministic result
|
||||
concurrency: 1,
|
||||
upgraded: []exectest.Release{},
|
||||
deleted: []exectest.Release{
|
||||
{Name: "frontend-v1", Flags: []string{}},
|
||||
},
|
||||
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: releases:
|
||||
2: - name: backend-v1
|
||||
3: chart: charts/backend
|
||||
4: installed: false
|
||||
5: - name: frontend-v1
|
||||
6: chart: charts/frontend
|
||||
7: needs:
|
||||
8: - backend-v1
|
||||
9:
|
||||
|
||||
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: releases:
|
||||
2: - name: backend-v1
|
||||
3: chart: charts/backend
|
||||
4: installed: false
|
||||
5: - name: frontend-v1
|
||||
6: chart: charts/frontend
|
||||
7: needs:
|
||||
8: - backend-v1
|
||||
9:
|
||||
|
||||
merged environment: &{default map[] map[]}
|
||||
2 release(s) found in helmfile.yaml
|
||||
|
||||
processing 2 groups of releases in this order:
|
||||
GROUP RELEASES
|
||||
1 default//frontend-v1
|
||||
2 default//backend-v1
|
||||
|
||||
processing releases in group 1/2: default//frontend-v1
|
||||
release "frontend-v1" processed
|
||||
processing releases in group 2/2: default//backend-v1
|
||||
release "backend-v1" processed
|
||||
|
||||
DELETED RELEASES:
|
||||
NAME
|
||||
frontend-v1
|
||||
backend-v1
|
||||
changing working directory back to "/path/to"
|
||||
`,
|
||||
})
|
||||
})
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user