Compare commits
962 Commits
encrypt-in
...
v0.38.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f58b82fb4e | ||
|
|
be361a44d5 | ||
|
|
12e065cc43 | ||
|
|
f9e69089ea | ||
|
|
2eced064dd | ||
|
|
7be91884b7 | ||
|
|
b2b610b55e | ||
|
|
21a943e6f9 | ||
|
|
cb1b117d17 | ||
|
|
96f177b101 | ||
|
|
7621418b72 | ||
|
|
7a94a3ac71 | ||
|
|
58b799fa83 | ||
|
|
38635e0ec5 | ||
|
|
d79e49f80b | ||
|
|
5e44b7b1b3 | ||
|
|
131c05d9c7 | ||
|
|
3ac8d54a30 | ||
|
|
c605f9a44f | ||
|
|
eefd47d701 | ||
|
|
0014bc4c43 | ||
|
|
ccf358f0ca | ||
|
|
bd284ab28b | ||
|
|
bed46f6b68 | ||
|
|
fdd3fd1d06 | ||
|
|
e81201b8cb | ||
|
|
2c1085d9ce | ||
|
|
49eb1c5444 | ||
|
|
81dc4adc69 | ||
|
|
8e23989418 | ||
|
|
f532bd2d48 | ||
|
|
da9df03675 | ||
|
|
91965ddfc9 | ||
|
|
0bd78ca80c | ||
|
|
96b96ac78e | ||
|
|
a9a63b8423 | ||
|
|
8abb93e831 | ||
|
|
645f9df4f0 | ||
|
|
6924a16ac7 | ||
|
|
dc2a4c267b | ||
|
|
d5e5a26f5c | ||
|
|
df5ac34c9b | ||
|
|
319dbad795 | ||
|
|
28feb8b1d7 | ||
|
|
f4d898cb92 | ||
|
|
75b5b0fd3c | ||
|
|
6ee3439462 | ||
|
|
4eda5a7ccd | ||
|
|
ad94037516 | ||
|
|
882fb35601 | ||
|
|
48f10a6a20 | ||
|
|
2c35880cbf | ||
|
|
c8af9ced89 | ||
|
|
f89525f8bd | ||
|
|
ad11fbcd00 | ||
|
|
9db661ae63 | ||
|
|
fff5cd50f0 | ||
|
|
b3b50cf503 | ||
|
|
cbebad9586 | ||
|
|
c01023d8f8 | ||
|
|
df610c3cca | ||
|
|
3b7c40bbb3 | ||
|
|
8674f31874 | ||
|
|
b518aad5ac | ||
|
|
12959dec88 | ||
|
|
e381da6a08 | ||
|
|
b004fbfc41 | ||
|
|
8c56ccc5b0 | ||
|
|
c8051eeeed | ||
|
|
5d944b69df | ||
|
|
1fca76c4a8 | ||
|
|
d0e6fcad3f | ||
|
|
d4ba6c4f44 | ||
|
|
35e1b5cbb9 | ||
|
|
f8da3a1b44 | ||
|
|
4ea253220a | ||
|
|
0a5048a56b | ||
|
|
a06652a374 | ||
|
|
86e3991998 | ||
|
|
d9102150cf | ||
|
|
fd08bae1c7 | ||
|
|
4b2af2ede2 | ||
|
|
c6be0b9389 | ||
|
|
6ccdfa074f | ||
|
|
8801029d95 | ||
|
|
5faf6ebadc | ||
|
|
f92d708051 | ||
|
|
76c31c6303 | ||
|
|
cf8ac4dd0e | ||
|
|
879041677c | ||
|
|
cac36365ae | ||
|
|
2c12385344 | ||
|
|
fa217b8775 | ||
|
|
6f7cdde1ba | ||
|
|
da9cc00a56 | ||
|
|
161c90eb8f | ||
|
|
ad5daee004 | ||
|
|
35ea91c111 | ||
|
|
6763490ef6 | ||
|
|
93382f65bb | ||
|
|
190c732c3a | ||
|
|
8bd13edc75 | ||
|
|
98e0774f56 | ||
|
|
c3a44e890d | ||
|
|
a4734d7e30 | ||
|
|
2c267c95e5 | ||
|
|
78f9a6214c | ||
|
|
7ee90a34e5 | ||
|
|
1a6b09afb4 | ||
|
|
c7e158aaa7 | ||
|
|
98c7afd69c | ||
|
|
f3da59e5af | ||
|
|
a17210f387 | ||
|
|
443212d3da | ||
|
|
7a5f60e23f | ||
|
|
7a1d978339 | ||
|
|
6c7ef96354 | ||
|
|
d2e7a37eb4 | ||
|
|
1d8105247a | ||
|
|
6d110cdfb1 | ||
|
|
d015895caa | ||
|
|
64e76a23c6 | ||
|
|
f5006aa239 | ||
|
|
4bd06771ae | ||
|
|
4643f8383e | ||
|
|
b82759b35a | ||
|
|
0343575146 | ||
|
|
e7847b75db | ||
|
|
bb1078d610 | ||
|
|
6f6c097980 | ||
|
|
73692df272 | ||
|
|
138cba6e57 | ||
|
|
2abf932ee4 | ||
|
|
939a75115c | ||
|
|
9f41efb6f7 | ||
|
|
c3d7cad53e | ||
|
|
463f9fbc64 | ||
|
|
4a51b111e6 | ||
|
|
63ebd7fd09 | ||
|
|
c31367909e | ||
|
|
0f0649a674 | ||
|
|
09cbf348a7 | ||
|
|
287bc520b1 | ||
|
|
65a2ceec5c | ||
|
|
516399bf81 | ||
|
|
4ea70765af | ||
|
|
d6372e396b | ||
|
|
7b20ad5dd2 | ||
|
|
3d962136a8 | ||
|
|
f3386505cf | ||
|
|
f4c8da35e8 | ||
|
|
cc3f2c7bde | ||
|
|
80b87729b6 | ||
|
|
2282223592 | ||
|
|
f6c96aea48 | ||
|
|
1fa48bf916 | ||
|
|
d49b77c8d2 | ||
|
|
91132e9c87 | ||
|
|
4680abe951 | ||
|
|
2963708a6c | ||
|
|
1f57cf3d31 | ||
|
|
80611ec70e | ||
|
|
d37bb42995 | ||
|
|
1bf63a94c2 | ||
|
|
cad251444c | ||
|
|
358c6d38b7 | ||
|
|
b8fd46d0df | ||
|
|
6a1ba3c545 | ||
|
|
33a874800b | ||
|
|
f417352370 | ||
|
|
72d90b5692 | ||
|
|
d7dadb4425 | ||
|
|
348408e16e | ||
|
|
04de52044a | ||
|
|
45a00a0170 | ||
|
|
1ac380a7f9 | ||
|
|
2971d34a13 | ||
|
|
90f0d81532 | ||
|
|
d5262404f3 | ||
|
|
03c3cb860a | ||
|
|
a1faa1d965 | ||
|
|
c40d290e46 | ||
|
|
5106a71e6a | ||
|
|
491acf57ad | ||
|
|
0694a9582f | ||
|
|
0c817378cf | ||
|
|
ec2aa13165 | ||
|
|
c921cf0d54 | ||
|
|
11dd0d918c | ||
|
|
467969de0f | ||
|
|
bdc5ae4573 | ||
|
|
1eb4b67013 | ||
|
|
e777947539 | ||
|
|
70b906cae2 | ||
|
|
c57afa1e56 | ||
|
|
73668d19d9 | ||
|
|
82f847e21d | ||
|
|
753b2e6eda | ||
|
|
7b95e90a33 | ||
|
|
7824229d7b | ||
|
|
20557f9f15 | ||
|
|
6430f2b4b0 | ||
|
|
92e136ed54 | ||
|
|
e79b008878 | ||
|
|
43cdea01d6 | ||
|
|
6ddaedb4fc | ||
|
|
b4fef0a6b9 | ||
|
|
735ebd3336 | ||
|
|
a5a9158a24 | ||
|
|
93fdd795da | ||
|
|
18c944d18a | ||
|
|
2c9ef85f6d | ||
|
|
80669d71ef | ||
|
|
b993d17148 | ||
|
|
c454dd481b | ||
|
|
07de9d9ffe | ||
|
|
9f26b09a06 | ||
|
|
ad0f3373b6 | ||
|
|
f880cce4f9 | ||
|
|
8a0fd6ddf9 | ||
|
|
c56f338b12 | ||
|
|
463d241a91 | ||
|
|
db0920ba32 | ||
|
|
16d3180e42 | ||
|
|
81d2ad8245 | ||
|
|
96d1c1b2bd | ||
|
|
545949c67f | ||
|
|
342bb81687 | ||
|
|
60b483569d | ||
|
|
b7a2fb4be0 | ||
|
|
5bdc083ce2 | ||
|
|
787b6953c8 | ||
|
|
40717fa4f4 | ||
|
|
899a1fffca | ||
|
|
02b38ac8e0 | ||
|
|
5dcd599612 | ||
|
|
854ec02823 | ||
|
|
9386b9e0c3 | ||
|
|
f2d749069e | ||
|
|
d4169aa4dd | ||
|
|
c06072d5cf | ||
|
|
7e2d235f53 | ||
|
|
b810aea6cc | ||
|
|
75a879c770 | ||
|
|
d4c5a137a1 | ||
|
|
d4718f6ff4 | ||
|
|
ac9b3d193d | ||
|
|
7c7e76f9f0 | ||
|
|
08401f62b2 | ||
|
|
69e26ca1d9 | ||
|
|
41aac68193 | ||
|
|
fcd38c9395 | ||
|
|
fe4b65972a | ||
|
|
4c576bf599 | ||
|
|
70d30fd52e | ||
|
|
803104578f | ||
|
|
030b6bc77c | ||
|
|
009413affd | ||
|
|
9e76787e9f | ||
|
|
b78bbd5b9d | ||
|
|
3e15e83926 | ||
|
|
1b327e9d4e | ||
|
|
7dd736954b | ||
|
|
6b98590461 | ||
|
|
8049634e4d | ||
|
|
adc7981f22 | ||
|
|
30e5389d02 | ||
|
|
b6a78f42ea | ||
|
|
e4fb8e75f9 | ||
|
|
2f35367a7f | ||
|
|
2d8db4f20d | ||
|
|
12a491f538 | ||
|
|
9503ecafb1 | ||
|
|
e927d39a27 | ||
|
|
ac50aea21f | ||
|
|
c45536723c | ||
|
|
fb1de8c649 | ||
|
|
e1c082e5ac | ||
|
|
1889b64b4e | ||
|
|
0cfdc5d674 | ||
|
|
96afee996a | ||
|
|
da9747a406 | ||
|
|
36d219e05c | ||
|
|
ea2de24ade | ||
|
|
f01911d0e2 | ||
|
|
43eb9327d5 | ||
|
|
ca212ac592 | ||
|
|
fe3e0efcf1 | ||
|
|
ed7a880287 | ||
|
|
e94853f023 | ||
|
|
cbecd8ab56 | ||
|
|
feaab54f70 | ||
|
|
02e12cf871 | ||
|
|
7aeec0a0c4 | ||
|
|
abeea06e72 | ||
|
|
4a55b828b1 | ||
|
|
6b9c0a5e48 | ||
|
|
e060873246 | ||
|
|
143609b9fb | ||
|
|
a22438b7fa | ||
|
|
34321983e7 | ||
|
|
44762933b3 | ||
|
|
2912d1d437 | ||
|
|
4885278691 | ||
|
|
8a7c94180b | ||
|
|
183b9a7ee0 | ||
|
|
0fc582d6fd | ||
|
|
c7a6ed53ca | ||
|
|
547e39d24c | ||
|
|
115b58fe49 | ||
|
|
613e270d00 | ||
|
|
c24e738973 | ||
|
|
e2fb6089c9 | ||
|
|
95eb7aede0 | ||
|
|
3cef177e24 | ||
|
|
c430556498 | ||
|
|
ff9c982df4 | ||
|
|
724c93c23d | ||
|
|
769e20423d | ||
|
|
d12e697769 | ||
|
|
874b05c5da | ||
|
|
1894b90d84 | ||
|
|
cdf5bf3c9e | ||
|
|
5f35bd4e00 | ||
|
|
12504c76d0 | ||
|
|
7346b1a762 | ||
|
|
f7d616d223 | ||
|
|
443e5b5539 | ||
|
|
f6c14c939d | ||
|
|
a602c57e5d | ||
|
|
9ae41899a8 | ||
|
|
cfdd5f0284 | ||
|
|
04b0a0a7ae | ||
|
|
83fcac1868 | ||
|
|
efb0ecb4f9 | ||
|
|
7498d516d4 | ||
|
|
2fe3362c3d | ||
|
|
6473331399 | ||
|
|
6f85363e58 | ||
|
|
02c0d3bd0d | ||
|
|
f1f4cc007a | ||
|
|
7293771766 | ||
|
|
25d065c211 | ||
|
|
bf14f47459 | ||
|
|
8576073b9d | ||
|
|
cbe1331815 | ||
|
|
998b763cf9 | ||
|
|
15e8f106ce | ||
|
|
9aee262054 | ||
|
|
c718336143 | ||
|
|
355ed94852 | ||
|
|
56c5e784fb | ||
|
|
0a30bc1024 | ||
|
|
a55548de07 | ||
|
|
b84e613b5e | ||
|
|
6b9e6cb9a5 | ||
|
|
f24c4034e2 | ||
|
|
797352e4fa | ||
|
|
7d742924f6 | ||
|
|
e19ea796b1 | ||
|
|
bcef28e80b | ||
|
|
4acef9d508 | ||
|
|
8128fc190d | ||
|
|
2ba0c4435e | ||
|
|
b8164717da | ||
|
|
ed88e9dec5 | ||
|
|
5ebb985b10 | ||
|
|
7f5f80286e | ||
|
|
3cd0bc9672 | ||
|
|
95f896e92c | ||
|
|
0b9e3d24ef | ||
|
|
3f0efc9435 | ||
|
|
64205cf523 | ||
|
|
38c62d46c7 | ||
|
|
b1ac3a26f4 | ||
|
|
b795e612f7 | ||
|
|
a1a2286794 | ||
|
|
8c5d83d9fe | ||
|
|
5130a154e4 | ||
|
|
938f2570ef | ||
|
|
97a7b4450f | ||
|
|
46fbc7c71b | ||
|
|
e35da1c890 | ||
|
|
9af6175302 | ||
|
|
e1def4f8ac | ||
|
|
e09078f697 | ||
|
|
7232ff9ea0 | ||
|
|
45876a723c | ||
|
|
1ece35e4c5 | ||
|
|
5dee903374 | ||
|
|
4dd20af7e0 | ||
|
|
b9fbdfc9a4 | ||
|
|
ab00e348a4 | ||
|
|
b5c0ae9d5a | ||
|
|
8e4044eed9 | ||
|
|
7034ef46af | ||
|
|
8dfbe952ae | ||
|
|
f1e9da56dc | ||
|
|
f924c5f76d | ||
|
|
57442e8faa | ||
|
|
95bfd3b3a4 | ||
|
|
2858e83fe1 | ||
|
|
5430152c7f | ||
|
|
3433079121 | ||
|
|
151b84b8fe | ||
|
|
e3e01cb5da | ||
|
|
c4c890d4e9 | ||
|
|
64a473db2e | ||
|
|
cc9bcbaefd | ||
|
|
787d491bd5 | ||
|
|
5c4991299c | ||
|
|
33ac3ef2c6 | ||
|
|
c7504442bd | ||
|
|
1a546a1d82 | ||
|
|
713365a12c | ||
|
|
5d8248d31d | ||
|
|
5346c1cca3 | ||
|
|
baadaa05d2 | ||
|
|
224a1ce941 | ||
|
|
52f1bfed4c | ||
|
|
5c9cbe676d | ||
|
|
e25bb74c05 | ||
|
|
c2f465e246 | ||
|
|
6bbbf16140 | ||
|
|
c5cdb70031 | ||
|
|
2955cd70a8 | ||
|
|
7b4940914c | ||
|
|
30f977a7cb | ||
|
|
e06fa24616 | ||
|
|
20d7d0c78a | ||
|
|
606078c1b3 | ||
|
|
0135eb19d4 | ||
|
|
8b989190c4 | ||
|
|
a85ea59824 | ||
|
|
d012f0f2bc | ||
|
|
7e2b63ea5d | ||
|
|
cb53243fc1 | ||
|
|
5536af9756 | ||
|
|
28087c1d76 | ||
|
|
b80f32ce7d | ||
|
|
8bad59ebde | ||
|
|
b44e4617e0 | ||
|
|
5d99e3d191 | ||
|
|
1807852b6b | ||
|
|
4f4a5c0ba0 | ||
|
|
24188e58ff | ||
|
|
e2be598988 | ||
|
|
9e2a4f329b | ||
|
|
574b86cbca | ||
|
|
4b7042cc46 | ||
|
|
5ae4711f7b | ||
|
|
97a53b1536 | ||
|
|
cc982cf3b1 | ||
|
|
3f652f8b05 | ||
|
|
dcd86dec6e | ||
|
|
0d8194c800 | ||
|
|
150d9d7ae6 | ||
|
|
694f1797d2 | ||
|
|
116be0cfed | ||
|
|
aa2b5ae18d | ||
|
|
e2ccbe2088 | ||
|
|
775891fc88 | ||
|
|
c85954ddef | ||
|
|
dd6db2cbd9 | ||
|
|
5f74c7d294 | ||
|
|
ed87a632b0 | ||
|
|
3edcd16b62 | ||
|
|
b01d3aeecd | ||
|
|
0717c8bdbb | ||
|
|
f1e4561bdd | ||
|
|
efe9a30523 | ||
|
|
e5ede275f8 | ||
|
|
a929d24924 | ||
|
|
368f2d3542 | ||
|
|
139bbbb87c | ||
|
|
51f5d85861 | ||
|
|
7756faec1f | ||
|
|
d9e3e3aa95 | ||
|
|
ff65491bb6 | ||
|
|
8f514d8991 | ||
|
|
2e1000c31a | ||
|
|
c5171a1f2e | ||
|
|
7359e63960 | ||
|
|
307309504b | ||
|
|
1fda202cf9 | ||
|
|
7e634c154f | ||
|
|
3c72e35381 | ||
|
|
7e23430882 | ||
|
|
2c4c3fd749 | ||
|
|
edaf6ca522 | ||
|
|
21f0d5d82c | ||
|
|
059751b3c9 | ||
|
|
05479756d8 | ||
|
|
34e19cb638 | ||
|
|
5312f81c8e | ||
|
|
7f02898539 | ||
|
|
8aabc544f1 | ||
|
|
3b62955e81 | ||
|
|
9c76ba903b | ||
|
|
b4118b73ed | ||
|
|
82a8697f28 | ||
|
|
5b9a1ce5c6 | ||
|
|
32ad462ebe | ||
|
|
1ff8c2806c | ||
|
|
437a7a2852 | ||
|
|
412db70773 | ||
|
|
a1bb6babed | ||
|
|
568c536c3c | ||
|
|
d7129d6b55 | ||
|
|
4a893b13f8 | ||
|
|
8c2983c958 | ||
|
|
a30ffdb176 | ||
|
|
7a306e69ab | ||
|
|
23c4c2f1aa | ||
|
|
aac07f03d8 | ||
|
|
f4418920fb | ||
|
|
7752206152 | ||
|
|
c950f8f817 | ||
|
|
9276345fe7 | ||
|
|
01f910e257 | ||
|
|
de5f00016b | ||
|
|
877729aca3 | ||
|
|
f65d87b191 | ||
|
|
3b1d706b05 | ||
|
|
b0552fa0de | ||
|
|
cbca583f4b | ||
|
|
a0520de7aa | ||
|
|
4602b72778 | ||
|
|
e69a6ed91a | ||
|
|
9d6a037935 | ||
|
|
41df03f600 | ||
|
|
ca92464ef6 | ||
|
|
2e9fd33ce5 | ||
|
|
cf3f729f98 | ||
|
|
8b444283e6 | ||
|
|
4b4e6b1be3 | ||
|
|
d3d271defe | ||
|
|
9bddabf4ff | ||
|
|
959ea6875a | ||
|
|
7b7eb011b0 | ||
|
|
997e6be3a2 | ||
|
|
51af4bbf52 | ||
|
|
e33198e750 | ||
|
|
e3f5a8fee3 | ||
|
|
f8b58f8be9 | ||
|
|
55542a8086 | ||
|
|
70c8c0445c | ||
|
|
29c0bb4ce2 | ||
|
|
b86b195450 | ||
|
|
edf15894f8 | ||
|
|
74878a9aef | ||
|
|
82824b4fc6 | ||
|
|
141d71c39d | ||
|
|
e9d6f271b5 | ||
|
|
8d4dee2aee | ||
|
|
246af92386 | ||
|
|
7c9957a18f | ||
|
|
9e7018383a | ||
|
|
920d6e5404 | ||
|
|
57962347f2 | ||
|
|
6f053c45df | ||
|
|
f154326391 | ||
|
|
776a7fc9c0 | ||
|
|
08412b72bc | ||
|
|
030e166f43 | ||
|
|
d92dfc56b8 | ||
|
|
365d2d102d | ||
|
|
f7853c4ddf | ||
|
|
0a6d5d9267 | ||
|
|
10b761e4e7 | ||
|
|
c6f2b410bc | ||
|
|
306f8f5715 | ||
|
|
f7d9ee90cd | ||
|
|
9376c9a946 | ||
|
|
70fb87bc93 | ||
|
|
63e54f3575 | ||
|
|
1e2a497108 | ||
|
|
5d95a6e750 | ||
|
|
af00610a61 | ||
|
|
809cb79828 | ||
|
|
e44a58cba0 | ||
|
|
10046187a6 | ||
|
|
a402461f9c | ||
|
|
8a6771c9a9 | ||
|
|
7173bd5945 | ||
|
|
8e09ade41c | ||
|
|
6ceb8d8338 | ||
|
|
11296cd94f | ||
|
|
677dca0bc4 | ||
|
|
8e7b957164 | ||
|
|
8f93e2a9d4 | ||
|
|
62755b4b75 | ||
|
|
dcfb745b1f | ||
|
|
f38b83231c | ||
|
|
269f5e2575 | ||
|
|
893596383a | ||
|
|
8c67708829 | ||
|
|
c1528503b6 | ||
|
|
d3c56eb3d3 | ||
|
|
b10eee87ee | ||
|
|
83de469967 | ||
|
|
192978125f | ||
|
|
b4b3551e39 | ||
|
|
7f580e89d0 | ||
|
|
81a087095a | ||
|
|
bcabde3bdb | ||
|
|
c190d80d4a | ||
|
|
11081e8cb2 | ||
|
|
c5890f08ef | ||
|
|
926d8a1c37 | ||
|
|
da6dfd5a1b | ||
|
|
4318152141 | ||
|
|
759145704f | ||
|
|
5cab8f4b11 | ||
|
|
a0ce4b23d2 | ||
|
|
6d88a0c3ac | ||
|
|
db44bcd88e | ||
|
|
585ae5090d | ||
|
|
fe46793c40 | ||
|
|
be146b1cc9 | ||
|
|
e46c7bd519 | ||
|
|
f3d143e5ee | ||
|
|
fc059df8ff | ||
|
|
6c047d1e2a | ||
|
|
f6afe7f0ec | ||
|
|
ca7d2e783f | ||
|
|
0b133ca9f2 | ||
|
|
ede6785e6b | ||
|
|
6d9f39d8ea | ||
|
|
fb637ea955 | ||
|
|
e07558f5b7 | ||
|
|
b75dbf8c70 | ||
|
|
062c1e59a9 | ||
|
|
ba5eea861e | ||
|
|
ff7df54899 | ||
|
|
b75ce95086 | ||
|
|
a86d94745a | ||
|
|
c13de6089a | ||
|
|
3cb748a47e | ||
|
|
3e6e93fab4 | ||
|
|
5832811930 | ||
|
|
6f0ea04ff3 | ||
|
|
26ea167524 | ||
|
|
1393e7a62b | ||
|
|
7e1fd499ca | ||
|
|
309fd86b45 | ||
|
|
e14357f694 | ||
|
|
29f0adc587 | ||
|
|
3ab578747d | ||
|
|
2c3cb1a664 | ||
|
|
99a0c47277 | ||
|
|
c5b2c6709a | ||
|
|
8354ac937c | ||
|
|
aa5ad65286 | ||
|
|
05adb44416 | ||
|
|
adf5a5278f | ||
|
|
bb04ca36b2 | ||
|
|
fc94b1af7a | ||
|
|
d9886035c8 | ||
|
|
0e122863dd | ||
|
|
3089f67946 | ||
|
|
3654e221a9 | ||
|
|
a26dd05c49 | ||
|
|
6c844369be | ||
|
|
a03574f8c3 | ||
|
|
43c6a1531a | ||
|
|
aed7341b34 | ||
|
|
8731f00347 | ||
|
|
75a18b4548 | ||
|
|
716b41e91b | ||
|
|
e72214e266 | ||
|
|
46f9fc194c | ||
|
|
6c5f27be02 | ||
|
|
e96652bdaa | ||
|
|
b7007a35d7 | ||
|
|
e4dc56b59d | ||
|
|
b05059a9c4 | ||
|
|
121783976a | ||
|
|
5a9424d0b1 | ||
|
|
e2a3800664 | ||
|
|
56815a3624 | ||
|
|
ca6e0ff36e | ||
|
|
4c60d1657d | ||
|
|
cdd90cfa75 | ||
|
|
05b053e2e9 | ||
|
|
46c75c1af2 | ||
|
|
80cf5fa729 | ||
|
|
789aa322f1 | ||
|
|
f03c24565f | ||
|
|
b6a95be5d0 | ||
|
|
0f9e8ed3f7 | ||
|
|
50af0ba93c | ||
|
|
923a5882de | ||
|
|
83dc3e6093 | ||
|
|
43edb62f87 | ||
|
|
21e5acc0e0 | ||
|
|
be1ce74dc5 | ||
|
|
31771f3575 | ||
|
|
ffcd7d8059 | ||
|
|
4bc4aa1397 | ||
|
|
04faba95cd | ||
|
|
f712dadab5 | ||
|
|
58b3150ce3 | ||
|
|
e7225db397 | ||
|
|
8ec5492d87 | ||
|
|
c2c64a70c4 | ||
|
|
4621576f40 | ||
|
|
3b609e9b03 | ||
|
|
4f2ebd78be | ||
|
|
88dacebc94 | ||
|
|
92e7d1ad1e | ||
|
|
d5d8c340c8 | ||
|
|
b8a85b809a | ||
|
|
61be0775af | ||
|
|
404ffa5a91 | ||
|
|
f2de7e04b8 | ||
|
|
8b3e3b1dd7 | ||
|
|
81e91ac3f5 | ||
|
|
b9bde94d08 | ||
|
|
37746023c1 | ||
|
|
d3e529b8a4 | ||
|
|
eb69083ef5 | ||
|
|
96aac387c9 | ||
|
|
870f18c621 | ||
|
|
57b33e29f7 | ||
|
|
94b7917679 | ||
|
|
98fa0c4271 | ||
|
|
8282907bce | ||
|
|
323f4f5e5f | ||
|
|
744b3ebd0a | ||
|
|
3fdba35993 | ||
|
|
ebdf9ed379 | ||
|
|
a572274c5c | ||
|
|
6a6bba8669 | ||
|
|
1d1d4bbf4b | ||
|
|
d9bb4c631e | ||
|
|
722962c138 | ||
|
|
c98ff6ae87 | ||
|
|
cbef6a4cad | ||
|
|
f887a2c029 | ||
|
|
078cfe92c2 | ||
|
|
80ef184b60 | ||
|
|
f2475988bd | ||
|
|
45526108e0 | ||
|
|
414c0bbbdc | ||
|
|
6873a710d9 | ||
|
|
8a44006384 | ||
|
|
1b6061066a | ||
|
|
3a8a5982c6 | ||
|
|
ccff578492 | ||
|
|
e2402e3d84 | ||
|
|
f13b1629cf | ||
|
|
72a97bb70a | ||
|
|
67b393ce09 | ||
|
|
48e89b95bb | ||
|
|
2159ed62d0 | ||
|
|
8bb65719cd | ||
|
|
4352915945 | ||
|
|
ebd145f7f7 | ||
|
|
cd52a0eef3 | ||
|
|
69e4a86fe2 | ||
|
|
52d89a2ee1 | ||
|
|
5c60e792d9 | ||
|
|
77c9611784 | ||
|
|
66780bbf54 | ||
|
|
a8932e677e | ||
|
|
e12988a8f9 | ||
|
|
6ee4abe79e | ||
|
|
948e050d60 | ||
|
|
87feb45751 | ||
|
|
77aa81a064 | ||
|
|
a4a1db0915 | ||
|
|
57b9610af7 | ||
|
|
c3384c6499 | ||
|
|
5389859260 | ||
|
|
84c585cf61 | ||
|
|
ca496d393d | ||
|
|
3d4ca831dc | ||
|
|
7ace8de753 | ||
|
|
928d3e2185 | ||
|
|
cbf2b90320 | ||
|
|
69dce73e51 | ||
|
|
75d4f87dec | ||
|
|
4f7d89e825 | ||
|
|
da87e16321 | ||
|
|
f7aa3e7e1b | ||
|
|
deacfd6c03 | ||
|
|
3e8d1ae1d5 | ||
|
|
08f5ca39b0 | ||
|
|
31da363495 | ||
|
|
2ecd99d317 | ||
|
|
59c3d84182 | ||
|
|
641d5378f8 | ||
|
|
08512e5c43 | ||
|
|
8f7f7b23e8 | ||
|
|
2eb6ba5a48 | ||
|
|
03df386f9e | ||
|
|
5e741da69c | ||
|
|
3bea028cc9 | ||
|
|
10475b24c4 | ||
|
|
a5238e867c | ||
|
|
0e747790f9 | ||
|
|
2b4d6150d4 | ||
|
|
e22ad96164 | ||
|
|
f54907e66e | ||
|
|
fb713e9632 | ||
|
|
0b659e3f09 | ||
|
|
4c99117c7c | ||
|
|
83c3e8c2fc | ||
|
|
92277225df | ||
|
|
622ed88a11 | ||
|
|
d9414f25d5 | ||
|
|
5249d17a95 | ||
|
|
25283d357e | ||
|
|
e926321094 | ||
|
|
9c1542c3f3 | ||
|
|
25d06a53bc | ||
|
|
4d904e8216 | ||
|
|
0beab87f5b | ||
|
|
b9ceceada4 | ||
|
|
ac7ccf7b94 | ||
|
|
5aa9ae511f | ||
|
|
dd81ed896b | ||
|
|
e6bbed162d | ||
|
|
3ee8747fdc | ||
|
|
0651064999 | ||
|
|
4661e4519d | ||
|
|
19caeb178f | ||
|
|
d8235ea21b | ||
|
|
5067df179e | ||
|
|
50a1e32da3 | ||
|
|
fb85cafcc5 | ||
|
|
d06a2936cc | ||
|
|
7c77a9723a | ||
|
|
8a3e5790f5 | ||
|
|
dd093a775a | ||
|
|
a096bd2d71 | ||
|
|
2eddcde609 | ||
|
|
1849e1768a | ||
|
|
bbe62d029c | ||
|
|
68a89d3cd4 | ||
|
|
b16f1fc260 | ||
|
|
64f39e160b | ||
|
|
3a76c26822 | ||
|
|
9d9fff5796 | ||
|
|
0a92c61b09 | ||
|
|
546be76f55 | ||
|
|
d770f3f53f | ||
|
|
254cc131ae | ||
|
|
70509ffcb4 | ||
|
|
4cc2326c7f | ||
|
|
0133caaec4 | ||
|
|
7ae4f28920 | ||
|
|
b1eb0270e9 | ||
|
|
03b6de1169 | ||
|
|
9d3f75d111 | ||
|
|
5c41924b2f | ||
|
|
7cf7cf2f1e | ||
|
|
2679731bde | ||
|
|
ad73370cd7 | ||
|
|
18acae57bd | ||
|
|
b427356eca | ||
|
|
2e6ca16a4a | ||
|
|
e98f1142a6 | ||
|
|
06fa8f75c9 | ||
|
|
8cbd4e8172 | ||
|
|
83c7994266 | ||
|
|
43843581b6 | ||
|
|
7e03d64e8a | ||
|
|
c6f4d71187 | ||
|
|
69c3b90fea | ||
|
|
75309b4c93 | ||
|
|
433f13a7ed | ||
|
|
8896a1e73e | ||
|
|
54758b1692 | ||
|
|
375e00c79c | ||
|
|
e2454d91f1 | ||
|
|
6894f6f3bf | ||
|
|
d45501a129 | ||
|
|
def92e14ee | ||
|
|
11708d4189 | ||
|
|
2bc64bf419 | ||
|
|
3a3bdc62c8 | ||
|
|
72294b2a56 | ||
|
|
94940a20ef | ||
|
|
219ff2ef7d | ||
|
|
bc2de741b8 | ||
|
|
5eabd4e898 | ||
|
|
e8d6d5fe5c | ||
|
|
55bd93ff79 | ||
|
|
b34b2d779b | ||
|
|
103ed2be65 | ||
|
|
cc32c1be07 | ||
|
|
a3ba9817a3 | ||
|
|
6d5f1b17ad | ||
|
|
0d5d5fce46 | ||
|
|
375edffd15 | ||
|
|
d1982e64b2 | ||
|
|
cec8b5336c | ||
|
|
8f78263455 | ||
|
|
cb96bca6aa | ||
|
|
c18d0b9217 | ||
|
|
d1970185b9 | ||
|
|
794d3ee2f5 | ||
|
|
daeef98dfb | ||
|
|
4146df1f02 | ||
|
|
78f4dfa48d | ||
|
|
8b68d7d7e2 | ||
|
|
ba1bba17ad | ||
|
|
70f2b5028f | ||
|
|
4f4f0d70a4 | ||
|
|
9607b07e65 | ||
|
|
3570fab0f9 | ||
|
|
dd0f17d7a5 | ||
|
|
2290880389 | ||
|
|
351d287d88 | ||
|
|
eba6706f15 | ||
|
|
7f425efa6b | ||
|
|
d40685ab62 | ||
|
|
f795e3eeb8 | ||
|
|
d3944c0204 | ||
|
|
4086ab15fa | ||
|
|
446a367094 | ||
|
|
bd250c9871 | ||
|
|
91b3788362 | ||
|
|
658dbb9ea8 | ||
|
|
1257b9cbc8 | ||
|
|
0ae39d5a0a | ||
|
|
d8911e0c77 | ||
|
|
1be006a45f | ||
|
|
b95e75ddb4 | ||
|
|
15a5f75fe7 | ||
|
|
b01e27f50f | ||
|
|
b1a9583262 | ||
|
|
dd5e6377f8 | ||
|
|
fcb73554c9 | ||
|
|
5c4b3d1080 | ||
|
|
c98cd10621 | ||
|
|
e4af8ddcc8 | ||
|
|
b5ce02b8cf | ||
|
|
cd42b3fb44 | ||
|
|
6dedb767c3 | ||
|
|
4de3268f3b | ||
|
|
a72ff06d79 | ||
|
|
cf239deb3d | ||
|
|
8735229745 | ||
|
|
3f081ba2d9 | ||
|
|
3f613341cb | ||
|
|
4305b8a77d | ||
|
|
fab91d44c3 | ||
|
|
34484734d8 | ||
|
|
10c58bb007 | ||
|
|
1c1fd62a91 | ||
|
|
637ac1e3fe | ||
|
|
f52387e849 | ||
|
|
9efc4986f9 | ||
|
|
fdc366ec1c | ||
|
|
d455db444c | ||
|
|
17418c9858 | ||
|
|
5da7eb7d0d | ||
|
|
9da977ee7a | ||
|
|
8e4b2ead9b |
46
.github/ISSUE_TEMPLATE/bug_report.md
vendored
46
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,46 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: Create a report to help us improve Flux v2
|
|
||||||
title: ''
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<!--
|
|
||||||
|
|
||||||
Find out more about your support options and getting help at
|
|
||||||
|
|
||||||
https://fluxcd.io/support/
|
|
||||||
|
|
||||||
-->
|
|
||||||
|
|
||||||
### Describe the bug
|
|
||||||
|
|
||||||
A clear and concise description of what the bug is.
|
|
||||||
|
|
||||||
### To Reproduce
|
|
||||||
|
|
||||||
Steps to reproduce the behaviour:
|
|
||||||
|
|
||||||
1. Provide Flux install instructions
|
|
||||||
2. Provide a GitHub repository with Kubernetes manifests
|
|
||||||
|
|
||||||
### Expected behavior
|
|
||||||
|
|
||||||
A clear and concise description of what you expected to happen.
|
|
||||||
|
|
||||||
### Additional context
|
|
||||||
|
|
||||||
- Kubernetes version:
|
|
||||||
- Git provider:
|
|
||||||
- Container registry provider:
|
|
||||||
|
|
||||||
Below please provide the output of the following commands:
|
|
||||||
|
|
||||||
```cli
|
|
||||||
flux --version
|
|
||||||
flux check
|
|
||||||
kubectl -n <namespace> get all
|
|
||||||
kubectl -n <namespace> logs deploy/source-controller
|
|
||||||
kubectl -n <namespace> logs deploy/kustomize-controller
|
|
||||||
```
|
|
||||||
84
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
Normal file
84
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
description: Create a report to help us improve Flux
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
## Support
|
||||||
|
Find out more about your support options and getting help at: https://fluxcd.io/support/
|
||||||
|
- type: textarea
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: Describe the bug
|
||||||
|
description: A clear description of what the bug is.
|
||||||
|
- type: textarea
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: Steps to reproduce
|
||||||
|
description: |
|
||||||
|
Steps to reproduce the problem.
|
||||||
|
placeholder: |
|
||||||
|
For example:
|
||||||
|
1. Install Flux with the additional image automation controllers
|
||||||
|
2. Run command '...'
|
||||||
|
3. See error
|
||||||
|
- type: textarea
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: Expected behavior
|
||||||
|
description: A brief description of what you expected to happen.
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Screenshots and recordings
|
||||||
|
description: |
|
||||||
|
If applicable, add screenshots to help explain your problem. You can also record an asciinema session: https://asciinema.org/
|
||||||
|
- type: input
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: OS / Distro
|
||||||
|
description: The OS / distro you are executing `flux` on. If not applicable, write `N/A`.
|
||||||
|
placeholder: e.g. Windows 10, Ubuntu 20.04, Arch Linux, macOS 10.15...
|
||||||
|
- type: input
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: Flux version
|
||||||
|
description: Run `flux version --client`. If not applicable, write `N/A`.
|
||||||
|
placeholder: e.g. v0.20.1
|
||||||
|
- type: textarea
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: Flux check
|
||||||
|
description: Run `flux check`. If not applicable, write `N/A`.
|
||||||
|
placeholder: |
|
||||||
|
For example:
|
||||||
|
► checking prerequisites
|
||||||
|
✔ Kubernetes 1.21.1 >=1.19.0-0
|
||||||
|
► checking controllers
|
||||||
|
✔ all checks passed
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: Git provider
|
||||||
|
description: If applicable, add the Git provider you are having problems with, e.g. GitHub (Enterprise), GitLab, etc.
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: Container Registry provider
|
||||||
|
description: If applicable, add the Container Registry provider you are having problems with, e.g. DockerHub, GitHub Packages, Quay.io, etc.
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Additional context
|
||||||
|
description: Add any other context about the problem here. This can be logs (e.g. output from `flux logs`), environment specific caveats, etc.
|
||||||
|
- type: checkboxes
|
||||||
|
id: terms
|
||||||
|
attributes:
|
||||||
|
label: Code of Conduct
|
||||||
|
description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/fluxcd/.github/blob/main/CODE_OF_CONDUCT.md)
|
||||||
|
options:
|
||||||
|
- label: I agree to follow this project's Code of Conduct
|
||||||
|
required: true
|
||||||
15
.github/aur/flux-bin/.SRCINFO.template
vendored
15
.github/aur/flux-bin/.SRCINFO.template
vendored
@@ -8,10 +8,15 @@ pkgbase = flux-bin
|
|||||||
arch = armv7h
|
arch = armv7h
|
||||||
arch = aarch64
|
arch = aarch64
|
||||||
license = APACHE
|
license = APACHE
|
||||||
optdepends = kubectl
|
optdepends = bash-completion: auto-completion for flux in Bash
|
||||||
source_x86_64 = flux-bin-${PKGVER}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v1/flux_${PKGVER}_linux_amd64.tar.gz
|
optdepends = zsh-completions: auto-completion for flux in ZSH
|
||||||
source_armv6h = flux-bin-${PKGVER}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v1/flux_${PKGVER}_linux_arm.tar.gz
|
source_x86_64 = ${PKGNAME}-${PKGVER}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${PKGVER}/flux_${PKGVER}_linux_amd64.tar.gz
|
||||||
source_armv7h = flux-bin-${PKGVER}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v1/flux_${PKGVER}_linux_arm.tar.gz
|
sha256sums_x86_64 = ${SHA256SUM_AMD64}
|
||||||
source_aarch64 = flux-bin-${PKGVER}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v1/flux_${PKGVER}_linux_arm64.tar.gz
|
source_armv6h = ${PKGNAME}-${PKGVER}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${PKGVER}/flux_${PKGVER}_linux_arm.tar.gz
|
||||||
|
sha256sums_armv6h = ${SHA256SUM_ARM}
|
||||||
|
source_armv7h = ${PKGNAME}-${PKGVER}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${PKGVER}/flux_${PKGVER}_linux_arm.tar.gz
|
||||||
|
sha256sums_armv7h = ${SHA256SUM_ARM}
|
||||||
|
source_aarch64 = ${PKGNAME}-${PKGVER}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${PKGVER}/flux_${PKGVER}_linux_arm64.tar.gz
|
||||||
|
sha256sums_aarch64 = ${SHA256SUM_ARM64}
|
||||||
|
|
||||||
pkgname = flux-bin
|
pkgname = flux-bin
|
||||||
|
|||||||
5
.github/aur/flux-bin/PKGBUILD.template
vendored
5
.github/aur/flux-bin/PKGBUILD.template
vendored
@@ -8,9 +8,8 @@ pkgdesc="Open and extensible continuous delivery solution for Kubernetes"
|
|||||||
url="https://fluxcd.io/"
|
url="https://fluxcd.io/"
|
||||||
arch=("x86_64" "armv6h" "armv7h" "aarch64")
|
arch=("x86_64" "armv6h" "armv7h" "aarch64")
|
||||||
license=("APACHE")
|
license=("APACHE")
|
||||||
optdepends=('kubectl: for apply actions on the Kubernetes cluster',
|
optdepends=('bash-completion: auto-completion for flux in Bash'
|
||||||
'bash-completion: auto-completion for flux in Bash',
|
'zsh-completions: auto-completion for flux in ZSH')
|
||||||
'zsh-completions: auto-completion for flux in ZSH')
|
|
||||||
source_x86_64=(
|
source_x86_64=(
|
||||||
"${pkgname}-${pkgver}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${pkgver}/flux_${pkgver}_linux_amd64.tar.gz"
|
"${pkgname}-${pkgver}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${pkgver}/flux_${pkgver}_linux_amd64.tar.gz"
|
||||||
)
|
)
|
||||||
|
|||||||
1
.github/aur/flux-go/.SRCINFO.template
vendored
1
.github/aur/flux-go/.SRCINFO.template
vendored
@@ -10,7 +10,6 @@ pkgbase = flux-go
|
|||||||
license = APACHE
|
license = APACHE
|
||||||
makedepends = go
|
makedepends = go
|
||||||
depends = glibc
|
depends = glibc
|
||||||
optdepends = kubectl
|
|
||||||
provides = flux-bin
|
provides = flux-bin
|
||||||
conflicts = flux-bin
|
conflicts = flux-bin
|
||||||
replaces = flux-cli
|
replaces = flux-cli
|
||||||
|
|||||||
15
.github/aur/flux-go/PKGBUILD.template
vendored
15
.github/aur/flux-go/PKGBUILD.template
vendored
@@ -12,9 +12,8 @@ provides=("flux-bin")
|
|||||||
conflicts=("flux-bin")
|
conflicts=("flux-bin")
|
||||||
replaces=("flux-cli")
|
replaces=("flux-cli")
|
||||||
depends=("glibc")
|
depends=("glibc")
|
||||||
makedepends=('go>=1.16', 'kustomize>=3.0')
|
makedepends=('go>=1.17', 'kustomize>=3.0')
|
||||||
optdepends=('kubectl: for apply actions on the Kubernetes cluster',
|
optdepends=('bash-completion: auto-completion for flux in Bash',
|
||||||
'bash-completion: auto-completion for flux in Bash',
|
|
||||||
'zsh-completions: auto-completion for flux in ZSH')
|
'zsh-completions: auto-completion for flux in ZSH')
|
||||||
source=(
|
source=(
|
||||||
"${pkgname}-${pkgver}.tar.gz::https://github.com/fluxcd/flux2/archive/v${pkgver}.tar.gz"
|
"${pkgname}-${pkgver}.tar.gz::https://github.com/fluxcd/flux2/archive/v${pkgver}.tar.gz"
|
||||||
@@ -31,12 +30,20 @@ build() {
|
|||||||
export CGO_CXXFLAGS="$CXXFLAGS"
|
export CGO_CXXFLAGS="$CXXFLAGS"
|
||||||
export CGO_CPPFLAGS="$CPPFLAGS"
|
export CGO_CPPFLAGS="$CPPFLAGS"
|
||||||
export GOFLAGS="-buildmode=pie -trimpath -mod=readonly -modcacherw"
|
export GOFLAGS="-buildmode=pie -trimpath -mod=readonly -modcacherw"
|
||||||
./manifests/scripts/bundle.sh "${PWD}/manifests" "${PWD}/cmd/flux/manifests"
|
make cmd/flux/.manifests.done
|
||||||
go build -ldflags "-linkmode=external -X main.VERSION=${pkgver}" -o ${_srcname} ./cmd/flux
|
go build -ldflags "-linkmode=external -X main.VERSION=${pkgver}" -o ${_srcname} ./cmd/flux
|
||||||
}
|
}
|
||||||
|
|
||||||
check() {
|
check() {
|
||||||
cd "flux2-${pkgver}"
|
cd "flux2-${pkgver}"
|
||||||
|
case $CARCH in
|
||||||
|
aarch64)
|
||||||
|
export ENVTEST_ARCH=arm64
|
||||||
|
;;
|
||||||
|
armv6h|armv7h)
|
||||||
|
export ENVTEST_ARCH=arm
|
||||||
|
;;
|
||||||
|
esac
|
||||||
make test
|
make test
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
1
.github/aur/flux-scm/.SRCINFO.template
vendored
1
.github/aur/flux-scm/.SRCINFO.template
vendored
@@ -10,7 +10,6 @@ pkgbase = flux-scm
|
|||||||
license = APACHE
|
license = APACHE
|
||||||
makedepends = go
|
makedepends = go
|
||||||
depends = glibc
|
depends = glibc
|
||||||
optdepends = kubectl
|
|
||||||
provides = flux-bin
|
provides = flux-bin
|
||||||
conflicts = flux-bin
|
conflicts = flux-bin
|
||||||
source = git+https://github.com/fluxcd/flux2.git
|
source = git+https://github.com/fluxcd/flux2.git
|
||||||
|
|||||||
15
.github/aur/flux-scm/PKGBUILD.template
vendored
15
.github/aur/flux-scm/PKGBUILD.template
vendored
@@ -11,9 +11,8 @@ license=("APACHE")
|
|||||||
provides=("flux-bin")
|
provides=("flux-bin")
|
||||||
conflicts=("flux-bin")
|
conflicts=("flux-bin")
|
||||||
depends=("glibc")
|
depends=("glibc")
|
||||||
makedepends=('go>=1.16', 'kustomize>=3.0')
|
makedepends=('go>=1.17', 'kustomize>=3.0', 'git')
|
||||||
optdepends=('kubectl: for apply actions on the Kubernetes cluster',
|
optdepends=('bash-completion: auto-completion for flux in Bash',
|
||||||
'bash-completion: auto-completion for flux in Bash',
|
|
||||||
'zsh-completions: auto-completion for flux in ZSH')
|
'zsh-completions: auto-completion for flux in ZSH')
|
||||||
source=(
|
source=(
|
||||||
"git+https://github.com/fluxcd/flux2.git"
|
"git+https://github.com/fluxcd/flux2.git"
|
||||||
@@ -33,12 +32,20 @@ build() {
|
|||||||
export CGO_CXXFLAGS="$CXXFLAGS"
|
export CGO_CXXFLAGS="$CXXFLAGS"
|
||||||
export CGO_CPPFLAGS="$CPPFLAGS"
|
export CGO_CPPFLAGS="$CPPFLAGS"
|
||||||
export GOFLAGS="-buildmode=pie -trimpath -mod=readonly -modcacherw"
|
export GOFLAGS="-buildmode=pie -trimpath -mod=readonly -modcacherw"
|
||||||
make cmd/flux/manifests
|
make cmd/flux/.manifests.done
|
||||||
go build -ldflags "-linkmode=external -X main.VERSION=${pkgver}" -o ${_srcname} ./cmd/flux
|
go build -ldflags "-linkmode=external -X main.VERSION=${pkgver}" -o ${_srcname} ./cmd/flux
|
||||||
}
|
}
|
||||||
|
|
||||||
check() {
|
check() {
|
||||||
cd "flux2"
|
cd "flux2"
|
||||||
|
case $CARCH in
|
||||||
|
aarch64)
|
||||||
|
export ENVTEST_ARCH=arm64
|
||||||
|
;;
|
||||||
|
armv6h|armv7h)
|
||||||
|
export ENVTEST_ARCH=arm
|
||||||
|
;;
|
||||||
|
esac
|
||||||
make test
|
make test
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
9
.github/dependabot.yml
vendored
Normal file
9
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
version: 2
|
||||||
|
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
labels: ["area/build"]
|
||||||
|
schedule:
|
||||||
|
# by default this will be on a monday.
|
||||||
|
interval: "weekly"
|
||||||
80
.github/runners/README.md
vendored
Normal file
80
.github/runners/README.md
vendored
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
# Flux ARM64 GitHub runners
|
||||||
|
|
||||||
|
The Flux ARM64 end-to-end tests run on Equinix Metal instances provisioned with Docker and GitHub self-hosted runners.
|
||||||
|
|
||||||
|
## Current instances
|
||||||
|
|
||||||
|
| Repository | Runner | Instance | Location |
|
||||||
|
|-----------------------------|------------------|------------------------|---------------|
|
||||||
|
| flux2 | equinix-arm-dc-1 | flux-equinix-arm-dc-01 | Washington DC |
|
||||||
|
| flux2 | equinix-arm-dc-2 | flux-equinix-arm-dc-01 | Washington DC |
|
||||||
|
| flux2 | equinix-arm-da-1 | flux-equinix-arm-da-01 | Dallas |
|
||||||
|
| flux2 | equinix-arm-da-2 | flux-equinix-arm-da-01 | Dallas |
|
||||||
|
| source-controller | equinix-arm-dc-1 | flux-equinix-arm-dc-01 | Washington DC |
|
||||||
|
| source-controller | equinix-arm-da-1 | flux-equinix-arm-da-01 | Dallas |
|
||||||
|
| image-automation-controller | equinix-arm-dc-1 | flux-equinix-arm-dc-01 | Washington DC |
|
||||||
|
| image-automation-controller | equinix-arm-da-1 | flux-equinix-arm-da-01 | Dallas |
|
||||||
|
|
||||||
|
Instance spec:
|
||||||
|
- Ampere Altra Q80-30 80-core processor @ 2.8GHz
|
||||||
|
- 2 x 960GB NVME
|
||||||
|
- 256GB RAM
|
||||||
|
- 2 x 25Gbps
|
||||||
|
|
||||||
|
## Instance setup
|
||||||
|
|
||||||
|
In order to add a new runner to the GitHub Actions pool,
|
||||||
|
first create a server on Equinix with the following configuration:
|
||||||
|
- Type: `c3.large.arm64`
|
||||||
|
- OS: `Ubuntu 22.04 LTS`
|
||||||
|
|
||||||
|
### Install prerequisites
|
||||||
|
|
||||||
|
- SSH into a newly created instance
|
||||||
|
```shell
|
||||||
|
ssh root@<instance-public-IP>
|
||||||
|
```
|
||||||
|
|
||||||
|
- Create the ubuntu user
|
||||||
|
```shell
|
||||||
|
adduser ubuntu
|
||||||
|
usermod -aG sudo ubuntu
|
||||||
|
su - ubuntu
|
||||||
|
```
|
||||||
|
|
||||||
|
- Create the prerequisites dir
|
||||||
|
```shell
|
||||||
|
mkdir -p prereq && cd prereq
|
||||||
|
```
|
||||||
|
|
||||||
|
- Download the prerequisites script
|
||||||
|
```shell
|
||||||
|
curl -sL https://raw.githubusercontent.com/fluxcd/flux2/main/.github/runners/prereq.sh > prereq.sh \
|
||||||
|
&& chmod +x ./prereq.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
- Install the prerequisites
|
||||||
|
```shell
|
||||||
|
sudo ./prereq.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### Install runners
|
||||||
|
|
||||||
|
- Retrieve the GitHub runner token from the repository [settings page](https://github.com/fluxcd/flux2/settings/actions/runners/new?arch=arm64&os=linux)
|
||||||
|
|
||||||
|
- Create two directories `flux2-01`, `flux2-02`
|
||||||
|
|
||||||
|
- In each dir run:
|
||||||
|
```shell
|
||||||
|
curl -sL https://raw.githubusercontent.com/fluxcd/flux2/main/.github/runners/runner-setup.sh > runner-setup.sh \
|
||||||
|
&& chmod +x ./runner-setup.sh
|
||||||
|
|
||||||
|
./runner-setup.sh equinix-arm-<NUMBER> <TOKEN> <REPO>
|
||||||
|
```
|
||||||
|
|
||||||
|
- Reboot the instance
|
||||||
|
```shell
|
||||||
|
sudo reboot
|
||||||
|
```
|
||||||
|
|
||||||
|
- Navigate to the GitHub repository [runners page](https://github.com/fluxcd/flux2/settings/actions/runners) and check the runner status
|
||||||
72
.github/runners/prereq.sh
vendored
Executable file
72
.github/runners/prereq.sh
vendored
Executable file
@@ -0,0 +1,72 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Copyright 2021 The Flux authors. All rights reserved.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
# This script installs the prerequisites for running Flux end-to-end tests with Docker and GitHub self-hosted runners.
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
KIND_VERSION=0.17.0
|
||||||
|
KUBECTL_VERSION=1.24.0
|
||||||
|
KUSTOMIZE_VERSION=4.5.7
|
||||||
|
HELM_VERSION=3.10.1
|
||||||
|
GITHUB_RUNNER_VERSION=2.298.2
|
||||||
|
PACKAGES="apt-transport-https ca-certificates software-properties-common build-essential libssl-dev gnupg lsb-release jq pkg-config"
|
||||||
|
|
||||||
|
# install prerequisites
|
||||||
|
apt-get update \
|
||||||
|
&& apt-get install -y -q ${PACKAGES} \
|
||||||
|
&& apt-get clean \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# fix Kubernetes DNS resolution
|
||||||
|
rm /etc/resolv.conf
|
||||||
|
cat "/run/systemd/resolve/stub-resolv.conf" | sed '/search/d' > /etc/resolv.conf
|
||||||
|
|
||||||
|
# install docker
|
||||||
|
curl -fsSL https://get.docker.com -o get-docker.sh \
|
||||||
|
&& chmod +x get-docker.sh
|
||||||
|
./get-docker.sh
|
||||||
|
systemctl enable docker.service
|
||||||
|
systemctl enable containerd.service
|
||||||
|
usermod -aG docker ubuntu
|
||||||
|
|
||||||
|
# install kind
|
||||||
|
curl -Lo ./kind https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-linux-arm64
|
||||||
|
install -o root -g root -m 0755 kind /usr/local/bin/kind
|
||||||
|
|
||||||
|
# install kubectl
|
||||||
|
curl -LO "https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/arm64/kubectl"
|
||||||
|
install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
|
||||||
|
|
||||||
|
# install kustomize
|
||||||
|
curl -Lo ./kustomize.tar.gz https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2Fv${KUSTOMIZE_VERSION}/kustomize_v${KUSTOMIZE_VERSION}_linux_arm64.tar.gz \
|
||||||
|
&& tar -zxvf kustomize.tar.gz \
|
||||||
|
&& rm kustomize.tar.gz
|
||||||
|
install -o root -g root -m 0755 kustomize /usr/local/bin/kustomize
|
||||||
|
|
||||||
|
# install helm
|
||||||
|
curl -Lo ./helm.tar.gz https://get.helm.sh/helm-v${HELM_VERSION}-linux-arm64.tar.gz \
|
||||||
|
&& tar -zxvf helm.tar.gz \
|
||||||
|
&& rm helm.tar.gz
|
||||||
|
install -o root -g root -m 0755 linux-arm64/helm /usr/local/bin/helm
|
||||||
|
|
||||||
|
# download runner
|
||||||
|
curl -o actions-runner-linux-arm64.tar.gz -L https://github.com/actions/runner/releases/download/v${GITHUB_RUNNER_VERSION}/actions-runner-linux-arm64-${GITHUB_RUNNER_VERSION}.tar.gz \
|
||||||
|
&& tar xzf actions-runner-linux-arm64.tar.gz \
|
||||||
|
&& rm actions-runner-linux-arm64.tar.gz
|
||||||
|
|
||||||
|
# install runner dependencies
|
||||||
|
./bin/installdependencies.sh
|
||||||
37
.github/runners/runner-setup.sh
vendored
Executable file
37
.github/runners/runner-setup.sh
vendored
Executable file
@@ -0,0 +1,37 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Copyright 2021 The Flux authors. All rights reserved.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
# This script installs a GitHub self-hosted ARM64 runner for running Flux end-to-end tests.
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
RUNNER_NAME=$1
|
||||||
|
REPOSITORY_TOKEN=$2
|
||||||
|
REPOSITORY_URL=${3:-https://github.com/fluxcd/flux2}
|
||||||
|
|
||||||
|
GITHUB_RUNNER_VERSION=2.298.2
|
||||||
|
|
||||||
|
# download runner
|
||||||
|
curl -o actions-runner-linux-arm64.tar.gz -L https://github.com/actions/runner/releases/download/v${GITHUB_RUNNER_VERSION}/actions-runner-linux-arm64-${GITHUB_RUNNER_VERSION}.tar.gz \
|
||||||
|
&& tar xzf actions-runner-linux-arm64.tar.gz \
|
||||||
|
&& rm actions-runner-linux-arm64.tar.gz
|
||||||
|
|
||||||
|
# register runner with GitHub
|
||||||
|
./config.sh --unattended --url ${REPOSITORY_URL} --token ${REPOSITORY_TOKEN} --name ${RUNNER_NAME}
|
||||||
|
|
||||||
|
# start runner
|
||||||
|
sudo ./svc.sh install
|
||||||
|
sudo ./svc.sh start
|
||||||
50
.github/workflows/README.md
vendored
Normal file
50
.github/workflows/README.md
vendored
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# Flux GitHub Workflows
|
||||||
|
|
||||||
|
## End-to-end Testing
|
||||||
|
|
||||||
|
The e2e workflows run a series of tests to ensure that the Flux CLI and
|
||||||
|
the GitOps Toolkit controllers work well all together.
|
||||||
|
The tests are written in Go, Bash, Make and Terraform.
|
||||||
|
|
||||||
|
| Workflow | Jobs | Runner | Role |
|
||||||
|
|--------------------|----------------------|----------------|-----------------------------------------------|
|
||||||
|
| e2e.yaml | e2e-amd64-kubernetes | GitHub Ubuntu | integration testing with Kubernetes Kind<br/> |
|
||||||
|
| e2e-arm64.yaml | e2e-arm64-kubernetes | Equinix Ubuntu | integration testing with Kubernetes Kind<br/> |
|
||||||
|
| e2e-bootstrap.yaml | e2e-boostrap-github | GitHub Ubuntu | integration testing with GitHub API<br/> |
|
||||||
|
| e2e-azure.yaml | e2e-amd64-aks | GitHub Ubuntu | integration testing with Azure API<br/> |
|
||||||
|
| scan.yaml | scan-fossa | GitHub Ubuntu | license scanning<br/> |
|
||||||
|
| scan.yaml | scan-snyk | GitHub Ubuntu | vulnerability scanning<br/> |
|
||||||
|
| scan.yaml | scan-codeql | GitHub Ubuntu | vulnerability scanning<br/> |
|
||||||
|
|
||||||
|
## Components Update
|
||||||
|
|
||||||
|
The components update workflow scans the GitOps Toolkit controller repositories for new releases,
|
||||||
|
amd when it finds a new controller version, the workflow performs the following steps:
|
||||||
|
- Updates the controller API package version in `go.mod`.
|
||||||
|
- Patches the controller CRDs version in the `manifests/crds` overlay.
|
||||||
|
- Patches the controller Deployment version in `manifests/bases` overlay.
|
||||||
|
- Opens a Pull Request against the `main` branch.
|
||||||
|
- Triggers the e2e test suite to run for the opened PR.
|
||||||
|
|
||||||
|
|
||||||
|
| Workflow | Jobs | Runner | Role |
|
||||||
|
|-------------|-------------------|---------------|-----------------------------------------------------|
|
||||||
|
| update.yaml | update-components | GitHub Ubuntu | update the GitOps Toolkit APIs and controllers<br/> |
|
||||||
|
|
||||||
|
## Release
|
||||||
|
|
||||||
|
The release workflow is triggered by a semver Git tag and performs the following steps:
|
||||||
|
- Generates the Flux install manifests (YAML).
|
||||||
|
- Generates the OpenAPI validation schemas for the GitOps Toolkit CRDs (JSON).
|
||||||
|
- Generates a Software Bill of Materials (SPDX JSON).
|
||||||
|
- Builds the Flux CLI binaries and the multi-arch container images.
|
||||||
|
- Pushes the container images to GitHub Container Registry and DockerHub.
|
||||||
|
- Signs the sbom, the binaries checksum and the container images with Cosign and GitHub OIDC.
|
||||||
|
- Uploads the sbom, binaries, checksums and install manifests to GitHub Releases.
|
||||||
|
- Pushes the install manifests as OCI artifacts to GitHub Container Registry and DockerHub.
|
||||||
|
- Signs the OCI artifacts with Cosign and GitHub OIDC.
|
||||||
|
|
||||||
|
| Workflow | Jobs | Runner | Role |
|
||||||
|
|--------------|------------------------|---------------|------------------------------------------------------|
|
||||||
|
| release.yaml | release-flux-cli | GitHub Ubuntu | build, push and sign the CLI release artifacts<br/> |
|
||||||
|
| release.yaml | release-flux-manifests | GitHub Ubuntu | build, push and sign the Flux install manifests<br/> |
|
||||||
154
.github/workflows/e2e-arm64.yaml
vendored
154
.github/workflows/e2e-arm64.yaml
vendored
@@ -3,107 +3,99 @@ name: e2e-arm64
|
|||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches: [ main, update-components ]
|
branches: [ main, update-components, e2e-arm64* ]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
ampere:
|
e2e-arm64-kubernetes:
|
||||||
# Runner info
|
# Hosted on Equinix
|
||||||
# Owner: Stefan Prodan
|
# Docs: https://github.com/fluxcd/flux2/tree/main/.github/runners
|
||||||
# VM: Oracle Cloud VM.Standard.A1.Flex 4CPU 24GB RAM
|
runs-on: [self-hosted, Linux, ARM64, equinix]
|
||||||
# OS: Linux 5.4.0-1045-oracle #49-Ubuntu SMP aarch64
|
strategy:
|
||||||
# Packages: docker, kind, kubectl, kustomize
|
matrix:
|
||||||
runs-on: [self-hosted, Linux, ARM64]
|
# Keep this list up-to-date with https://endoflife.date/kubernetes
|
||||||
|
KUBERNETES_VERSION: [ 1.23.13, 1.24.7, 1.25.3 ]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568
|
||||||
with:
|
with:
|
||||||
go-version: 1.16.x
|
go-version: 1.19.x
|
||||||
- name: Prepare
|
- name: Prepare
|
||||||
id: prep
|
id: prep
|
||||||
run: |
|
run: |
|
||||||
echo ::set-output name=CLUSTER::arm64-${GITHUB_SHA:0:7}-$(date +%s)
|
ID=${GITHUB_SHA:0:7}-${{ matrix.KUBERNETES_VERSION }}-$(date +%s)
|
||||||
echo ::set-output name=CONTEXT::kind-arm64-${GITHUB_SHA:0:7}-$(date +%s)
|
echo "CLUSTER=arm64-${ID}" >> $GITHUB_OUTPUT
|
||||||
- name: Run unit tests
|
|
||||||
run: make test
|
|
||||||
- name: Check if working tree is dirty
|
|
||||||
run: |
|
|
||||||
if [[ $(git diff --stat) != '' ]]; then
|
|
||||||
git diff
|
|
||||||
echo 'run make test and commit changes'
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
go build -o /tmp/flux ./cmd/flux
|
make build
|
||||||
- name: Setup Kubernetes Kind
|
- name: Setup Kubernetes Kind
|
||||||
run: |
|
run: |
|
||||||
kind create cluster --name ${{ steps.prep.outputs.CLUSTER }}
|
kind create cluster \
|
||||||
- name: flux check --pre
|
--wait 5m \
|
||||||
|
--name ${{ steps.prep.outputs.CLUSTER }} \
|
||||||
|
--kubeconfig=/tmp/${{ steps.prep.outputs.CLUSTER }} \
|
||||||
|
--image=kindest/node:v${{ matrix.KUBERNETES_VERSION }}
|
||||||
|
- name: Run e2e tests
|
||||||
|
run: TEST_KUBECONFIG=/tmp/${{ steps.prep.outputs.CLUSTER }} make e2e
|
||||||
|
- name: Run multi-tenancy tests
|
||||||
|
env:
|
||||||
|
KUBECONFIG: /tmp/${{ steps.prep.outputs.CLUSTER }}
|
||||||
run: |
|
run: |
|
||||||
/tmp/flux check --pre \
|
./bin/flux install
|
||||||
--context ${{ steps.prep.outputs.CONTEXT }}
|
./bin/flux create source git flux-system \
|
||||||
- name: flux install
|
--interval=15m \
|
||||||
|
--url=https://github.com/fluxcd/flux2-multi-tenancy \
|
||||||
|
--branch=main \
|
||||||
|
--ignore-paths="./clusters/**/flux-system/"
|
||||||
|
./bin/flux create kustomization flux-system \
|
||||||
|
--interval=15m \
|
||||||
|
--source=flux-system \
|
||||||
|
--path=./clusters/staging
|
||||||
|
kubectl -n flux-system wait kustomization/tenants --for=condition=ready --timeout=5m
|
||||||
|
kubectl -n apps wait kustomization/dev-team --for=condition=ready --timeout=1m
|
||||||
|
kubectl -n apps wait helmrelease/podinfo --for=condition=ready --timeout=1m
|
||||||
|
- name: Run monitoring tests
|
||||||
|
# Keep this test in sync with https://fluxcd.io/flux/guides/monitoring/
|
||||||
|
env:
|
||||||
|
KUBECONFIG: /tmp/${{ steps.prep.outputs.CLUSTER }}
|
||||||
run: |
|
run: |
|
||||||
/tmp/flux install \
|
./bin/flux create source git flux-monitoring \
|
||||||
--components-extra=image-reflector-controller,image-automation-controller \
|
--interval=30m \
|
||||||
--context ${{ steps.prep.outputs.CONTEXT }}
|
--url=https://github.com/fluxcd/flux2 \
|
||||||
- name: flux create source git
|
--branch=${GITHUB_REF#refs/heads/}
|
||||||
run: |
|
./bin/flux create kustomization kube-prometheus-stack \
|
||||||
/tmp/flux create source git podinfo-gogit \
|
--interval=1h \
|
||||||
--git-implementation=go-git \
|
--prune \
|
||||||
--url https://github.com/stefanprodan/podinfo \
|
--source=flux-monitoring \
|
||||||
--tag-semver=">1.0.0" \
|
--path="./manifests/monitoring/kube-prometheus-stack" \
|
||||||
--context ${{ steps.prep.outputs.CONTEXT }}
|
--health-check-timeout=5m \
|
||||||
/tmp/flux create source git podinfo-libgit2 \
|
--wait
|
||||||
--git-implementation=libgit2 \
|
./bin/flux create kustomization monitoring-config \
|
||||||
--url https://github.com/stefanprodan/podinfo \
|
--depends-on=kube-prometheus-stack \
|
||||||
--branch="master" \
|
--interval=1h \
|
||||||
--context ${{ steps.prep.outputs.CONTEXT }}
|
|
||||||
- name: flux create kustomization
|
|
||||||
run: |
|
|
||||||
/tmp/flux create kustomization podinfo \
|
|
||||||
--source=podinfo-gogit \
|
|
||||||
--path="./deploy/overlays/dev" \
|
|
||||||
--prune=true \
|
--prune=true \
|
||||||
--interval=5m \
|
--source=flux-monitoring \
|
||||||
--validation=client \
|
--path="./manifests/monitoring/monitoring-config" \
|
||||||
--health-check="Deployment/frontend.dev" \
|
--health-check-timeout=1m \
|
||||||
--health-check="Deployment/backend.dev" \
|
--wait
|
||||||
--health-check-timeout=3m \
|
kubectl -n flux-system wait kustomization/kube-prometheus-stack --for=condition=ready --timeout=5m
|
||||||
--context ${{ steps.prep.outputs.CONTEXT }}
|
kubectl -n flux-system wait kustomization/monitoring-config --for=condition=ready --timeout=5m
|
||||||
- name: flux create tenant
|
kubectl -n monitoring wait helmrelease/kube-prometheus-stack --for=condition=ready --timeout=1m
|
||||||
run: |
|
|
||||||
/tmp/flux create tenant dev-team \
|
|
||||||
--with-namespace=apps \
|
|
||||||
--context ${{ steps.prep.outputs.CONTEXT }}
|
|
||||||
- name: flux create helmrelease
|
|
||||||
run: |
|
|
||||||
/tmp/flux -n apps create source helm podinfo \
|
|
||||||
--url https://stefanprodan.github.io/podinfo \
|
|
||||||
--context ${{ steps.prep.outputs.CONTEXT }}
|
|
||||||
|
|
||||||
/tmp/flux -n apps create hr podinfo-helm \
|
|
||||||
--source=HelmRepository/podinfo \
|
|
||||||
--chart=podinfo \
|
|
||||||
--chart-version="6.0.x" \
|
|
||||||
--service-account=dev-team \
|
|
||||||
--context ${{ steps.prep.outputs.CONTEXT }}
|
|
||||||
- name: flux get all
|
|
||||||
run: |
|
|
||||||
/tmp/flux get all --all-namespaces \
|
|
||||||
--context ${{ steps.prep.outputs.CONTEXT }}
|
|
||||||
- name: flux uninstall
|
|
||||||
run: |
|
|
||||||
/tmp/flux uninstall -s \
|
|
||||||
--context ${{ steps.prep.outputs.CONTEXT }}
|
|
||||||
- name: Debug failure
|
- name: Debug failure
|
||||||
if: failure()
|
if: failure()
|
||||||
|
env:
|
||||||
|
KUBECONFIG: /tmp/${{ steps.prep.outputs.CLUSTER }}
|
||||||
run: |
|
run: |
|
||||||
kubectl --context ${{ steps.prep.outputs.CONTEXT }} -n flux-system get all
|
kubectl -n flux-system get all
|
||||||
/tmp/flux logs --all-namespaces
|
kubectl -n flux-system describe po
|
||||||
|
kubectl -n flux-system logs deploy/source-controller
|
||||||
|
kubectl -n flux-system logs deploy/kustomize-controller
|
||||||
- name: Cleanup
|
- name: Cleanup
|
||||||
if: always()
|
if: always()
|
||||||
run: |
|
run: |
|
||||||
kind delete cluster --name ${{ steps.prep.outputs.CLUSTER }}
|
kind delete cluster --name ${{ steps.prep.outputs.CLUSTER }}
|
||||||
|
rm /tmp/${{ steps.prep.outputs.CLUSTER }}
|
||||||
|
|||||||
61
.github/workflows/e2e-azure.yaml
vendored
Normal file
61
.github/workflows/e2e-azure.yaml
vendored
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
name: e2e-azure
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 6 * * *'
|
||||||
|
push:
|
||||||
|
branches: [ azure* ]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
e2e-amd64-aks:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b
|
||||||
|
- name: Restore Go cache
|
||||||
|
uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7
|
||||||
|
with:
|
||||||
|
path: ~/go/pkg/mod
|
||||||
|
key: ${{ runner.os }}-go1.18-${{ hashFiles('**/go.sum') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-go1.18-
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568
|
||||||
|
with:
|
||||||
|
go-version: 1.19.x
|
||||||
|
- name: Setup Flux CLI
|
||||||
|
run: |
|
||||||
|
make build
|
||||||
|
mkdir -p $HOME/.local/bin
|
||||||
|
mv ./bin/flux $HOME/.local/bin
|
||||||
|
- name: Setup SOPS
|
||||||
|
run: |
|
||||||
|
wget https://github.com/mozilla/sops/releases/download/v3.7.1/sops-v3.7.1.linux
|
||||||
|
chmod +x sops-v3.7.1.linux
|
||||||
|
mkdir -p $HOME/.local/bin
|
||||||
|
mv sops-v3.7.1.linux $HOME/.local/bin/sops
|
||||||
|
- name: Setup Terraform
|
||||||
|
uses: hashicorp/setup-terraform@633666f66e0061ca3b725c73b2ec20cd13a8fdd1 # v2
|
||||||
|
with:
|
||||||
|
terraform_version: 1.2.8
|
||||||
|
terraform_wrapper: false
|
||||||
|
- name: Setup Azure CLI
|
||||||
|
run: |
|
||||||
|
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
|
||||||
|
- name: Run Azure e2e tests
|
||||||
|
env:
|
||||||
|
ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }}
|
||||||
|
ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }}
|
||||||
|
ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }}
|
||||||
|
ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }}
|
||||||
|
run: |
|
||||||
|
echo $HOME
|
||||||
|
echo $PATH
|
||||||
|
ls $HOME/.local/bin
|
||||||
|
az login --service-principal -u ${ARM_CLIENT_ID} -p ${ARM_CLIENT_SECRET} -t ${ARM_TENANT_ID}
|
||||||
|
cd ./tests/azure
|
||||||
|
go test -v -coverprofile cover.out -timeout 60m .
|
||||||
@@ -1,36 +1,43 @@
|
|||||||
name: bootstrap
|
name: e2e-bootstrap
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches: [ main ]
|
branches: [ main ]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ main ]
|
branches: [ main ]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
github:
|
e2e-boostrap-github:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b
|
||||||
- name: Restore Go cache
|
- name: Restore Go cache
|
||||||
uses: actions/cache@v1
|
uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7
|
||||||
with:
|
with:
|
||||||
path: ~/go/pkg/mod
|
path: ~/go/pkg/mod
|
||||||
key: ${{ runner.os }}-go1.16-${{ hashFiles('**/go.sum') }}
|
key: ${{ runner.os }}-go1.18-${{ hashFiles('**/go.sum') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-go1.16-
|
${{ runner.os }}-go1.18-
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568
|
||||||
with:
|
with:
|
||||||
go-version: 1.16.x
|
go-version: 1.19.x
|
||||||
- name: Setup Kubernetes
|
- name: Setup Kubernetes
|
||||||
uses: engineerd/setup-kind@v0.5.0
|
uses: engineerd/setup-kind@aa272fe2a7309878ffc2a81c56cfe3ef108ae7d0 # v0.5.0
|
||||||
|
with:
|
||||||
|
version: v0.16.0
|
||||||
|
image: kindest/node:v1.25.2@sha256:9be91e9e9cdf116809841fc77ebdb8845443c4c72fe5218f3ae9eb57fdb4bace
|
||||||
- name: Setup Kustomize
|
- name: Setup Kustomize
|
||||||
uses: fluxcd/pkg//actions/kustomize@main
|
uses: fluxcd/pkg//actions/kustomize@main
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
make cmd/flux/manifests
|
make cmd/flux/.manifests.done
|
||||||
go build -o /tmp/flux ./cmd/flux
|
go build -o /tmp/flux ./cmd/flux
|
||||||
- name: Set outputs
|
- name: Set outputs
|
||||||
id: vars
|
id: vars
|
||||||
@@ -61,21 +68,65 @@ jobs:
|
|||||||
--team=team-z
|
--team=team-z
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}
|
||||||
- name: uninstall
|
- name: bootstrap customize
|
||||||
run: |
|
|
||||||
/tmp/flux uninstall -s --keep-namespace
|
|
||||||
kubectl delete ns flux-system --timeout=10m --wait=true
|
|
||||||
- name: bootstrap reinstall
|
|
||||||
run: |
|
run: |
|
||||||
|
make setup-bootstrap-patch
|
||||||
/tmp/flux bootstrap github --manifests ./manifests/install/ \
|
/tmp/flux bootstrap github --manifests ./manifests/install/ \
|
||||||
--owner=fluxcd-testing \
|
--owner=fluxcd-testing \
|
||||||
--repository=${{ steps.vars.outputs.test_repo_name }} \
|
--repository=${{ steps.vars.outputs.test_repo_name }} \
|
||||||
--branch=main \
|
--branch=main \
|
||||||
--path=test-cluster \
|
--path=test-cluster \
|
||||||
--team=team-z
|
--team=team-z
|
||||||
|
if [ $(kubectl get deployments.apps source-controller -o jsonpath='{.spec.template.spec.securityContext.runAsUser}') != "10000" ]; then
|
||||||
|
echo "Bootstrap not customized as controller is not running as user 10000" && exit 1
|
||||||
|
fi
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}
|
||||||
|
GITHUB_REPO_NAME: ${{ steps.vars.outputs.test_repo_name }}
|
||||||
|
GITHUB_ORG_NAME: fluxcd-testing
|
||||||
|
- name: libgit2
|
||||||
|
run: |
|
||||||
|
/tmp/flux create source git test-libgit2 \
|
||||||
|
--url=ssh://git@github.com/fluxcd-testing/${{ steps.vars.outputs.test_repo_name }} \
|
||||||
|
--git-implementation=libgit2 \
|
||||||
|
--secret-ref=flux-system \
|
||||||
|
--branch=main
|
||||||
|
- name: uninstall
|
||||||
|
run: |
|
||||||
|
/tmp/flux uninstall -s --keep-namespace
|
||||||
|
kubectl delete ns flux-system --timeout=10m --wait=true
|
||||||
|
- name: test image automation
|
||||||
|
run: |
|
||||||
|
make setup-image-automation
|
||||||
|
/tmp/flux bootstrap github --manifests ./manifests/install/ \
|
||||||
|
--owner=fluxcd-testing \
|
||||||
|
--repository=${{ steps.vars.outputs.test_repo_name }} \
|
||||||
|
--branch=main \
|
||||||
|
--path=test-cluster \
|
||||||
|
--read-write-key
|
||||||
|
/tmp/flux reconcile image repository podinfo
|
||||||
|
/tmp/flux reconcile image update flux-system
|
||||||
|
/tmp/flux get images all
|
||||||
|
|
||||||
|
retries=10
|
||||||
|
count=0
|
||||||
|
ok=false
|
||||||
|
until ${ok}; do
|
||||||
|
/tmp/flux get image update flux-system | grep 'commit' && ok=true || ok=false
|
||||||
|
count=$(($count + 1))
|
||||||
|
if [[ ${count} -eq ${retries} ]]; then
|
||||||
|
echo "No more retries left"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sleep 6
|
||||||
|
/tmp/flux reconcile image update flux-system
|
||||||
|
done
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}
|
||||||
|
GITHUB_REPO_NAME: ${{ steps.vars.outputs.test_repo_name }}
|
||||||
|
GITHUB_ORG_NAME: fluxcd-testing
|
||||||
- name: delete repository
|
- name: delete repository
|
||||||
|
if: ${{ always() }}
|
||||||
run: |
|
run: |
|
||||||
curl \
|
curl \
|
||||||
-X DELETE \
|
-X DELETE \
|
||||||
110
.github/workflows/e2e.yaml
vendored
110
.github/workflows/e2e.yaml
vendored
@@ -1,42 +1,53 @@
|
|||||||
name: e2e
|
name: e2e
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches: [ main ]
|
branches: [ main ]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ main ]
|
branches: [ main, oci ]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
kind:
|
e2e-amd64-kubernetes:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
services:
|
||||||
|
registry:
|
||||||
|
image: registry:2
|
||||||
|
ports:
|
||||||
|
- 5000:5000
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b
|
||||||
- name: Restore Go cache
|
- name: Restore Go cache
|
||||||
uses: actions/cache@v1
|
uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7
|
||||||
with:
|
with:
|
||||||
path: ~/go/pkg/mod
|
path: ~/go/pkg/mod
|
||||||
key: ${{ runner.os }}-go1.16-${{ hashFiles('**/go.sum') }}
|
key: ${{ runner.os }}-go1.18-${{ hashFiles('**/go.sum') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-go1.16-
|
${{ runner.os }}-go1.18-
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568
|
||||||
with:
|
with:
|
||||||
go-version: 1.16.x
|
go-version: 1.19.x
|
||||||
- name: Setup Kubernetes
|
- name: Setup Kubernetes
|
||||||
uses: engineerd/setup-kind@v0.5.0
|
uses: engineerd/setup-kind@aa272fe2a7309878ffc2a81c56cfe3ef108ae7d0 # v0.5.0
|
||||||
with:
|
with:
|
||||||
version: "v0.10.0"
|
version: v0.11.1
|
||||||
image: kindest/node:v1.20.2@sha256:8f7ea6e7642c0da54f04a7ee10431549c0257315b3a634f6ef2fecaaedb19bab
|
image: kindest/node:v1.23.13
|
||||||
config: .github/kind/config.yaml # disable KIND-net
|
config: .github/kind/config.yaml # disable KIND-net
|
||||||
- name: Setup Calico for network policy
|
- name: Setup Calico for network policy
|
||||||
run: |
|
run: |
|
||||||
kubectl apply -f https://docs.projectcalico.org/v3.16/manifests/calico.yaml
|
kubectl apply -f https://docs.projectcalico.org/v3.20/manifests/calico.yaml
|
||||||
kubectl -n kube-system set env daemonset/calico-node FELIX_IGNORELOOSERPF=true
|
kubectl -n kube-system set env daemonset/calico-node FELIX_IGNORELOOSERPF=true
|
||||||
- name: Setup Kustomize
|
- name: Setup Kustomize
|
||||||
uses: fluxcd/pkg//actions/kustomize@main
|
uses: fluxcd/pkg//actions/kustomize@main
|
||||||
- name: Run test
|
- name: Run tests
|
||||||
run: make test
|
run: make test
|
||||||
|
- name: Run e2e tests
|
||||||
|
run: TEST_KUBECONFIG=$HOME/.kube/config make e2e
|
||||||
- name: Check if working tree is dirty
|
- name: Check if working tree is dirty
|
||||||
run: |
|
run: |
|
||||||
if [[ $(git diff --stat) != '' ]]; then
|
if [[ $(git diff --stat) != '' ]]; then
|
||||||
@@ -74,6 +85,13 @@ jobs:
|
|||||||
--tag-semver=">=3.2.3" \
|
--tag-semver=">=3.2.3" \
|
||||||
--export | kubectl apply -f -
|
--export | kubectl apply -f -
|
||||||
/tmp/flux delete source git podinfo-export --silent
|
/tmp/flux delete source git podinfo-export --silent
|
||||||
|
- name: flux create source git libgit2 semver
|
||||||
|
run: |
|
||||||
|
/tmp/flux create source git podinfo-libgit2 \
|
||||||
|
--url https://github.com/stefanprodan/podinfo \
|
||||||
|
--tag-semver=">=3.2.3" \
|
||||||
|
--git-implementation=libgit2
|
||||||
|
/tmp/flux delete source git podinfo-libgit2 --silent
|
||||||
- name: flux get sources git
|
- name: flux get sources git
|
||||||
run: |
|
run: |
|
||||||
/tmp/flux get sources git
|
/tmp/flux get sources git
|
||||||
@@ -87,10 +105,15 @@ jobs:
|
|||||||
--path="./deploy/overlays/dev" \
|
--path="./deploy/overlays/dev" \
|
||||||
--prune=true \
|
--prune=true \
|
||||||
--interval=5m \
|
--interval=5m \
|
||||||
--validation=client \
|
|
||||||
--health-check="Deployment/frontend.dev" \
|
--health-check="Deployment/frontend.dev" \
|
||||||
--health-check="Deployment/backend.dev" \
|
--health-check="Deployment/backend.dev" \
|
||||||
--health-check-timeout=3m
|
--health-check-timeout=3m
|
||||||
|
- name: flux trace
|
||||||
|
run: |
|
||||||
|
/tmp/flux trace frontend \
|
||||||
|
--kind=deployment \
|
||||||
|
--api-version=apps/v1 \
|
||||||
|
--namespace=dev
|
||||||
- name: flux reconcile kustomization --with-source
|
- name: flux reconcile kustomization --with-source
|
||||||
run: |
|
run: |
|
||||||
/tmp/flux reconcile kustomization podinfo --with-source
|
/tmp/flux reconcile kustomization podinfo --with-source
|
||||||
@@ -154,6 +177,36 @@ jobs:
|
|||||||
- name: flux delete source git
|
- name: flux delete source git
|
||||||
run: |
|
run: |
|
||||||
/tmp/flux delete source git podinfo --silent
|
/tmp/flux delete source git podinfo --silent
|
||||||
|
- name: flux oci artifacts
|
||||||
|
run: |
|
||||||
|
/tmp/flux push artifact oci://localhost:5000/fluxcd/flux:${{ github.sha }} \
|
||||||
|
--path="./manifests" \
|
||||||
|
--source="${{ github.repositoryUrl }}" \
|
||||||
|
--revision="${{ github.ref }}/${{ github.sha }}"
|
||||||
|
/tmp/flux tag artifact oci://localhost:5000/fluxcd/flux:${{ github.sha }} \
|
||||||
|
--tag latest
|
||||||
|
/tmp/flux list artifacts oci://localhost:5000/fluxcd/flux
|
||||||
|
- name: flux oci repositories
|
||||||
|
run: |
|
||||||
|
/tmp/flux create source oci podinfo-oci \
|
||||||
|
--url oci://ghcr.io/stefanprodan/manifests/podinfo \
|
||||||
|
--tag-semver 6.1.x \
|
||||||
|
--interval 10m
|
||||||
|
/tmp/flux create kustomization podinfo-oci \
|
||||||
|
--source=OCIRepository/podinfo-oci \
|
||||||
|
--path="./kustomize" \
|
||||||
|
--prune=true \
|
||||||
|
--interval=5m \
|
||||||
|
--target-namespace=default \
|
||||||
|
--wait=true \
|
||||||
|
--health-check-timeout=3m
|
||||||
|
/tmp/flux reconcile source oci podinfo-oci
|
||||||
|
/tmp/flux suspend source oci podinfo-oci
|
||||||
|
/tmp/flux get sources oci
|
||||||
|
/tmp/flux resume source oci podinfo-oci
|
||||||
|
/tmp/flux export source oci podinfo-oci
|
||||||
|
/tmp/flux delete ks podinfo-oci --silent
|
||||||
|
/tmp/flux delete source oci podinfo-oci --silent
|
||||||
- name: flux create tenant
|
- name: flux create tenant
|
||||||
run: |
|
run: |
|
||||||
/tmp/flux create tenant dev-team --with-namespace=apps
|
/tmp/flux create tenant dev-team --with-namespace=apps
|
||||||
@@ -164,36 +217,22 @@ jobs:
|
|||||||
--chart=podinfo \
|
--chart=podinfo \
|
||||||
--chart-version="5.0.x" \
|
--chart-version="5.0.x" \
|
||||||
--service-account=dev-team
|
--service-account=dev-team
|
||||||
- name: flux create image repository
|
|
||||||
run: |
|
|
||||||
/tmp/flux create image repository podinfo \
|
|
||||||
--image=ghcr.io/stefanprodan/podinfo \
|
|
||||||
--interval=1m
|
|
||||||
- name: flux create image policy
|
|
||||||
run: |
|
|
||||||
/tmp/flux create image policy podinfo \
|
|
||||||
--image-ref=podinfo \
|
|
||||||
--interval=1m \
|
|
||||||
--select-semver=5.0.x
|
|
||||||
- name: flux create image policy podinfo-select-alpha
|
|
||||||
run: |
|
|
||||||
/tmp/flux create image policy podinfo-alpha \
|
|
||||||
--image-ref=podinfo \
|
|
||||||
--interval=1m \
|
|
||||||
--select-alpha=desc
|
|
||||||
- name: flux get image policy
|
|
||||||
run: |
|
|
||||||
/tmp/flux get image policy podinfo | grep '5.0.3'
|
|
||||||
- name: flux2-kustomize-helm-example
|
- name: flux2-kustomize-helm-example
|
||||||
run: |
|
run: |
|
||||||
/tmp/flux create source git flux-system \
|
/tmp/flux create source git flux-system \
|
||||||
--url=https://github.com/fluxcd/flux2-kustomize-helm-example \
|
--url=https://github.com/fluxcd/flux2-kustomize-helm-example \
|
||||||
--branch=main \
|
--branch=main \
|
||||||
|
--ignore-paths="./clusters/**/flux-system/" \
|
||||||
--recurse-submodules
|
--recurse-submodules
|
||||||
/tmp/flux create kustomization flux-system \
|
/tmp/flux create kustomization flux-system \
|
||||||
--source=flux-system \
|
--source=flux-system \
|
||||||
--path=./clusters/staging
|
--path=./clusters/staging
|
||||||
kubectl -n flux-system wait kustomization/apps --for=condition=ready --timeout=2m
|
kubectl -n flux-system wait kustomization/infra-controllers --for=condition=ready --timeout=5m
|
||||||
|
kubectl -n flux-system wait kustomization/apps --for=condition=ready --timeout=5m
|
||||||
|
kubectl -n podinfo wait helmrelease/podinfo --for=condition=ready --timeout=5m
|
||||||
|
- name: flux tree
|
||||||
|
run: |
|
||||||
|
/tmp/flux tree kustomization flux-system | grep Service/podinfo
|
||||||
- name: flux check
|
- name: flux check
|
||||||
run: |
|
run: |
|
||||||
/tmp/flux check
|
/tmp/flux check
|
||||||
@@ -205,6 +244,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
kubectl version --client --short
|
kubectl version --client --short
|
||||||
kubectl -n flux-system get all
|
kubectl -n flux-system get all
|
||||||
|
kubectl -n flux-system describe pods
|
||||||
kubectl -n flux-system get kustomizations -oyaml
|
kubectl -n flux-system get kustomizations -oyaml
|
||||||
kubectl -n flux-system logs deploy/source-controller
|
kubectl -n flux-system logs deploy/source-controller
|
||||||
kubectl -n flux-system logs deploy/kustomize-controller
|
kubectl -n flux-system logs deploy/kustomize-controller
|
||||||
|
|||||||
21
.github/workflows/rebase.yaml
vendored
21
.github/workflows/rebase.yaml
vendored
@@ -1,21 +0,0 @@
|
|||||||
name: rebase
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
types: [ opened ]
|
|
||||||
issue_comment:
|
|
||||||
types: [ created ]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
rebase:
|
|
||||||
if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/rebase') && (github.event.comment.author_association == 'CONTRIBUTOR' || github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER')
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout the latest code
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Automatic Rebase
|
|
||||||
uses: cirrus-actions/rebase@1.3.1
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }}
|
|
||||||
128
.github/workflows/release.yaml
vendored
128
.github/workflows/release.yaml
vendored
@@ -4,53 +4,50 @@ on:
|
|||||||
push:
|
push:
|
||||||
tags: [ 'v*' ]
|
tags: [ 'v*' ]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
goreleaser:
|
release-flux-cli:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write # needed to write releases
|
||||||
|
id-token: write # needed for keyless signing
|
||||||
|
packages: write # needed for ghcr access
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b
|
||||||
- name: Unshallow
|
- name: Unshallow
|
||||||
run: git fetch --prune --unshallow
|
run: git fetch --prune --unshallow
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568
|
||||||
with:
|
with:
|
||||||
go-version: 1.16.x
|
go-version: 1.19.x
|
||||||
- name: Setup QEMU
|
- name: Setup QEMU
|
||||||
uses: docker/setup-qemu-action@v1
|
uses: docker/setup-qemu-action@e81a89b1732b9c48d79cd809d8d81d79c4647a18 # v2
|
||||||
with:
|
|
||||||
platforms: all
|
|
||||||
- name: Setup Docker Buildx
|
- name: Setup Docker Buildx
|
||||||
id: buildx
|
id: buildx
|
||||||
uses: docker/setup-buildx-action@v1
|
uses: docker/setup-buildx-action@8c0edbc76e98fa90f69d9a2c020dcb50019dc325 # v2
|
||||||
with:
|
- name: Setup Syft
|
||||||
buildkitd-flags: "--debug"
|
uses: anchore/sbom-action/download-syft@06e109483e6aa305a2b2395eabae554e51530e1d # v0.13.1
|
||||||
|
- name: Setup Cosign
|
||||||
|
uses: sigstore/cosign-installer@9becc617647dfa20ae7b1151972e9b3a2c338a2b # v2.8.1
|
||||||
|
- name: Setup Kustomize
|
||||||
|
uses: fluxcd/pkg//actions/kustomize@main
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v1
|
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # v2
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: fluxcdbot
|
username: fluxcdbot
|
||||||
password: ${{ secrets.GHCR_TOKEN }}
|
password: ${{ secrets.GHCR_TOKEN }}
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@v1
|
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # v2
|
||||||
with:
|
with:
|
||||||
username: fluxcdbot
|
username: fluxcdbot
|
||||||
password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
|
password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
|
||||||
- name: Download release notes utility
|
|
||||||
env:
|
|
||||||
GH_REL_URL: https://github.com/buchanae/github-release-notes/releases/download/0.2.0/github-release-notes-linux-amd64-0.2.0.tar.gz
|
|
||||||
run: cd /tmp && curl -sSL ${GH_REL_URL} | tar xz && sudo mv github-release-notes /usr/local/bin/
|
|
||||||
- name: Generate release notes
|
|
||||||
run: |
|
|
||||||
echo 'CHANGELOG' > /tmp/release.txt
|
|
||||||
github-release-notes -org fluxcd -repo toolkit -since-latest-release -include-author >> /tmp/release.txt
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: Setup Kustomize
|
|
||||||
uses: fluxcd/pkg//actions/kustomize@main
|
|
||||||
- name: Generate manifests
|
- name: Generate manifests
|
||||||
run: |
|
run: |
|
||||||
make cmd/flux/manifests
|
make cmd/flux/.manifests.done
|
||||||
./manifests/scripts/bundle.sh "" ./output manifests.tar.gz
|
./manifests/scripts/bundle.sh "" ./output manifests.tar.gz
|
||||||
kustomize build ./manifests/install > ./output/install.yaml
|
kustomize build ./manifests/install > ./output/install.yaml
|
||||||
- name: Build CRDs
|
- name: Build CRDs
|
||||||
@@ -64,12 +61,89 @@ jobs:
|
|||||||
- name: Archive the OpenAPI JSON schemas
|
- name: Archive the OpenAPI JSON schemas
|
||||||
run: |
|
run: |
|
||||||
tar -czvf ./output/crd-schemas.tar.gz -C schemas .
|
tar -czvf ./output/crd-schemas.tar.gz -C schemas .
|
||||||
|
- name: Download release notes utility
|
||||||
|
env:
|
||||||
|
GH_REL_URL: https://github.com/buchanae/github-release-notes/releases/download/0.2.0/github-release-notes-linux-amd64-0.2.0.tar.gz
|
||||||
|
run: cd /tmp && curl -sSL ${GH_REL_URL} | tar xz && sudo mv github-release-notes /usr/local/bin/
|
||||||
|
- name: Generate release notes
|
||||||
|
run: |
|
||||||
|
NOTES="./output/notes.md"
|
||||||
|
echo '## CLI Changelog' > ${NOTES}
|
||||||
|
github-release-notes -org fluxcd -repo flux2 -since-latest-release -include-author >> ${NOTES}
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Run GoReleaser
|
- name: Run GoReleaser
|
||||||
uses: goreleaser/goreleaser-action@v1
|
uses: goreleaser/goreleaser-action@8f67e590f2d095516493f017008adc464e63adb1 # v3
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
args: release --release-notes=/tmp/release.txt --skip-validate
|
args: release --release-notes=output/notes.md --skip-validate
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }}
|
HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }}
|
||||||
AUR_BOT_SSH_PRIVATE_KEY: ${{ secrets.AUR_BOT_SSH_PRIVATE_KEY }}
|
AUR_BOT_SSH_PRIVATE_KEY: ${{ secrets.AUR_BOT_SSH_PRIVATE_KEY }}
|
||||||
|
release-flux-manifests:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: release-flux-cli
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
packages: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b
|
||||||
|
- name: Setup Kustomize
|
||||||
|
uses: fluxcd/pkg/actions/kustomize@main
|
||||||
|
- name: Setup Flux CLI
|
||||||
|
uses: ./action/
|
||||||
|
- name: Prepare
|
||||||
|
id: prep
|
||||||
|
run: |
|
||||||
|
VERSION=$(flux version --client | awk '{ print $NF }')
|
||||||
|
echo ::set-output name=VERSION::${VERSION}
|
||||||
|
- name: Login to GHCR
|
||||||
|
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # v2
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: fluxcdbot
|
||||||
|
password: ${{ secrets.GHCR_TOKEN }}
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # v2
|
||||||
|
with:
|
||||||
|
username: fluxcdbot
|
||||||
|
password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
|
||||||
|
- name: Push manifests to GHCR
|
||||||
|
run: |
|
||||||
|
mkdir -p ./ghcr.io/flux-system
|
||||||
|
flux install --registry=ghcr.io/fluxcd \
|
||||||
|
--components-extra=image-reflector-controller,image-automation-controller \
|
||||||
|
--export > ./ghcr.io/flux-system/gotk-components.yaml
|
||||||
|
|
||||||
|
cd ./ghcr.io && flux push artifact \
|
||||||
|
oci://ghcr.io/fluxcd/flux-manifests:${{ steps.prep.outputs.VERSION }} \
|
||||||
|
--path="./flux-system" \
|
||||||
|
--source=${{ github.repositoryUrl }} \
|
||||||
|
--revision="${{ github.ref_name }}/${{ github.sha }}"
|
||||||
|
- name: Push manifests to DockerHub
|
||||||
|
run: |
|
||||||
|
mkdir -p ./docker.io/flux-system
|
||||||
|
flux install --registry=docker.io/fluxcd \
|
||||||
|
--components-extra=image-reflector-controller,image-automation-controller \
|
||||||
|
--export > ./docker.io/flux-system/gotk-components.yaml
|
||||||
|
|
||||||
|
cd ./docker.io && flux push artifact \
|
||||||
|
oci://docker.io/fluxcd/flux-manifests:${{ steps.prep.outputs.VERSION }} \
|
||||||
|
--path="./flux-system" \
|
||||||
|
--source=${{ github.repositoryUrl }} \
|
||||||
|
--revision="${{ github.ref_name }}/${{ github.sha }}"
|
||||||
|
- uses: sigstore/cosign-installer@9becc617647dfa20ae7b1151972e9b3a2c338a2b # v2.8.1
|
||||||
|
- name: Sign manifests
|
||||||
|
env:
|
||||||
|
COSIGN_EXPERIMENTAL: 1
|
||||||
|
run: |
|
||||||
|
cosign sign ghcr.io/fluxcd/flux-manifests:${{ steps.prep.outputs.VERSION }}
|
||||||
|
cosign sign docker.io/fluxcd/flux-manifests:${{ steps.prep.outputs.VERSION }}
|
||||||
|
- name: Tag manifests
|
||||||
|
run: |
|
||||||
|
flux tag artifact oci://ghcr.io/fluxcd/flux-manifests:${{ steps.prep.outputs.VERSION }} \
|
||||||
|
--tag latest
|
||||||
|
|
||||||
|
flux tag artifact oci://docker.io/fluxcd/flux-manifests:${{ steps.prep.outputs.VERSION }} \
|
||||||
|
--tag latest
|
||||||
|
|||||||
47
.github/workflows/scan.yaml
vendored
47
.github/workflows/scan.yaml
vendored
@@ -1,6 +1,7 @@
|
|||||||
name: Scan
|
name: scan
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches: [ main ]
|
branches: [ main ]
|
||||||
pull_request:
|
pull_request:
|
||||||
@@ -8,53 +9,63 @@ on:
|
|||||||
schedule:
|
schedule:
|
||||||
- cron: '18 10 * * 3'
|
- cron: '18 10 * * 3'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
fossa:
|
scan-fossa:
|
||||||
name: FOSSA
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
if: github.actor != 'dependabot[bot]'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b
|
||||||
- name: Run FOSSA scan and upload build data
|
- name: Run FOSSA scan and upload build data
|
||||||
uses: fossa-contrib/fossa-action@v1
|
uses: fossa-contrib/fossa-action@6cffaa064112e1cf9b5798c6224f9487dc1ec316 # v1
|
||||||
with:
|
with:
|
||||||
# FOSSA Push-Only API Token
|
# FOSSA Push-Only API Token
|
||||||
fossa-api-key: 5ee8bf422db1471e0bcf2bcb289185de
|
fossa-api-key: 5ee8bf422db1471e0bcf2bcb289185de
|
||||||
github-token: ${{ github.token }}
|
github-token: ${{ github.token }}
|
||||||
|
|
||||||
snyk:
|
scan-snyk:
|
||||||
name: Snyk
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
permissions:
|
||||||
|
security-events: write
|
||||||
|
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b
|
||||||
- name: Setup Kustomize
|
- name: Setup Kustomize
|
||||||
uses: fluxcd/pkg//actions/kustomize@main
|
uses: fluxcd/pkg//actions/kustomize@main
|
||||||
- name: Build manifests
|
- name: Build manifests
|
||||||
run: |
|
run: |
|
||||||
make cmd/flux/manifests
|
make cmd/flux/.manifests.done
|
||||||
- name: Run Snyk to check for vulnerabilities
|
- name: Run Snyk to check for vulnerabilities
|
||||||
uses: snyk/actions/golang@master
|
uses: snyk/actions/golang@1cc9026f51d822442cb4b872d8d7ead8cc69a018 # v0.3.0
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
env:
|
env:
|
||||||
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
||||||
with:
|
with:
|
||||||
args: --sarif-file-output=snyk.sarif
|
args: --sarif-file-output=snyk.sarif
|
||||||
- name: Upload result to GitHub Code Scanning
|
- name: Upload result to GitHub Code Scanning
|
||||||
uses: github/codeql-action/upload-sarif@v1
|
uses: github/codeql-action/upload-sarif@959cbb7472c4d4ad70cdfe6f4976053fe48ab394 # v2
|
||||||
with:
|
with:
|
||||||
sarif_file: snyk.sarif
|
sarif_file: snyk.sarif
|
||||||
|
|
||||||
codeql:
|
scan-codeql:
|
||||||
name: CodeQL
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
security-events: write
|
||||||
|
if: github.actor != 'dependabot[bot]'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568
|
||||||
|
with:
|
||||||
|
go-version: 1.19.x
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v1
|
uses: github/codeql-action/init@959cbb7472c4d4ad70cdfe6f4976053fe48ab394 # v2
|
||||||
with:
|
with:
|
||||||
languages: go
|
languages: go
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@v1
|
uses: github/codeql-action/autobuild@959cbb7472c4d4ad70cdfe6f4976053fe48ab394 # v2
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v1
|
uses: github/codeql-action/analyze@959cbb7472c4d4ad70cdfe6f4976053fe48ab394 # v2
|
||||||
|
|||||||
19
.github/workflows/update.yaml
vendored
19
.github/workflows/update.yaml
vendored
@@ -1,4 +1,4 @@
|
|||||||
name: Update Components
|
name: update
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
@@ -7,16 +7,22 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
update-components:
|
update-components:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code
|
- name: Check out code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568
|
||||||
with:
|
with:
|
||||||
go-version: 1.16.x
|
go-version: 1.19.x
|
||||||
- name: Update component versions
|
- name: Update component versions
|
||||||
id: update
|
id: update
|
||||||
run: |
|
run: |
|
||||||
@@ -42,8 +48,7 @@ jobs:
|
|||||||
|
|
||||||
if [[ "${MOD_VERSION}" != "${LATEST_VERSION}" ]]; then
|
if [[ "${MOD_VERSION}" != "${LATEST_VERSION}" ]]; then
|
||||||
go mod edit -require="github.com/fluxcd/$1/api@${LATEST_VERSION}"
|
go mod edit -require="github.com/fluxcd/$1/api@${LATEST_VERSION}"
|
||||||
rm go.sum
|
make tidy
|
||||||
go mod tidy
|
|
||||||
changed=true
|
changed=true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -70,7 +75,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
id: cpr
|
id: cpr
|
||||||
uses: peter-evans/create-pull-request@v3
|
uses: peter-evans/create-pull-request@2b011faafdcbc9ceb11414d64d0573f37c774b04 # v4
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.BOT_GITHUB_TOKEN }}
|
token: ${{ secrets.BOT_GITHUB_TOKEN }}
|
||||||
commit-message: |
|
commit-message: |
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -19,6 +19,8 @@ dist/
|
|||||||
bin/
|
bin/
|
||||||
output/
|
output/
|
||||||
cmd/flux/manifests/
|
cmd/flux/manifests/
|
||||||
|
cmd/flux/.manifests.done
|
||||||
|
testbin/
|
||||||
|
|
||||||
# Docs
|
# Docs
|
||||||
site/
|
site/
|
||||||
|
|||||||
@@ -40,6 +40,36 @@ archives:
|
|||||||
format: zip
|
format: zip
|
||||||
files:
|
files:
|
||||||
- none*
|
- none*
|
||||||
|
source:
|
||||||
|
enabled: true
|
||||||
|
name_template: '{{ .ProjectName }}_{{ .Version }}_source_code'
|
||||||
|
sboms:
|
||||||
|
- id: source
|
||||||
|
artifacts: source
|
||||||
|
documents:
|
||||||
|
- "{{ .ProjectName }}_{{ .Version }}_sbom.spdx.json"
|
||||||
|
release:
|
||||||
|
extra_files:
|
||||||
|
- glob: output/crd-schemas.tar.gz
|
||||||
|
- glob: output/manifests.tar.gz
|
||||||
|
- glob: output/install.yaml
|
||||||
|
checksum:
|
||||||
|
extra_files:
|
||||||
|
- glob: output/crd-schemas.tar.gz
|
||||||
|
- glob: output/manifests.tar.gz
|
||||||
|
- glob: output/install.yaml
|
||||||
|
signs:
|
||||||
|
- cmd: cosign
|
||||||
|
env:
|
||||||
|
- COSIGN_EXPERIMENTAL=1
|
||||||
|
certificate: '${artifact}.pem'
|
||||||
|
args:
|
||||||
|
- sign-blob
|
||||||
|
- '--output-certificate=${certificate}'
|
||||||
|
- '--output-signature=${signature}'
|
||||||
|
- '${artifact}'
|
||||||
|
artifacts: checksum
|
||||||
|
output: true
|
||||||
brews:
|
brews:
|
||||||
- name: flux
|
- name: flux
|
||||||
tap:
|
tap:
|
||||||
@@ -49,9 +79,17 @@ brews:
|
|||||||
folder: Formula
|
folder: Formula
|
||||||
homepage: "https://fluxcd.io/"
|
homepage: "https://fluxcd.io/"
|
||||||
description: "Flux CLI"
|
description: "Flux CLI"
|
||||||
dependencies:
|
install: |
|
||||||
- name: kubectl
|
bin.install "flux"
|
||||||
type: optional
|
|
||||||
|
bash_output = Utils.safe_popen_read(bin/"flux", "completion", "bash")
|
||||||
|
(bash_completion/"flux").write bash_output
|
||||||
|
|
||||||
|
zsh_output = Utils.safe_popen_read(bin/"flux", "completion", "zsh")
|
||||||
|
(zsh_completion/"_flux").write zsh_output
|
||||||
|
|
||||||
|
fish_output = Utils.safe_popen_read(bin/"flux", "completion", "fish")
|
||||||
|
(fish_completion/"flux.fish").write fish_output
|
||||||
test: |
|
test: |
|
||||||
system "#{bin}/flux --version"
|
system "#{bin}/flux --version"
|
||||||
publishers:
|
publishers:
|
||||||
@@ -70,17 +108,12 @@ publishers:
|
|||||||
- AUR_BOT_SSH_PRIVATE_KEY={{ .Env.AUR_BOT_SSH_PRIVATE_KEY }}
|
- AUR_BOT_SSH_PRIVATE_KEY={{ .Env.AUR_BOT_SSH_PRIVATE_KEY }}
|
||||||
cmd: |
|
cmd: |
|
||||||
.github/aur/flux-go/publish.sh {{ .Version }}
|
.github/aur/flux-go/publish.sh {{ .Version }}
|
||||||
release:
|
|
||||||
extra_files:
|
|
||||||
- glob: ./output/crd-schemas.tar.gz
|
|
||||||
- glob: ./output/manifests.tar.gz
|
|
||||||
- glob: ./output/install.yaml
|
|
||||||
dockers:
|
dockers:
|
||||||
- image_templates:
|
- image_templates:
|
||||||
- 'fluxcd/flux-cli:{{ .Tag }}-amd64'
|
- 'fluxcd/flux-cli:{{ .Tag }}-amd64'
|
||||||
- 'ghcr.io/fluxcd/flux-cli:{{ .Tag }}-amd64'
|
- 'ghcr.io/fluxcd/flux-cli:{{ .Tag }}-amd64'
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
use_buildx: true
|
use: buildx
|
||||||
goos: linux
|
goos: linux
|
||||||
goarch: amd64
|
goarch: amd64
|
||||||
build_flag_templates:
|
build_flag_templates:
|
||||||
@@ -96,7 +129,7 @@ dockers:
|
|||||||
- 'fluxcd/flux-cli:{{ .Tag }}-arm64'
|
- 'fluxcd/flux-cli:{{ .Tag }}-arm64'
|
||||||
- 'ghcr.io/fluxcd/flux-cli:{{ .Tag }}-arm64'
|
- 'ghcr.io/fluxcd/flux-cli:{{ .Tag }}-arm64'
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
use_buildx: true
|
use: buildx
|
||||||
goos: linux
|
goos: linux
|
||||||
goarch: arm64
|
goarch: arm64
|
||||||
build_flag_templates:
|
build_flag_templates:
|
||||||
@@ -112,7 +145,7 @@ dockers:
|
|||||||
- 'fluxcd/flux-cli:{{ .Tag }}-arm'
|
- 'fluxcd/flux-cli:{{ .Tag }}-arm'
|
||||||
- 'ghcr.io/fluxcd/flux-cli:{{ .Tag }}-arm'
|
- 'ghcr.io/fluxcd/flux-cli:{{ .Tag }}-arm'
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
use_buildx: true
|
use: buildx
|
||||||
goos: linux
|
goos: linux
|
||||||
goarch: arm
|
goarch: arm
|
||||||
goarm: 7
|
goarm: 7
|
||||||
@@ -136,3 +169,12 @@ docker_manifests:
|
|||||||
- 'ghcr.io/fluxcd/flux-cli:{{ .Tag }}-amd64'
|
- 'ghcr.io/fluxcd/flux-cli:{{ .Tag }}-amd64'
|
||||||
- 'ghcr.io/fluxcd/flux-cli:{{ .Tag }}-arm64'
|
- 'ghcr.io/fluxcd/flux-cli:{{ .Tag }}-arm64'
|
||||||
- 'ghcr.io/fluxcd/flux-cli:{{ .Tag }}-arm'
|
- 'ghcr.io/fluxcd/flux-cli:{{ .Tag }}-arm'
|
||||||
|
docker_signs:
|
||||||
|
- cmd: cosign
|
||||||
|
env:
|
||||||
|
- COSIGN_EXPERIMENTAL=1
|
||||||
|
args:
|
||||||
|
- sign
|
||||||
|
- '${artifact}'
|
||||||
|
artifacts: all
|
||||||
|
output: true
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ you can sign your commit automatically with `git commit -s`.
|
|||||||
|
|
||||||
For realtime communications we use Slack: To join the conversation, simply
|
For realtime communications we use Slack: To join the conversation, simply
|
||||||
join the [CNCF](https://slack.cncf.io/) Slack workspace and use the
|
join the [CNCF](https://slack.cncf.io/) Slack workspace and use the
|
||||||
[#flux-dev](https://cloud-native.slack.com/messages/flux-dev/) channel.
|
[#flux-contributors](https://cloud-native.slack.com/messages/flux-contributors/) channel.
|
||||||
|
|
||||||
To discuss ideas and specifications we use [Github
|
To discuss ideas and specifications we use [Github
|
||||||
Discussions](https://github.com/fluxcd/flux2/discussions).
|
Discussions](https://github.com/fluxcd/flux2/discussions).
|
||||||
@@ -59,24 +59,69 @@ This project is composed of:
|
|||||||
### Understanding the code
|
### Understanding the code
|
||||||
|
|
||||||
To get started with developing controllers, you might want to review
|
To get started with developing controllers, you might want to review
|
||||||
[our guide](https://fluxcd.io/docs/gitops-toolkit/source-watcher/) which
|
[our guide](https://fluxcd.io/flux/gitops-toolkit/source-watcher/) which
|
||||||
walks you through writing a short and concise controller that watches out
|
walks you through writing a short and concise controller that watches out
|
||||||
for source changes.
|
for source changes.
|
||||||
|
|
||||||
### How to run the test suite
|
## How to run the test suite
|
||||||
|
|
||||||
Prerequisites:
|
Prerequisites:
|
||||||
|
|
||||||
* go >= 1.16
|
* go >= 1.19
|
||||||
* kubectl >= 1.18
|
* kubectl >= 1.20
|
||||||
* kustomize >= 3.1
|
* kustomize >= 4.4
|
||||||
|
* coreutils (on Mac OS)
|
||||||
|
|
||||||
You can run the unit tests by simply doing
|
Install the [controller-runtime/envtest](https://github.com/kubernetes-sigs/controller-runtime/tree/master/tools/setup-envtest) binaries with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make install-envtest
|
||||||
|
```
|
||||||
|
|
||||||
|
Then you can run the unit tests with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
make test
|
make test
|
||||||
```
|
```
|
||||||
|
|
||||||
|
After [installing Kubernetes kind](https://kind.sigs.k8s.io/docs/user/quick-start#installation) on your machine,
|
||||||
|
create a cluster for testing with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make setup-kind
|
||||||
|
```
|
||||||
|
|
||||||
|
Then you can run the end-to-end tests with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make e2e
|
||||||
|
```
|
||||||
|
|
||||||
|
When the output of the Flux CLI changes, to automatically update the golden
|
||||||
|
files used in the test, pass `-update` flag to the test as:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make e2e TEST_ARGS="-update"
|
||||||
|
```
|
||||||
|
|
||||||
|
Since not all packages use golden files for testing, `-update` argument must be
|
||||||
|
passed only for the packages that use golden files. Use the variables
|
||||||
|
`TEST_PKG_PATH` for unit tests and `E2E_TEST_PKG_PATH` for e2e tests, to set the
|
||||||
|
path of the target test package:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Unit test
|
||||||
|
make test TEST_PKG_PATH="./cmd/flux" TEST_ARGS="-update"
|
||||||
|
# e2e test
|
||||||
|
make e2e E2E_TEST_PKG_PATH="./cmd/flux" TEST_ARGS="-update"
|
||||||
|
```
|
||||||
|
|
||||||
|
Teardown the e2e environment with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make cleanup-kind
|
||||||
|
```
|
||||||
|
|
||||||
## Acceptance policy
|
## Acceptance policy
|
||||||
|
|
||||||
These things will make a PR more likely to be accepted:
|
These things will make a PR more likely to be accepted:
|
||||||
|
|||||||
11
Dockerfile
11
Dockerfile
@@ -1,23 +1,20 @@
|
|||||||
FROM alpine:3.13 as builder
|
FROM alpine:3.16 as builder
|
||||||
|
|
||||||
RUN apk add --no-cache ca-certificates curl
|
RUN apk add --no-cache ca-certificates curl
|
||||||
|
|
||||||
ARG ARCH=linux/amd64
|
ARG ARCH=linux/amd64
|
||||||
ARG KUBECTL_VER=1.20.4
|
ARG KUBECTL_VER=1.25.4
|
||||||
|
|
||||||
RUN curl -sL https://storage.googleapis.com/kubernetes-release/release/v${KUBECTL_VER}/bin/${ARCH}/kubectl \
|
RUN curl -sL https://storage.googleapis.com/kubernetes-release/release/v${KUBECTL_VER}/bin/${ARCH}/kubectl \
|
||||||
-o /usr/local/bin/kubectl && chmod +x /usr/local/bin/kubectl && \
|
-o /usr/local/bin/kubectl && chmod +x /usr/local/bin/kubectl && \
|
||||||
kubectl version --client=true
|
kubectl version --client=true
|
||||||
|
|
||||||
FROM alpine:3.13 as flux-cli
|
FROM alpine:3.16 as flux-cli
|
||||||
|
|
||||||
# Create minimal nsswitch.conf file to prioritize the usage of /etc/hosts over DNS queries.
|
|
||||||
# https://github.com/gliderlabs/docker-alpine/issues/367#issuecomment-354316460
|
|
||||||
RUN [ ! -e /etc/nsswitch.conf ] && echo 'hosts: files dns' > /etc/nsswitch.conf
|
|
||||||
|
|
||||||
RUN apk add --no-cache ca-certificates
|
RUN apk add --no-cache ca-certificates
|
||||||
|
|
||||||
COPY --from=builder /usr/local/bin/kubectl /usr/local/bin/
|
COPY --from=builder /usr/local/bin/kubectl /usr/local/bin/
|
||||||
COPY --chmod=755 flux /usr/local/bin/
|
COPY --chmod=755 flux /usr/local/bin/
|
||||||
|
|
||||||
|
USER 65534:65534
|
||||||
ENTRYPOINT [ "flux" ]
|
ENTRYPOINT [ "flux" ]
|
||||||
|
|||||||
16
MAINTAINERS
16
MAINTAINERS
@@ -2,17 +2,7 @@ The maintainers are generally available in Slack at
|
|||||||
https://cloud-native.slack.com in #flux (https://cloud-native.slack.com/messages/CLAJ40HV3)
|
https://cloud-native.slack.com in #flux (https://cloud-native.slack.com/messages/CLAJ40HV3)
|
||||||
(obtain an invitation at https://slack.cncf.io/).
|
(obtain an invitation at https://slack.cncf.io/).
|
||||||
|
|
||||||
These maintainers are shared with other Flux v2-related git
|
The Flux2 maintainers team is identical with the core maintainers of the project
|
||||||
repositories under https://github.com/fluxcd, as noted in their
|
as listed in
|
||||||
respective MAINTAINERS files.
|
|
||||||
|
|
||||||
For convenience, they are reflected in the GitHub team
|
https://github.com/fluxcd/community/blob/main/CORE-MAINTAINERS
|
||||||
@fluxcd/flux2-maintainers -- if the list here changes, that team also
|
|
||||||
should.
|
|
||||||
|
|
||||||
In alphabetical order:
|
|
||||||
|
|
||||||
Aurel Canciu, Sortlist <aurel@sortlist.com> (github: @relu, slack: relu)
|
|
||||||
Hidde Beydals, Weaveworks <hidde@weave.works> (github: @hiddeco, slack: hidde)
|
|
||||||
Philip Laine, Xenit <philip.laine@xenit.se> (github: @phillebaba, slack: phillebaba)
|
|
||||||
Stefan Prodan, Weaveworks <stefan@weave.works> (github: @stefanprodan, slack: stefanprodan)
|
|
||||||
|
|||||||
82
Makefile
82
Makefile
@@ -1,12 +1,24 @@
|
|||||||
VERSION?=$(shell grep 'VERSION' cmd/flux/main.go | awk '{ print $$4 }' | tr -d '"')
|
VERSION?=$(shell grep 'VERSION' cmd/flux/main.go | awk '{ print $$4 }' | head -n 1 | tr -d '"')
|
||||||
EMBEDDED_MANIFESTS_TARGET=cmd/flux/manifests
|
DEV_VERSION?=0.0.0-$(shell git rev-parse --abbrev-ref HEAD)-$(shell git rev-parse --short HEAD)-$(shell date +%s)
|
||||||
|
EMBEDDED_MANIFESTS_TARGET=cmd/flux/.manifests.done
|
||||||
|
TEST_KUBECONFIG?=/tmp/flux-e2e-test-kubeconfig
|
||||||
|
# Architecture to use envtest with
|
||||||
|
ENVTEST_ARCH ?= amd64
|
||||||
|
|
||||||
|
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
|
||||||
|
ifeq (,$(shell go env GOBIN))
|
||||||
|
GOBIN=$(shell go env GOPATH)/bin
|
||||||
|
else
|
||||||
|
GOBIN=$(shell go env GOBIN)
|
||||||
|
endif
|
||||||
|
|
||||||
rwildcard=$(foreach d,$(wildcard $(addsuffix *,$(1))),$(call rwildcard,$(d)/,$(2)) $(filter $(subst *,%,$(2)),$(d)))
|
rwildcard=$(foreach d,$(wildcard $(addsuffix *,$(1))),$(call rwildcard,$(d)/,$(2)) $(filter $(subst *,%,$(2)),$(d)))
|
||||||
|
|
||||||
all: test build
|
all: test build
|
||||||
|
|
||||||
tidy:
|
tidy:
|
||||||
go mod tidy
|
go mod tidy -compat=1.19
|
||||||
|
cd tests/azure && go mod tidy -compat=1.19
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
go fmt ./...
|
go fmt ./...
|
||||||
@@ -14,17 +26,73 @@ fmt:
|
|||||||
vet:
|
vet:
|
||||||
go vet ./...
|
go vet ./...
|
||||||
|
|
||||||
test: $(EMBEDDED_MANIFESTS_TARGET) tidy fmt vet
|
setup-kind:
|
||||||
go test ./... -coverprofile cover.out
|
kind create cluster --name=flux-e2e-test --kubeconfig=$(TEST_KUBECONFIG) --config=.github/kind/config.yaml
|
||||||
|
kubectl --kubeconfig=$(TEST_KUBECONFIG) apply -f https://docs.projectcalico.org/v3.16/manifests/calico.yaml
|
||||||
|
kubectl --kubeconfig=$(TEST_KUBECONFIG) -n kube-system set env daemonset/calico-node FELIX_IGNORELOOSERPF=true
|
||||||
|
|
||||||
|
cleanup-kind:
|
||||||
|
kind delete cluster --name=flux-e2e-test
|
||||||
|
rm $(TEST_KUBECONFIG)
|
||||||
|
|
||||||
|
KUBEBUILDER_ASSETS?="$(shell $(ENVTEST) --arch=$(ENVTEST_ARCH) use -i $(ENVTEST_KUBERNETES_VERSION) --bin-dir=$(ENVTEST_ASSETS_DIR) -p path)"
|
||||||
|
TEST_PKG_PATH="./..."
|
||||||
|
test: $(EMBEDDED_MANIFESTS_TARGET) tidy fmt vet install-envtest
|
||||||
|
KUBEBUILDER_ASSETS="$(KUBEBUILDER_ASSETS)" go test $(TEST_PKG_PATH) -coverprofile cover.out --tags=unit $(TEST_ARGS)
|
||||||
|
|
||||||
|
E2E_TEST_PKG_PATH="./cmd/flux/..."
|
||||||
|
e2e: $(EMBEDDED_MANIFESTS_TARGET) tidy fmt vet
|
||||||
|
TEST_KUBECONFIG=$(TEST_KUBECONFIG) go test $(E2E_TEST_PKG_PATH) -coverprofile e2e.cover.out --tags=e2e -v -failfast $(TEST_ARGS)
|
||||||
|
|
||||||
|
test-with-kind: install-envtest
|
||||||
|
make setup-kind
|
||||||
|
make e2e
|
||||||
|
make cleanup-kind
|
||||||
|
|
||||||
$(EMBEDDED_MANIFESTS_TARGET): $(call rwildcard,manifests/,*.yaml *.json)
|
$(EMBEDDED_MANIFESTS_TARGET): $(call rwildcard,manifests/,*.yaml *.json)
|
||||||
./manifests/scripts/bundle.sh
|
./manifests/scripts/bundle.sh
|
||||||
|
touch $@
|
||||||
|
|
||||||
build: $(EMBEDDED_MANIFESTS_TARGET)
|
build: $(EMBEDDED_MANIFESTS_TARGET)
|
||||||
CGO_ENABLED=0 go build -o ./bin/flux ./cmd/flux
|
CGO_ENABLED=0 go build -ldflags="-s -w -X main.VERSION=$(VERSION)" -o ./bin/flux ./cmd/flux
|
||||||
|
|
||||||
|
build-dev: $(EMBEDDED_MANIFESTS_TARGET)
|
||||||
|
CGO_ENABLED=0 go build -ldflags="-s -w -X main.VERSION=$(DEV_VERSION)" -o ./bin/flux ./cmd/flux
|
||||||
|
|
||||||
|
.PHONY: install
|
||||||
install:
|
install:
|
||||||
go install cmd/flux
|
CGO_ENABLED=0 go install ./cmd/flux
|
||||||
|
|
||||||
install-dev:
|
install-dev:
|
||||||
CGO_ENABLED=0 go build -o /usr/local/bin ./cmd/flux
|
CGO_ENABLED=0 go build -o /usr/local/bin ./cmd/flux
|
||||||
|
|
||||||
|
setup-bootstrap-patch:
|
||||||
|
go run ./tests/bootstrap/main.go
|
||||||
|
|
||||||
|
setup-image-automation:
|
||||||
|
cd tests/image-automation && go run main.go
|
||||||
|
|
||||||
|
ENVTEST_ASSETS_DIR=$(shell pwd)/testbin
|
||||||
|
ENVTEST_KUBERNETES_VERSION?=latest
|
||||||
|
install-envtest: setup-envtest
|
||||||
|
mkdir -p ${ENVTEST_ASSETS_DIR}
|
||||||
|
$(ENVTEST) use $(ENVTEST_KUBERNETES_VERSION) --arch=$(ENVTEST_ARCH) --bin-dir=$(ENVTEST_ASSETS_DIR)
|
||||||
|
|
||||||
|
ENVTEST = $(shell pwd)/bin/setup-envtest
|
||||||
|
.PHONY: envtest
|
||||||
|
setup-envtest: ## Download envtest-setup locally if necessary.
|
||||||
|
$(call go-install-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest@latest)
|
||||||
|
|
||||||
|
# go-install-tool will 'go install' any package $2 and install it to $1.
|
||||||
|
PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST))))
|
||||||
|
define go-install-tool
|
||||||
|
@[ -f $(1) ] || { \
|
||||||
|
set -e ;\
|
||||||
|
TMP_DIR=$$(mktemp -d) ;\
|
||||||
|
cd $$TMP_DIR ;\
|
||||||
|
go mod init tmp ;\
|
||||||
|
echo "Downloading $(2)" ;\
|
||||||
|
GOBIN=$(PROJECT_DIR)/bin go install $(2) ;\
|
||||||
|
rm -rf $$TMP_DIR ;\
|
||||||
|
}
|
||||||
|
endef
|
||||||
|
|||||||
130
README.md
130
README.md
@@ -1,14 +1,13 @@
|
|||||||
# Flux version 2
|
# Flux version 2
|
||||||
|
|
||||||
[](https://bestpractices.coreinfrastructure.org/projects/4782)
|
|
||||||
[](https://github.com/fluxcd/flux2/actions)
|
|
||||||
[](https://goreportcard.com/report/github.com/fluxcd/flux2)
|
|
||||||
[](https://github.com/fluxcd/flux2/blob/main/LICENSE)
|
|
||||||
[](https://github.com/fluxcd/flux2/releases)
|
[](https://github.com/fluxcd/flux2/releases)
|
||||||
|
[](https://bestpractices.coreinfrastructure.org/projects/4782)
|
||||||
|
[](https://app.fossa.com/projects/custom%2B162%2Fgithub.com%2Ffluxcd%2Fflux2?ref=badge_shield)
|
||||||
|
[](https://artifacthub.io/packages/helm/fluxcd-community/flux2)
|
||||||
|
|
||||||
Flux is a tool for keeping Kubernetes clusters in sync with sources of
|
Flux is a tool for keeping Kubernetes clusters in sync with sources of
|
||||||
configuration (like Git repositories), and automating updates to
|
configuration (like Git repositories and OCI artifacts),
|
||||||
configuration when there is new code to deploy.
|
and automating updates to configuration when there is new code to deploy.
|
||||||
|
|
||||||
Flux version 2 ("v2") is built from the ground up to use Kubernetes'
|
Flux version 2 ("v2") is built from the ground up to use Kubernetes'
|
||||||
API extension system, and to integrate with Prometheus and other core
|
API extension system, and to integrate with Prometheus and other core
|
||||||
@@ -20,62 +19,19 @@ Flux v2 is constructed with the [GitOps Toolkit](#gitops-toolkit), a
|
|||||||
set of composable APIs and specialized tools for building Continuous
|
set of composable APIs and specialized tools for building Continuous
|
||||||
Delivery on top of Kubernetes.
|
Delivery on top of Kubernetes.
|
||||||
|
|
||||||
## Flux installation
|
Flux is a Cloud Native Computing Foundation ([CNCF](https://www.cncf.io/)) project, used in
|
||||||
|
production by various [organisations](https://fluxcd.io/adopters) and [cloud providers](https://fluxcd.io/ecosystem).
|
||||||
|
|
||||||
With [Homebrew](https://brew.sh) for macOS and Linux:
|
## Quickstart and documentation
|
||||||
|
|
||||||
```sh
|
To get started check out this [guide](https://fluxcd.io/flux/get-started/)
|
||||||
brew install fluxcd/tap/flux
|
on how to bootstrap Flux on Kubernetes and deploy a sample application in a GitOps manner.
|
||||||
```
|
|
||||||
|
|
||||||
With [GoFish](https://gofi.sh) for Windows, macOS and Linux:
|
For more comprehensive documentation, see the following guides:
|
||||||
|
- [Ways of structuring your repositories](https://fluxcd.io/flux/guides/repository-structure/)
|
||||||
```sh
|
- [Manage Helm Releases](https://fluxcd.io/flux/guides/helmreleases/)
|
||||||
gofish install flux
|
- [Automate image updates to Git](https://fluxcd.io/flux/guides/image-update/)
|
||||||
```
|
- [Manage Kubernetes secrets with Mozilla SOPS](https://fluxcd.io/flux/guides/mozilla-sops/)
|
||||||
|
|
||||||
With Bash for macOS and Linux:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
curl -s https://fluxcd.io/install.sh | sudo bash
|
|
||||||
|
|
||||||
# enable completions in ~/.bash_profile
|
|
||||||
. <(flux completion bash)
|
|
||||||
```
|
|
||||||
|
|
||||||
Arch Linux (AUR) packages:
|
|
||||||
|
|
||||||
- [flux-bin](https://aur.archlinux.org/packages/flux-bin): install the latest
|
|
||||||
stable version using a pre-build binary (recommended)
|
|
||||||
- [flux-go](https://aur.archlinux.org/packages/flux-go): build the latest
|
|
||||||
stable version from source code
|
|
||||||
- [flux-scm](https://aur.archlinux.org/packages/flux-scm): build the latest
|
|
||||||
(unstable) version from source code from our git `main` branch
|
|
||||||
|
|
||||||
Binaries for macOS AMD64/ARM64, Linux AMD64/ARM/ARM64 and Windows are available to
|
|
||||||
download on the [release page](https://github.com/fluxcd/flux2/releases).
|
|
||||||
|
|
||||||
A multi-arch container image with `kubectl` and `flux` is available on Docker Hub and GitHub:
|
|
||||||
|
|
||||||
* `docker.io/fluxcd/flux-cli:<version>`
|
|
||||||
* `ghcr.io/fluxcd/flux-cli:<version>`
|
|
||||||
|
|
||||||
Verify that your cluster satisfies the prerequisites with:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
flux check --pre
|
|
||||||
```
|
|
||||||
|
|
||||||
## Get started
|
|
||||||
|
|
||||||
To get started with Flux, start [browsing the
|
|
||||||
documentation](https://fluxcd.io/docs/) or get started with one of
|
|
||||||
the following guides:
|
|
||||||
|
|
||||||
- [Get started with Flux](https://fluxcd.io/docs/get-started/)
|
|
||||||
- [Manage Helm Releases](https://fluxcd.io/docs/guides/helmreleases/)
|
|
||||||
- [Automate image updates to Git](https://fluxcd.io/docs/guides/image-update/)
|
|
||||||
- [Manage Kubernetes secrets with Mozilla SOPS](https://fluxcd.io/docs/guides/mozilla-sops/)
|
|
||||||
|
|
||||||
If you need help, please refer to our **[Support page](https://fluxcd.io/support/)**.
|
If you need help, please refer to our **[Support page](https://fluxcd.io/support/)**.
|
||||||
|
|
||||||
@@ -90,27 +46,28 @@ automation tooling.
|
|||||||
|
|
||||||
You can use the toolkit to extend Flux, or to build your own systems
|
You can use the toolkit to extend Flux, or to build your own systems
|
||||||
for continuous delivery -- see [the developer
|
for continuous delivery -- see [the developer
|
||||||
guides](https://fluxcd.io/docs/gitops-toolkit/source-watcher/).
|
guides](https://fluxcd.io/flux/gitops-toolkit/source-watcher/).
|
||||||
|
|
||||||
### Components
|
### Components
|
||||||
|
|
||||||
- [Source Controller](https://fluxcd.io/docs/components/source/)
|
- [Source Controller](https://fluxcd.io/flux/components/source/)
|
||||||
- [GitRepository CRD](https://fluxcd.io/docs/components/source/gitrepositories/)
|
- [GitRepository CRD](https://fluxcd.io/flux/components/source/gitrepositories/)
|
||||||
- [HelmRepository CRD](https://fluxcd.io/docs/components/source/helmrepositories/)
|
- [OCIRepository CRD](https://fluxcd.io/flux/components/source/ocirepositories/)
|
||||||
- [HelmChart CRD](https://fluxcd.io/docs/components/source/helmcharts/)
|
- [HelmRepository CRD](https://fluxcd.io/flux/components/source/helmrepositories/)
|
||||||
- [Bucket CRD](https://fluxcd.io/docs/components/source/buckets/)
|
- [HelmChart CRD](https://fluxcd.io/flux/components/source/helmcharts/)
|
||||||
- [Kustomize Controller](https://fluxcd.io/docs/components/kustomize/)
|
- [Bucket CRD](https://fluxcd.io/flux/components/source/buckets/)
|
||||||
- [Kustomization CRD](https://fluxcd.io/docs/components/kustomize/kustomization/)
|
- [Kustomize Controller](https://fluxcd.io/flux/components/kustomize/)
|
||||||
- [Helm Controller](https://fluxcd.io/docs/components/helm/)
|
- [Kustomization CRD](https://fluxcd.io/flux/components/kustomize/kustomization/)
|
||||||
- [HelmRelease CRD](https://fluxcd.io/docs/components/helm/helmreleases/)
|
- [Helm Controller](https://fluxcd.io/flux/components/helm/)
|
||||||
- [Notification Controller](https://fluxcd.io/docs/components/notification/)
|
- [HelmRelease CRD](https://fluxcd.io/flux/components/helm/helmreleases/)
|
||||||
- [Provider CRD](https://fluxcd.io/docs/components/notification/provider/)
|
- [Notification Controller](https://fluxcd.io/flux/components/notification/)
|
||||||
- [Alert CRD](https://fluxcd.io/docs/components/notification/alert/)
|
- [Provider CRD](https://fluxcd.io/flux/components/notification/provider/)
|
||||||
- [Receiver CRD](https://fluxcd.io/docs/components/notification/receiver/)
|
- [Alert CRD](https://fluxcd.io/flux/components/notification/alert/)
|
||||||
- [Image Automation Controllers](https://fluxcd.io/docs/components/image/)
|
- [Receiver CRD](https://fluxcd.io/flux/components/notification/receiver/)
|
||||||
- [ImageRepository CRD](https://fluxcd.io/docs/components/image/imagerepositories/)
|
- [Image Automation Controllers](https://fluxcd.io/flux/components/image/)
|
||||||
- [ImagePolicy CRD](https://fluxcd.io/docs/components/image/imagepolicies/)
|
- [ImageRepository CRD](https://fluxcd.io/flux/components/image/imagerepositories/)
|
||||||
- [ImageUpdateAutomation CRD](https://fluxcd.io/docs/components/image/imageupdateautomations/)
|
- [ImagePolicy CRD](https://fluxcd.io/flux/components/image/imagepolicies/)
|
||||||
|
- [ImageUpdateAutomation CRD](https://fluxcd.io/flux/components/image/imageupdateautomations/)
|
||||||
|
|
||||||
## Community
|
## Community
|
||||||
|
|
||||||
@@ -118,22 +75,25 @@ Need help or want to contribute? Please see the links below. The Flux project is
|
|||||||
new contributors and there are a multitude of ways to get involved.
|
new contributors and there are a multitude of ways to get involved.
|
||||||
|
|
||||||
- Getting Started?
|
- Getting Started?
|
||||||
- Look at our [Get Started guide](https://fluxcd.io/docs/get-started/) and give us feedback
|
- Look at our [Get Started guide](https://fluxcd.io/flux/get-started/) and give us feedback
|
||||||
- Need help?
|
- Need help?
|
||||||
- First: Ask questions on our [GH Discussions page](https://github.com/fluxcd/flux2/discussions)
|
- First: Ask questions on our [GH Discussions page](https://github.com/fluxcd/flux2/discussions).
|
||||||
- Second: Talk to us in the #flux channel on [CNCF Slack](https://slack.cncf.io/)
|
- Second: Talk to us in the #flux channel on [CNCF Slack](https://slack.cncf.io/).
|
||||||
- Please follow our [Support Guidelines](https://fluxcd.io/support/)
|
- Please follow our [Support Guidelines](https://fluxcd.io/support/)
|
||||||
(in short: be nice, be respectful of volunteers' time, understand that maintainers and
|
(in short: be nice, be respectful of volunteers' time, understand that maintainers and
|
||||||
contributors cannot respond to all DMs, and keep discussions in the public #flux channel as much as possible).
|
contributors cannot respond to all DMs, and keep discussions in the public #flux channel as much as possible).
|
||||||
- Have feature proposals or want to contribute?
|
- Have feature proposals or want to contribute?
|
||||||
- Propose features on our [GH Discussions page](https://github.com/fluxcd/flux2/discussions)
|
- Propose features on our [GitHub Discussions page](https://github.com/fluxcd/flux2/discussions).
|
||||||
- Join our upcoming dev meetings ([meeting access and agenda](https://docs.google.com/document/d/1l_M0om0qUEN_NNiGgpqJ2tvsF2iioHkaARDeh6b70B0/view))
|
- Join our upcoming dev meetings ([meeting access and agenda](https://docs.google.com/document/d/1l_M0om0qUEN_NNiGgpqJ2tvsF2iioHkaARDeh6b70B0/view)).
|
||||||
- [Join the flux-dev mailing list](https://lists.cncf.io/g/cncf-flux-dev).
|
- [Join the flux-dev mailing list](https://lists.cncf.io/g/cncf-flux-dev).
|
||||||
- Check out [how to contribute](CONTRIBUTING.md) to the project
|
- Check out [how to contribute](CONTRIBUTING.md) to the project.
|
||||||
|
- Check out the [project roadmap](https://fluxcd.io/roadmap/).
|
||||||
|
|
||||||
### Events
|
### Events
|
||||||
|
|
||||||
Check out our **[events calendar](https://fluxcd.io/community/#talks)**,
|
Check out our **[events calendar](https://fluxcd.io/#calendar)**,
|
||||||
both with upcoming talks you can attend or past events videos you can watch.
|
both with upcoming talks, events and meetings you can attend.
|
||||||
|
Or view the **[resources section](https://fluxcd.io/resources)**
|
||||||
|
with past events videos you can watch.
|
||||||
|
|
||||||
We look forward to seeing you with us!
|
We look forward to seeing you with us!
|
||||||
|
|||||||
100
action/README.md
100
action/README.md
@@ -10,11 +10,21 @@ Usage:
|
|||||||
run: flux -v
|
run: flux -v
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that this action can only be used on GitHub **Linux AMD64** runners.
|
|
||||||
The latest stable version of the `flux` binary is downloaded from
|
The latest stable version of the `flux` binary is downloaded from
|
||||||
GitHub [releases](https://github.com/fluxcd/flux2/releases)
|
GitHub [releases](https://github.com/fluxcd/flux2/releases)
|
||||||
and placed at `/usr/local/bin/flux`.
|
and placed at `/usr/local/bin/flux`.
|
||||||
|
|
||||||
|
Note that this action can only be used on GitHub **Linux** runners.
|
||||||
|
You can change the arch (defaults to `amd64`) with:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
steps:
|
||||||
|
- name: Setup Flux CLI
|
||||||
|
uses: fluxcd/flux2/action@main
|
||||||
|
with:
|
||||||
|
arch: arm64 # can be amd64, arm64 or arm
|
||||||
|
```
|
||||||
|
|
||||||
You can download a specific version with:
|
You can download a specific version with:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@@ -22,7 +32,7 @@ You can download a specific version with:
|
|||||||
- name: Setup Flux CLI
|
- name: Setup Flux CLI
|
||||||
uses: fluxcd/flux2/action@main
|
uses: fluxcd/flux2/action@main
|
||||||
with:
|
with:
|
||||||
version: 0.8.0
|
version: 0.32.0
|
||||||
```
|
```
|
||||||
|
|
||||||
### Automate Flux updates
|
### Automate Flux updates
|
||||||
@@ -64,6 +74,92 @@ jobs:
|
|||||||
${{ steps.update.outputs.flux_version }}
|
${{ steps.update.outputs.flux_version }}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Push Kubernetes manifests to container registries
|
||||||
|
|
||||||
|
Example workflow for publishing Kubernetes manifests bundled as OCI artifacts to GitHub Container Registry:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: push-artifact-staging
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'main'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
packages: write # needed for ghcr.io access
|
||||||
|
|
||||||
|
env:
|
||||||
|
OCI_REPO: "oci://ghcr.io/my-org/manifests/${{ github.event.repository.name }}"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
kubernetes:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Setup Flux CLI
|
||||||
|
uses: fluxcd/flux2/action@main
|
||||||
|
- name: Login to GHCR
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Generate manifests
|
||||||
|
run: |
|
||||||
|
kustomize build ./manifests/staging > ./deploy/app.yaml
|
||||||
|
- name: Push manifests
|
||||||
|
run: |
|
||||||
|
flux push artifact $OCI_REPO:$(git rev-parse --short HEAD) \
|
||||||
|
--path="./deploy" \
|
||||||
|
--source="$(git config --get remote.origin.url)" \
|
||||||
|
--revision="$(git branch --show-current)/$(git rev-parse HEAD)"
|
||||||
|
- name: Deploy manifests to staging
|
||||||
|
run: |
|
||||||
|
flux tag artifact $OCI_REPO:$(git rev-parse --short HEAD) --tag staging
|
||||||
|
```
|
||||||
|
|
||||||
|
Example workflow for publishing Kubernetes manifests bundled as OCI artifacts to Docker Hub:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: push-artifact-production
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- '*'
|
||||||
|
|
||||||
|
env:
|
||||||
|
OCI_REPO: "oci://docker.io/my-org/app-config"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
kubernetes:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Setup Flux CLI
|
||||||
|
uses: fluxcd/flux2/action@main
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
- name: Generate manifests
|
||||||
|
run: |
|
||||||
|
kustomize build ./manifests/production > ./deploy/app.yaml
|
||||||
|
- name: Push manifests
|
||||||
|
run: |
|
||||||
|
flux push artifact $OCI_REPO:$(git tag --points-at HEAD) \
|
||||||
|
--path="./deploy" \
|
||||||
|
--source="$(git config --get remote.origin.url)" \
|
||||||
|
--revision="$(git tag --points-at HEAD)/$(git rev-parse HEAD)"
|
||||||
|
- name: Deploy manifests to production
|
||||||
|
run: |
|
||||||
|
flux tag artifact $OCI_REPO:$(git tag --points-at HEAD) --tag production
|
||||||
|
```
|
||||||
|
|
||||||
### End-to-end testing
|
### End-to-end testing
|
||||||
|
|
||||||
Example workflow for running Flux in Kubernetes Kind:
|
Example workflow for running Flux in Kubernetes Kind:
|
||||||
|
|||||||
@@ -8,26 +8,40 @@ inputs:
|
|||||||
version:
|
version:
|
||||||
description: "Flux version e.g. 0.8.0 (defaults to latest stable release)"
|
description: "Flux version e.g. 0.8.0 (defaults to latest stable release)"
|
||||||
required: false
|
required: false
|
||||||
|
arch:
|
||||||
|
description: "arch can be amd64, arm64 or arm"
|
||||||
|
required: true
|
||||||
|
default: "amd64"
|
||||||
|
bindir:
|
||||||
|
description: "Optional location of the Flux binary. Will not use sudo if set. Updates System Path."
|
||||||
|
required: false
|
||||||
runs:
|
runs:
|
||||||
using: composite
|
using: composite
|
||||||
steps:
|
steps:
|
||||||
- name: "Download flux binary to tmp"
|
- name: "Download flux binary to tmp"
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
|
ARCH=${{ inputs.arch }}
|
||||||
VERSION=${{ inputs.version }}
|
VERSION=${{ inputs.version }}
|
||||||
|
|
||||||
if [ -z $VERSION ]; then
|
if [ -z $VERSION ]; then
|
||||||
VERSION=$(curl https://api.github.com/repos/fluxcd/flux2/releases/latest -sL | grep tag_name | sed -E 's/.*"([^"]+)".*/\1/' | cut -c 2-)
|
VERSION=$(curl https://api.github.com/repos/fluxcd/flux2/releases/latest -sL | grep tag_name | sed -E 's/.*"([^"]+)".*/\1/' | cut -c 2-)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
BIN_URL="https://github.com/fluxcd/flux2/releases/download/v${VERSION}/flux_${VERSION}_linux_amd64.tar.gz"
|
BIN_URL="https://github.com/fluxcd/flux2/releases/download/v${VERSION}/flux_${VERSION}_linux_${ARCH}.tar.gz"
|
||||||
curl -sL ${BIN_URL} -o /tmp/flux.tar.gz
|
curl -sL ${BIN_URL} -o /tmp/flux.tar.gz
|
||||||
mkdir -p /tmp/flux
|
mkdir -p /tmp/flux
|
||||||
tar -C /tmp/flux/ -zxvf /tmp/flux.tar.gz
|
tar -C /tmp/flux/ -zxvf /tmp/flux.tar.gz
|
||||||
- name: "Add flux binary to /usr/local/bin"
|
- name: "Copy Flux binary to execute location"
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
|
BINDIR=${{ inputs.bindir }}
|
||||||
|
if [ -z $BINDIR ]; then
|
||||||
sudo cp /tmp/flux/flux /usr/local/bin
|
sudo cp /tmp/flux/flux /usr/local/bin
|
||||||
|
else
|
||||||
|
cp /tmp/flux/flux "${BINDIR}"
|
||||||
|
echo "${BINDIR}" >> $GITHUB_PATH
|
||||||
|
fi
|
||||||
- name: "Cleanup tmp"
|
- name: "Cleanup tmp"
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
|
||||||
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
|
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// notificationv1.Alert
|
// notificationv1.Alert
|
||||||
@@ -27,6 +27,7 @@ import (
|
|||||||
var alertType = apiType{
|
var alertType = apiType{
|
||||||
kind: notificationv1.AlertKind,
|
kind: notificationv1.AlertKind,
|
||||||
humanKind: "alert",
|
humanKind: "alert",
|
||||||
|
groupVersion: notificationv1.GroupVersion,
|
||||||
}
|
}
|
||||||
|
|
||||||
type alertAdapter struct {
|
type alertAdapter struct {
|
||||||
@@ -37,6 +38,10 @@ func (a alertAdapter) asClientObject() client.Object {
|
|||||||
return a.Alert
|
return a.Alert
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a alertAdapter) deepCopyClientObject() client.Object {
|
||||||
|
return a.Alert.DeepCopy()
|
||||||
|
}
|
||||||
|
|
||||||
// notificationv1.Alert
|
// notificationv1.Alert
|
||||||
|
|
||||||
type alertListAdapter struct {
|
type alertListAdapter struct {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
|
||||||
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
|
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// notificationv1.Provider
|
// notificationv1.Provider
|
||||||
@@ -27,6 +27,7 @@ import (
|
|||||||
var alertProviderType = apiType{
|
var alertProviderType = apiType{
|
||||||
kind: notificationv1.ProviderKind,
|
kind: notificationv1.ProviderKind,
|
||||||
humanKind: "alert provider",
|
humanKind: "alert provider",
|
||||||
|
groupVersion: notificationv1.GroupVersion,
|
||||||
}
|
}
|
||||||
|
|
||||||
type alertProviderAdapter struct {
|
type alertProviderAdapter struct {
|
||||||
@@ -37,6 +38,10 @@ func (a alertProviderAdapter) asClientObject() client.Object {
|
|||||||
return a.Provider
|
return a.Provider
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a alertProviderAdapter) deepCopyClientObject() client.Object {
|
||||||
|
return a.Provider.DeepCopy()
|
||||||
|
}
|
||||||
|
|
||||||
// notificationv1.Provider
|
// notificationv1.Provider
|
||||||
|
|
||||||
type alertProviderListAdapter struct {
|
type alertProviderListAdapter struct {
|
||||||
|
|||||||
@@ -19,12 +19,13 @@ package main
|
|||||||
import (
|
import (
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/internal/flags"
|
"github.com/fluxcd/flux2/internal/flags"
|
||||||
"github.com/fluxcd/flux2/internal/utils"
|
"github.com/fluxcd/flux2/internal/utils"
|
||||||
|
"github.com/fluxcd/flux2/pkg/manifestgen"
|
||||||
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
|
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -67,6 +68,10 @@ type bootstrapFlags struct {
|
|||||||
authorName string
|
authorName string
|
||||||
authorEmail string
|
authorEmail string
|
||||||
|
|
||||||
|
gpgKeyRingPath string
|
||||||
|
gpgPassphrase string
|
||||||
|
gpgKeyID string
|
||||||
|
|
||||||
commitMessageAppendix string
|
commitMessageAppendix string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +88,7 @@ func init() {
|
|||||||
bootstrapCmd.PersistentFlags().StringSliceVar(&bootstrapArgs.defaultComponents, "components", rootArgs.defaults.Components,
|
bootstrapCmd.PersistentFlags().StringSliceVar(&bootstrapArgs.defaultComponents, "components", rootArgs.defaults.Components,
|
||||||
"list of components, accepts comma-separated values")
|
"list of components, accepts comma-separated values")
|
||||||
bootstrapCmd.PersistentFlags().StringSliceVar(&bootstrapArgs.extraComponents, "components-extra", nil,
|
bootstrapCmd.PersistentFlags().StringSliceVar(&bootstrapArgs.extraComponents, "components-extra", nil,
|
||||||
"list of components in addition to those supplied or defaulted, accepts comma-separated values")
|
"list of components in addition to those supplied or defaulted, accepts values such as 'image-reflector-controller,image-automation-controller'")
|
||||||
|
|
||||||
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.registry, "registry", "ghcr.io/fluxcd",
|
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.registry, "registry", "ghcr.io/fluxcd",
|
||||||
"container registry where the toolkit images are published")
|
"container registry where the toolkit images are published")
|
||||||
@@ -118,6 +123,10 @@ func init() {
|
|||||||
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.authorName, "author-name", "Flux", "author name for Git commits")
|
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.authorName, "author-name", "Flux", "author name for Git commits")
|
||||||
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.authorEmail, "author-email", "", "author email for Git commits")
|
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.authorEmail, "author-email", "", "author email for Git commits")
|
||||||
|
|
||||||
|
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.gpgKeyRingPath, "gpg-key-ring", "", "path to GPG key ring for signing commits")
|
||||||
|
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.gpgPassphrase, "gpg-passphrase", "", "passphrase for decrypting GPG private key")
|
||||||
|
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.gpgKeyID, "gpg-key-id", "", "key id for selecting a particular key")
|
||||||
|
|
||||||
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.commitMessageAppendix, "commit-message-appendix", "", "string to add to the commit messages, e.g. '[ci skip]'")
|
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.commitMessageAppendix, "commit-message-appendix", "", "string to add to the commit messages, e.g. '[ci skip]'")
|
||||||
|
|
||||||
bootstrapCmd.PersistentFlags().Var(&bootstrapArgs.arch, "arch", bootstrapArgs.arch.Description())
|
bootstrapCmd.PersistentFlags().Var(&bootstrapArgs.arch, "arch", bootstrapArgs.arch.Description())
|
||||||
@@ -131,7 +140,7 @@ func NewBootstrapFlags() bootstrapFlags {
|
|||||||
return bootstrapFlags{
|
return bootstrapFlags{
|
||||||
logLevel: flags.LogLevel(rootArgs.defaults.LogLevel),
|
logLevel: flags.LogLevel(rootArgs.defaults.LogLevel),
|
||||||
requiredComponents: []string{"source-controller", "kustomize-controller"},
|
requiredComponents: []string{"source-controller", "kustomize-controller"},
|
||||||
keyAlgorithm: flags.PublicKeyAlgorithm(sourcesecret.RSAPrivateKeyAlgorithm),
|
keyAlgorithm: flags.PublicKeyAlgorithm(sourcesecret.ECDSAPrivateKeyAlgorithm),
|
||||||
keyRSABits: 2048,
|
keyRSABits: 2048,
|
||||||
keyECDSACurve: flags.ECDSACurve{Curve: elliptic.P384()},
|
keyECDSACurve: flags.ECDSACurve{Curve: elliptic.P384()},
|
||||||
}
|
}
|
||||||
@@ -145,7 +154,7 @@ func buildEmbeddedManifestBase() (string, error) {
|
|||||||
if !isEmbeddedVersion(bootstrapArgs.version) {
|
if !isEmbeddedVersion(bootstrapArgs.version) {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
tmpBaseDir, err := ioutil.TempDir("", "flux-manifests-")
|
tmpBaseDir, err := manifestgen.MkdirTempAbs("", "flux-manifests-")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -174,6 +183,10 @@ func mapTeamSlice(s []string, defaultPermission string) map[string]string {
|
|||||||
m := make(map[string]string, len(s))
|
m := make(map[string]string, len(s))
|
||||||
for _, v := range s {
|
for _, v := range s {
|
||||||
m[v] = defaultPermission
|
m[v] = defaultPermission
|
||||||
|
if s := strings.Split(v, ":"); len(s) == 2 {
|
||||||
|
m[s[0]] = s[1]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|||||||
294
cmd/flux/bootstrap_bitbucket_server.go
Normal file
294
cmd/flux/bootstrap_bitbucket_server.go
Normal file
@@ -0,0 +1,294 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2021 The Flux authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fluxcd/pkg/git"
|
||||||
|
"github.com/fluxcd/pkg/git/gogit"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/fluxcd/flux2/internal/flags"
|
||||||
|
"github.com/fluxcd/flux2/internal/utils"
|
||||||
|
"github.com/fluxcd/flux2/pkg/bootstrap"
|
||||||
|
"github.com/fluxcd/flux2/pkg/bootstrap/provider"
|
||||||
|
"github.com/fluxcd/flux2/pkg/manifestgen"
|
||||||
|
"github.com/fluxcd/flux2/pkg/manifestgen/install"
|
||||||
|
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
|
||||||
|
"github.com/fluxcd/flux2/pkg/manifestgen/sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var bootstrapBServerCmd = &cobra.Command{
|
||||||
|
Use: "bitbucket-server",
|
||||||
|
Short: "Bootstrap toolkit components in a Bitbucket Server repository",
|
||||||
|
Long: `The bootstrap bitbucket-server command creates the Bitbucket Server repository if it doesn't exists and
|
||||||
|
commits the toolkit components manifests to the master branch.
|
||||||
|
Then it configures the target cluster to synchronize with the repository.
|
||||||
|
If the toolkit components are present on the cluster,
|
||||||
|
the bootstrap command will perform an upgrade if needed.`,
|
||||||
|
Example: ` # Create a Bitbucket Server API token and export it as an env var
|
||||||
|
export BITBUCKET_TOKEN=<my-token>
|
||||||
|
|
||||||
|
# Run bootstrap for a private repository using HTTPS token authentication
|
||||||
|
flux bootstrap bitbucket-server --owner=<project> --username=<user> --repository=<repository name> --hostname=<domain> --token-auth
|
||||||
|
|
||||||
|
# Run bootstrap for a private repository using SSH authentication
|
||||||
|
flux bootstrap bitbucket-server --owner=<project> --username=<user> --repository=<repository name> --hostname=<domain>
|
||||||
|
|
||||||
|
# Run bootstrap for a repository path
|
||||||
|
flux bootstrap bitbucket-server --owner=<project> --username=<user> --repository=<repository name> --path=dev-cluster --hostname=<domain>
|
||||||
|
|
||||||
|
# Run bootstrap for a public repository on a personal account
|
||||||
|
flux bootstrap bitbucket-server --owner=<user> --repository=<repository name> --private=false --personal --hostname=<domain> --token-auth
|
||||||
|
|
||||||
|
# Run bootstrap for a an existing repository with a branch named main
|
||||||
|
flux bootstrap bitbucket-server --owner=<project> --username=<user> --repository=<repository name> --branch=main --hostname=<domain> --token-auth`,
|
||||||
|
RunE: bootstrapBServerCmdRun,
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
bServerDefaultPermission = "push"
|
||||||
|
bServerTokenEnvVar = "BITBUCKET_TOKEN"
|
||||||
|
)
|
||||||
|
|
||||||
|
type bServerFlags struct {
|
||||||
|
owner string
|
||||||
|
repository string
|
||||||
|
interval time.Duration
|
||||||
|
personal bool
|
||||||
|
username string
|
||||||
|
private bool
|
||||||
|
hostname string
|
||||||
|
path flags.SafeRelativePath
|
||||||
|
teams []string
|
||||||
|
readWriteKey bool
|
||||||
|
reconcile bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var bServerArgs bServerFlags
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
bootstrapBServerCmd.Flags().StringVar(&bServerArgs.owner, "owner", "", "Bitbucket Server user or project name")
|
||||||
|
bootstrapBServerCmd.Flags().StringVar(&bServerArgs.repository, "repository", "", "Bitbucket Server repository name")
|
||||||
|
bootstrapBServerCmd.Flags().StringSliceVar(&bServerArgs.teams, "group", []string{}, "Bitbucket Server groups to be given write access (also accepts comma-separated values)")
|
||||||
|
bootstrapBServerCmd.Flags().BoolVar(&bServerArgs.personal, "personal", false, "if true, the owner is assumed to be a Bitbucket Server user; otherwise a group")
|
||||||
|
bootstrapBServerCmd.Flags().StringVarP(&bServerArgs.username, "username", "u", "git", "authentication username")
|
||||||
|
bootstrapBServerCmd.Flags().BoolVar(&bServerArgs.private, "private", true, "if true, the repository is setup or configured as private")
|
||||||
|
bootstrapBServerCmd.Flags().DurationVar(&bServerArgs.interval, "interval", time.Minute, "sync interval")
|
||||||
|
bootstrapBServerCmd.Flags().StringVar(&bServerArgs.hostname, "hostname", "", "Bitbucket Server hostname")
|
||||||
|
bootstrapBServerCmd.Flags().Var(&bServerArgs.path, "path", "path relative to the repository root, when specified the cluster sync will be scoped to this path")
|
||||||
|
bootstrapBServerCmd.Flags().BoolVar(&bServerArgs.readWriteKey, "read-write-key", false, "if true, the deploy key is configured with read/write permissions")
|
||||||
|
bootstrapBServerCmd.Flags().BoolVar(&bServerArgs.reconcile, "reconcile", false, "if true, the configured options are also reconciled if the repository already exists")
|
||||||
|
|
||||||
|
bootstrapCmd.AddCommand(bootstrapBServerCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func bootstrapBServerCmdRun(cmd *cobra.Command, args []string) error {
|
||||||
|
bitbucketToken := os.Getenv(bServerTokenEnvVar)
|
||||||
|
if bitbucketToken == "" {
|
||||||
|
var err error
|
||||||
|
bitbucketToken, err = readPasswordFromStdin("Please enter your Bitbucket personal access token (PAT): ")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not read token: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if bServerArgs.hostname == "" {
|
||||||
|
return fmt.Errorf("invalid hostname %q", bServerArgs.hostname)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := bootstrapValidate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manifest base
|
||||||
|
if ver, err := getVersion(bootstrapArgs.version); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
bootstrapArgs.version = ver
|
||||||
|
}
|
||||||
|
manifestsBase, err := buildEmbeddedManifestBase()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(manifestsBase)
|
||||||
|
|
||||||
|
user := bServerArgs.username
|
||||||
|
if bServerArgs.personal {
|
||||||
|
user = bServerArgs.owner
|
||||||
|
}
|
||||||
|
|
||||||
|
var caBundle []byte
|
||||||
|
if bootstrapArgs.caFile != "" {
|
||||||
|
var err error
|
||||||
|
caBundle, err = os.ReadFile(bootstrapArgs.caFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to read TLS CA file: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build Bitbucket Server provider
|
||||||
|
providerCfg := provider.Config{
|
||||||
|
Provider: provider.GitProviderStash,
|
||||||
|
Hostname: bServerArgs.hostname,
|
||||||
|
Username: user,
|
||||||
|
Token: bitbucketToken,
|
||||||
|
CaBundle: caBundle,
|
||||||
|
}
|
||||||
|
|
||||||
|
providerClient, err := provider.BuildGitProvider(providerCfg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lazy go-git repository
|
||||||
|
tmpDir, err := manifestgen.MkdirTempAbs("", "flux-bootstrap-")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create temporary working dir: %w", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
|
clientOpts := []gogit.ClientOption{gogit.WithDiskStorage(), gogit.WithFallbackToDefaultKnownHosts()}
|
||||||
|
gitClient, err := gogit.NewClient(tmpDir, &git.AuthOptions{
|
||||||
|
Transport: git.HTTPS,
|
||||||
|
Username: user,
|
||||||
|
Password: bitbucketToken,
|
||||||
|
CAFile: caBundle,
|
||||||
|
}, clientOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create a Git client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Install manifest config
|
||||||
|
installOptions := install.Options{
|
||||||
|
BaseURL: rootArgs.defaults.BaseURL,
|
||||||
|
Version: bootstrapArgs.version,
|
||||||
|
Namespace: *kubeconfigArgs.Namespace,
|
||||||
|
Components: bootstrapComponents(),
|
||||||
|
Registry: bootstrapArgs.registry,
|
||||||
|
ImagePullSecret: bootstrapArgs.imagePullSecret,
|
||||||
|
WatchAllNamespaces: bootstrapArgs.watchAllNamespaces,
|
||||||
|
NetworkPolicy: bootstrapArgs.networkPolicy,
|
||||||
|
LogLevel: bootstrapArgs.logLevel.String(),
|
||||||
|
NotificationController: rootArgs.defaults.NotificationController,
|
||||||
|
ManifestFile: rootArgs.defaults.ManifestFile,
|
||||||
|
Timeout: rootArgs.timeout,
|
||||||
|
TargetPath: bServerArgs.path.ToSlash(),
|
||||||
|
ClusterDomain: bootstrapArgs.clusterDomain,
|
||||||
|
TolerationKeys: bootstrapArgs.tolerationKeys,
|
||||||
|
}
|
||||||
|
if customBaseURL := bootstrapArgs.manifestsPath; customBaseURL != "" {
|
||||||
|
installOptions.BaseURL = customBaseURL
|
||||||
|
}
|
||||||
|
|
||||||
|
// Source generation and secret config
|
||||||
|
secretOpts := sourcesecret.Options{
|
||||||
|
Name: bootstrapArgs.secretName,
|
||||||
|
Namespace: *kubeconfigArgs.Namespace,
|
||||||
|
TargetPath: bServerArgs.path.String(),
|
||||||
|
ManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile,
|
||||||
|
}
|
||||||
|
if bootstrapArgs.tokenAuth {
|
||||||
|
if bServerArgs.personal {
|
||||||
|
secretOpts.Username = bServerArgs.owner
|
||||||
|
} else {
|
||||||
|
secretOpts.Username = bServerArgs.username
|
||||||
|
}
|
||||||
|
secretOpts.Password = bitbucketToken
|
||||||
|
secretOpts.CAFile = caBundle
|
||||||
|
} else {
|
||||||
|
keypair, err := sourcesecret.LoadKeyPairFromPath(bootstrapArgs.privateKeyFile, gitArgs.password)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
secretOpts.Keypair = keypair
|
||||||
|
secretOpts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(bootstrapArgs.keyAlgorithm)
|
||||||
|
secretOpts.RSAKeyBits = int(bootstrapArgs.keyRSABits)
|
||||||
|
secretOpts.ECDSACurve = bootstrapArgs.keyECDSACurve.Curve
|
||||||
|
|
||||||
|
secretOpts.SSHHostname = bServerArgs.hostname
|
||||||
|
if bootstrapArgs.sshHostname != "" {
|
||||||
|
secretOpts.SSHHostname = bootstrapArgs.sshHostname
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync manifest config
|
||||||
|
syncOpts := sync.Options{
|
||||||
|
Interval: bServerArgs.interval,
|
||||||
|
Name: *kubeconfigArgs.Namespace,
|
||||||
|
Namespace: *kubeconfigArgs.Namespace,
|
||||||
|
Branch: bootstrapArgs.branch,
|
||||||
|
Secret: bootstrapArgs.secretName,
|
||||||
|
TargetPath: bServerArgs.path.ToSlash(),
|
||||||
|
ManifestFile: sync.MakeDefaultOptions().ManifestFile,
|
||||||
|
GitImplementation: sourceGitArgs.gitImplementation.String(),
|
||||||
|
RecurseSubmodules: bootstrapArgs.recurseSubmodules,
|
||||||
|
}
|
||||||
|
|
||||||
|
entityList, err := bootstrap.LoadEntityListFromPath(bootstrapArgs.gpgKeyRingPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bootstrap config
|
||||||
|
|
||||||
|
bootstrapOpts := []bootstrap.GitProviderOption{
|
||||||
|
bootstrap.WithProviderRepository(bServerArgs.owner, bServerArgs.repository, bServerArgs.personal),
|
||||||
|
bootstrap.WithBranch(bootstrapArgs.branch),
|
||||||
|
bootstrap.WithBootstrapTransportType("https"),
|
||||||
|
bootstrap.WithSignature(bootstrapArgs.authorName, bootstrapArgs.authorEmail),
|
||||||
|
bootstrap.WithCommitMessageAppendix(bootstrapArgs.commitMessageAppendix),
|
||||||
|
bootstrap.WithProviderTeamPermissions(mapTeamSlice(bServerArgs.teams, bServerDefaultPermission)),
|
||||||
|
bootstrap.WithReadWriteKeyPermissions(bServerArgs.readWriteKey),
|
||||||
|
bootstrap.WithKubeconfig(kubeconfigArgs, kubeclientOptions),
|
||||||
|
bootstrap.WithLogger(logger),
|
||||||
|
bootstrap.WithGitCommitSigning(entityList, bootstrapArgs.gpgPassphrase, bootstrapArgs.gpgKeyID),
|
||||||
|
}
|
||||||
|
if bootstrapArgs.sshHostname != "" {
|
||||||
|
bootstrapOpts = append(bootstrapOpts, bootstrap.WithSSHHostname(bootstrapArgs.sshHostname))
|
||||||
|
}
|
||||||
|
if bootstrapArgs.tokenAuth {
|
||||||
|
bootstrapOpts = append(bootstrapOpts, bootstrap.WithSyncTransportType("https"))
|
||||||
|
}
|
||||||
|
if !bServerArgs.private {
|
||||||
|
bootstrapOpts = append(bootstrapOpts, bootstrap.WithProviderRepositoryConfig("", "", "public"))
|
||||||
|
}
|
||||||
|
if bServerArgs.reconcile {
|
||||||
|
bootstrapOpts = append(bootstrapOpts, bootstrap.WithReconcile())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup bootstrapper with constructed configs
|
||||||
|
b, err := bootstrap.NewGitProviderBootstrapper(gitClient, providerClient, kubeClient, bootstrapOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run
|
||||||
|
return bootstrap.Run(ctx, b, manifestsBase, installOptions, secretOpts, syncOpts, rootArgs.pollInterval, rootArgs.timeout)
|
||||||
|
}
|
||||||
@@ -19,26 +19,24 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-git/go-git/v5/plumbing/transport"
|
|
||||||
"github.com/go-git/go-git/v5/plumbing/transport/http"
|
|
||||||
"github.com/go-git/go-git/v5/plumbing/transport/ssh"
|
|
||||||
"github.com/manifoldco/promptui"
|
"github.com/manifoldco/promptui"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/internal/bootstrap"
|
|
||||||
"github.com/fluxcd/flux2/internal/bootstrap/git/gogit"
|
|
||||||
"github.com/fluxcd/flux2/internal/flags"
|
"github.com/fluxcd/flux2/internal/flags"
|
||||||
"github.com/fluxcd/flux2/internal/utils"
|
"github.com/fluxcd/flux2/internal/utils"
|
||||||
|
"github.com/fluxcd/flux2/pkg/bootstrap"
|
||||||
|
"github.com/fluxcd/flux2/pkg/manifestgen"
|
||||||
"github.com/fluxcd/flux2/pkg/manifestgen/install"
|
"github.com/fluxcd/flux2/pkg/manifestgen/install"
|
||||||
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
|
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
|
||||||
"github.com/fluxcd/flux2/pkg/manifestgen/sync"
|
"github.com/fluxcd/flux2/pkg/manifestgen/sync"
|
||||||
|
"github.com/fluxcd/pkg/git"
|
||||||
|
"github.com/fluxcd/pkg/git/gogit"
|
||||||
)
|
)
|
||||||
|
|
||||||
var bootstrapGitCmd = &cobra.Command{
|
var bootstrapGitCmd = &cobra.Command{
|
||||||
@@ -54,11 +52,20 @@ command will perform an upgrade if needed.`,
|
|||||||
# Run bootstrap for a Git repository and authenticate using a password
|
# Run bootstrap for a Git repository and authenticate using a password
|
||||||
flux bootstrap git --url=https://example.com/repository.git --password=<password>
|
flux bootstrap git --url=https://example.com/repository.git --password=<password>
|
||||||
|
|
||||||
|
# Run bootstrap for a Git repository and authenticate using a password from environment variable
|
||||||
|
GIT_PASSWORD=<password> && flux bootstrap git --url=https://example.com/repository.git
|
||||||
|
|
||||||
# Run bootstrap for a Git repository with a passwordless private key
|
# Run bootstrap for a Git repository with a passwordless private key
|
||||||
flux bootstrap git --url=ssh://git@example.com/repository.git --private-key-file=<path/to/private.key>
|
flux bootstrap git --url=ssh://git@example.com/repository.git --private-key-file=<path/to/private.key>
|
||||||
|
|
||||||
# Run bootstrap for a Git repository with a private key and password
|
# Run bootstrap for a Git repository with a private key and password
|
||||||
flux bootstrap git --url=ssh://git@example.com/repository.git --private-key-file=<path/to/private.key> --password=<password>
|
flux bootstrap git --url=ssh://git@example.com/repository.git --private-key-file=<path/to/private.key> --password=<password>
|
||||||
|
|
||||||
|
# Run bootstrap for a Git repository on AWS CodeCommit
|
||||||
|
flux bootstrap git --url=ssh://<SSH-Key-ID>@git-codecommit.<region>.amazonaws.com/v1/repos/<repository> --private-key-file=<path/to/private.key> --password=<SSH-passphrase>
|
||||||
|
|
||||||
|
# Run bootstrap for a Git repository on Azure Devops
|
||||||
|
flux bootstrap git --url=ssh://git@ssh.dev.azure.com/v3/<org>/<project>/<repository> --ssh-key-algorithm=rsa --ssh-rsa-bits=4096
|
||||||
`,
|
`,
|
||||||
RunE: bootstrapGitCmdRun,
|
RunE: bootstrapGitCmdRun,
|
||||||
}
|
}
|
||||||
@@ -70,8 +77,13 @@ type gitFlags struct {
|
|||||||
username string
|
username string
|
||||||
password string
|
password string
|
||||||
silent bool
|
silent bool
|
||||||
|
insecureHttpAllowed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
gitPasswordEnvVar = "GIT_PASSWORD"
|
||||||
|
)
|
||||||
|
|
||||||
var gitArgs gitFlags
|
var gitArgs gitFlags
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -81,11 +93,25 @@ func init() {
|
|||||||
bootstrapGitCmd.Flags().StringVarP(&gitArgs.username, "username", "u", "git", "basic authentication username")
|
bootstrapGitCmd.Flags().StringVarP(&gitArgs.username, "username", "u", "git", "basic authentication username")
|
||||||
bootstrapGitCmd.Flags().StringVarP(&gitArgs.password, "password", "p", "", "basic authentication password")
|
bootstrapGitCmd.Flags().StringVarP(&gitArgs.password, "password", "p", "", "basic authentication password")
|
||||||
bootstrapGitCmd.Flags().BoolVarP(&gitArgs.silent, "silent", "s", false, "assumes the deploy key is already setup, skips confirmation")
|
bootstrapGitCmd.Flags().BoolVarP(&gitArgs.silent, "silent", "s", false, "assumes the deploy key is already setup, skips confirmation")
|
||||||
|
bootstrapGitCmd.Flags().BoolVar(&gitArgs.insecureHttpAllowed, "allow-insecure-http", false, "allows http git url connections")
|
||||||
|
|
||||||
bootstrapCmd.AddCommand(bootstrapGitCmd)
|
bootstrapCmd.AddCommand(bootstrapGitCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
|
func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
|
||||||
|
gitPassword := os.Getenv(gitPasswordEnvVar)
|
||||||
|
if gitPassword != "" && gitArgs.password == "" {
|
||||||
|
gitArgs.password = gitPassword
|
||||||
|
}
|
||||||
|
if bootstrapArgs.tokenAuth && gitArgs.password == "" {
|
||||||
|
var err error
|
||||||
|
gitPassword, err = readPasswordFromStdin("Please enter your Git repository password: ")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not read token: %w", err)
|
||||||
|
}
|
||||||
|
gitArgs.password = gitPassword
|
||||||
|
}
|
||||||
|
|
||||||
if err := bootstrapValidate(); err != nil {
|
if err := bootstrapValidate(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -94,21 +120,36 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
gitAuth, err := transportForURL(repositoryURL)
|
|
||||||
if err != nil {
|
if strings.Contains(repositoryURL.Hostname(), "git-codecommit") && strings.Contains(repositoryURL.Hostname(), "amazonaws.com") {
|
||||||
return err
|
if repositoryURL.Scheme == string(git.SSH) {
|
||||||
|
if repositoryURL.User == nil {
|
||||||
|
return fmt.Errorf("invalid AWS CodeCommit url: ssh username should be specified in the url")
|
||||||
|
}
|
||||||
|
if repositoryURL.User.Username() == git.DefaultPublicKeyAuthUser {
|
||||||
|
return fmt.Errorf("invalid AWS CodeCommit url: ssh username should be the SSH key ID for the provided private key")
|
||||||
|
}
|
||||||
|
if bootstrapArgs.privateKeyFile == "" {
|
||||||
|
return fmt.Errorf("private key file is required for bootstrapping against AWS CodeCommit using ssh")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if repositoryURL.Scheme == string(git.HTTPS) && !bootstrapArgs.tokenAuth {
|
||||||
|
return fmt.Errorf("--token-auth=true must be specified for using a HTTPS AWS CodeCommit url")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manifest base
|
// Manifest base
|
||||||
if ver, err := getVersion(bootstrapArgs.version); err == nil {
|
if ver, err := getVersion(bootstrapArgs.version); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
bootstrapArgs.version = ver
|
bootstrapArgs.version = ver
|
||||||
}
|
}
|
||||||
manifestsBase, err := buildEmbeddedManifestBase()
|
manifestsBase, err := buildEmbeddedManifestBase()
|
||||||
@@ -118,18 +159,39 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
defer os.RemoveAll(manifestsBase)
|
defer os.RemoveAll(manifestsBase)
|
||||||
|
|
||||||
// Lazy go-git repository
|
// Lazy go-git repository
|
||||||
tmpDir, err := ioutil.TempDir("", "flux-bootstrap-")
|
tmpDir, err := manifestgen.MkdirTempAbs("", "flux-bootstrap-")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create temporary working dir: %w", err)
|
return fmt.Errorf("failed to create temporary working dir: %w", err)
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(tmpDir)
|
defer os.RemoveAll(tmpDir)
|
||||||
gitClient := gogit.New(tmpDir, gitAuth)
|
|
||||||
|
var caBundle []byte
|
||||||
|
if bootstrapArgs.caFile != "" {
|
||||||
|
var err error
|
||||||
|
caBundle, err = os.ReadFile(bootstrapArgs.caFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to read TLS CA file: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
authOpts, err := getAuthOpts(repositoryURL, caBundle)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create authentication options for %s: %w", repositoryURL.String(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
clientOpts := []gogit.ClientOption{gogit.WithDiskStorage(), gogit.WithFallbackToDefaultKnownHosts()}
|
||||||
|
if gitArgs.insecureHttpAllowed {
|
||||||
|
clientOpts = append(clientOpts, gogit.WithInsecureCredentialsOverHTTP())
|
||||||
|
}
|
||||||
|
gitClient, err := gogit.NewClient(tmpDir, authOpts, clientOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create a Git client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Install manifest config
|
// Install manifest config
|
||||||
installOptions := install.Options{
|
installOptions := install.Options{
|
||||||
BaseURL: rootArgs.defaults.BaseURL,
|
BaseURL: rootArgs.defaults.BaseURL,
|
||||||
Version: bootstrapArgs.version,
|
Version: bootstrapArgs.version,
|
||||||
Namespace: rootArgs.namespace,
|
Namespace: *kubeconfigArgs.Namespace,
|
||||||
Components: bootstrapComponents(),
|
Components: bootstrapComponents(),
|
||||||
Registry: bootstrapArgs.registry,
|
Registry: bootstrapArgs.registry,
|
||||||
ImagePullSecret: bootstrapArgs.imagePullSecret,
|
ImagePullSecret: bootstrapArgs.imagePullSecret,
|
||||||
@@ -150,37 +212,50 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
// Source generation and secret config
|
// Source generation and secret config
|
||||||
secretOpts := sourcesecret.Options{
|
secretOpts := sourcesecret.Options{
|
||||||
Name: bootstrapArgs.secretName,
|
Name: bootstrapArgs.secretName,
|
||||||
Namespace: rootArgs.namespace,
|
Namespace: *kubeconfigArgs.Namespace,
|
||||||
TargetPath: gitArgs.path.String(),
|
TargetPath: gitArgs.path.String(),
|
||||||
ManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile,
|
ManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile,
|
||||||
}
|
}
|
||||||
if bootstrapArgs.tokenAuth {
|
if bootstrapArgs.tokenAuth {
|
||||||
secretOpts.Username = gitArgs.username
|
secretOpts.Username = gitArgs.username
|
||||||
secretOpts.Password = gitArgs.password
|
secretOpts.Password = gitArgs.password
|
||||||
|
secretOpts.CAFile = caBundle
|
||||||
|
|
||||||
if bootstrapArgs.caFile != "" {
|
// Remove port of the given host when not syncing over HTTP/S to not assume port for protocol
|
||||||
secretOpts.CAFilePath = bootstrapArgs.caFile
|
// This _might_ be overwritten later on by e.g. --ssh-hostname
|
||||||
|
if repositoryURL.Scheme != "https" && repositoryURL.Scheme != "http" {
|
||||||
|
repositoryURL.Host = repositoryURL.Hostname()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure repository URL to match auth config for sync.
|
// Configure repository URL to match auth config for sync.
|
||||||
repositoryURL.User = nil
|
repositoryURL.User = nil
|
||||||
|
if !gitArgs.insecureHttpAllowed {
|
||||||
repositoryURL.Scheme = "https"
|
repositoryURL.Scheme = "https"
|
||||||
repositoryURL.Host = repositoryURL.Hostname()
|
}
|
||||||
} else {
|
} else {
|
||||||
secretOpts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(bootstrapArgs.keyAlgorithm)
|
secretOpts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(bootstrapArgs.keyAlgorithm)
|
||||||
secretOpts.Password = gitArgs.password
|
secretOpts.Password = gitArgs.password
|
||||||
secretOpts.RSAKeyBits = int(bootstrapArgs.keyRSABits)
|
secretOpts.RSAKeyBits = int(bootstrapArgs.keyRSABits)
|
||||||
secretOpts.ECDSACurve = bootstrapArgs.keyECDSACurve.Curve
|
secretOpts.ECDSACurve = bootstrapArgs.keyECDSACurve.Curve
|
||||||
|
|
||||||
// Configure repository URL to match auth config for sync.
|
// Configure repository URL to match auth config for sync
|
||||||
|
|
||||||
|
// Override existing user when user is not already set
|
||||||
|
// or when a username was passed in
|
||||||
|
if repositoryURL.User == nil || gitArgs.username != "git" {
|
||||||
repositoryURL.User = url.User(gitArgs.username)
|
repositoryURL.User = url.User(gitArgs.username)
|
||||||
|
}
|
||||||
|
|
||||||
repositoryURL.Scheme = "ssh"
|
repositoryURL.Scheme = "ssh"
|
||||||
if bootstrapArgs.sshHostname != "" {
|
if bootstrapArgs.sshHostname != "" {
|
||||||
repositoryURL.Host = bootstrapArgs.sshHostname
|
repositoryURL.Host = bootstrapArgs.sshHostname
|
||||||
}
|
}
|
||||||
if bootstrapArgs.privateKeyFile != "" {
|
|
||||||
secretOpts.PrivateKeyPath = bootstrapArgs.privateKeyFile
|
keypair, err := sourcesecret.LoadKeyPairFromPath(bootstrapArgs.privateKeyFile, gitArgs.password)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
secretOpts.Keypair = keypair
|
||||||
|
|
||||||
// Configure last as it depends on the config above.
|
// Configure last as it depends on the config above.
|
||||||
secretOpts.SSHHostname = repositoryURL.Host
|
secretOpts.SSHHostname = repositoryURL.Host
|
||||||
@@ -189,8 +264,8 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
// Sync manifest config
|
// Sync manifest config
|
||||||
syncOpts := sync.Options{
|
syncOpts := sync.Options{
|
||||||
Interval: gitArgs.interval,
|
Interval: gitArgs.interval,
|
||||||
Name: rootArgs.namespace,
|
Name: *kubeconfigArgs.Namespace,
|
||||||
Namespace: rootArgs.namespace,
|
Namespace: *kubeconfigArgs.Namespace,
|
||||||
URL: repositoryURL.String(),
|
URL: repositoryURL.String(),
|
||||||
Branch: bootstrapArgs.branch,
|
Branch: bootstrapArgs.branch,
|
||||||
Secret: bootstrapArgs.secretName,
|
Secret: bootstrapArgs.secretName,
|
||||||
@@ -200,15 +275,21 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
RecurseSubmodules: bootstrapArgs.recurseSubmodules,
|
RecurseSubmodules: bootstrapArgs.recurseSubmodules,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
entityList, err := bootstrap.LoadEntityListFromPath(bootstrapArgs.gpgKeyRingPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Bootstrap config
|
// Bootstrap config
|
||||||
bootstrapOpts := []bootstrap.GitOption{
|
bootstrapOpts := []bootstrap.GitOption{
|
||||||
bootstrap.WithRepositoryURL(gitArgs.url),
|
bootstrap.WithRepositoryURL(gitArgs.url),
|
||||||
bootstrap.WithBranch(bootstrapArgs.branch),
|
bootstrap.WithBranch(bootstrapArgs.branch),
|
||||||
bootstrap.WithAuthor(bootstrapArgs.authorName, bootstrapArgs.authorEmail),
|
bootstrap.WithSignature(bootstrapArgs.authorName, bootstrapArgs.authorEmail),
|
||||||
bootstrap.WithCommitMessageAppendix(bootstrapArgs.commitMessageAppendix),
|
bootstrap.WithCommitMessageAppendix(bootstrapArgs.commitMessageAppendix),
|
||||||
bootstrap.WithKubeconfig(rootArgs.kubeconfig, rootArgs.kubecontext),
|
bootstrap.WithKubeconfig(kubeconfigArgs, kubeclientOptions),
|
||||||
bootstrap.WithPostGenerateSecretFunc(promptPublicKey),
|
bootstrap.WithPostGenerateSecretFunc(promptPublicKey),
|
||||||
bootstrap.WithLogger(logger),
|
bootstrap.WithLogger(logger),
|
||||||
|
bootstrap.WithGitCommitSigning(entityList, bootstrapArgs.gpgPassphrase, bootstrapArgs.gpgKeyID),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup bootstrapper with constructed configs
|
// Setup bootstrapper with constructed configs
|
||||||
@@ -221,22 +302,47 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
return bootstrap.Run(ctx, b, manifestsBase, installOptions, secretOpts, syncOpts, rootArgs.pollInterval, rootArgs.timeout)
|
return bootstrap.Run(ctx, b, manifestsBase, installOptions, secretOpts, syncOpts, rootArgs.pollInterval, rootArgs.timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
// transportForURL constructs a transport.AuthMethod based on the scheme
|
// getAuthOpts retruns a AuthOptions based on the scheme
|
||||||
// of the given URL and the configured flags. If the protocol equals
|
// of the given URL and the configured flags. If the protocol equals
|
||||||
// "ssh" but no private key is configured, authentication using the local
|
// "ssh" but no private key is configured, authentication using the local
|
||||||
// SSH-agent is attempted.
|
// SSH-agent is attempted.
|
||||||
func transportForURL(u *url.URL) (transport.AuthMethod, error) {
|
func getAuthOpts(u *url.URL, caBundle []byte) (*git.AuthOptions, error) {
|
||||||
switch u.Scheme {
|
switch u.Scheme {
|
||||||
case "https":
|
case "http":
|
||||||
return &http.BasicAuth{
|
if !gitArgs.insecureHttpAllowed {
|
||||||
|
return nil, fmt.Errorf("scheme http is insecure, pass --allow-insecure-http=true to allow it")
|
||||||
|
}
|
||||||
|
return &git.AuthOptions{
|
||||||
|
Transport: git.HTTP,
|
||||||
Username: gitArgs.username,
|
Username: gitArgs.username,
|
||||||
Password: gitArgs.password,
|
Password: gitArgs.password,
|
||||||
}, nil
|
}, nil
|
||||||
|
case "https":
|
||||||
|
return &git.AuthOptions{
|
||||||
|
Transport: git.HTTPS,
|
||||||
|
Username: gitArgs.username,
|
||||||
|
Password: gitArgs.password,
|
||||||
|
CAFile: caBundle,
|
||||||
|
}, nil
|
||||||
case "ssh":
|
case "ssh":
|
||||||
if bootstrapArgs.privateKeyFile != "" {
|
authOpts := &git.AuthOptions{
|
||||||
return ssh.NewPublicKeysFromFile(u.User.Username(), bootstrapArgs.privateKeyFile, gitArgs.password)
|
Transport: git.SSH,
|
||||||
|
Username: u.User.Username(),
|
||||||
|
Password: gitArgs.password,
|
||||||
}
|
}
|
||||||
return nil, nil
|
if bootstrapArgs.privateKeyFile != "" {
|
||||||
|
pk, err := os.ReadFile(bootstrapArgs.privateKeyFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
kh, err := sourcesecret.ScanHostKey(u.Host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
authOpts.Identity = pk
|
||||||
|
authOpts.KnownHosts = kh
|
||||||
|
}
|
||||||
|
return authOpts, nil
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("scheme %q is not supported", u.Scheme)
|
return nil, fmt.Errorf("scheme %q is not supported", u.Scheme)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,18 +19,18 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-git/go-git/v5/plumbing/transport/http"
|
"github.com/fluxcd/pkg/git"
|
||||||
|
"github.com/fluxcd/pkg/git/gogit"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/internal/bootstrap"
|
|
||||||
"github.com/fluxcd/flux2/internal/bootstrap/git/gogit"
|
|
||||||
"github.com/fluxcd/flux2/internal/bootstrap/provider"
|
|
||||||
"github.com/fluxcd/flux2/internal/flags"
|
"github.com/fluxcd/flux2/internal/flags"
|
||||||
"github.com/fluxcd/flux2/internal/utils"
|
"github.com/fluxcd/flux2/internal/utils"
|
||||||
|
"github.com/fluxcd/flux2/pkg/bootstrap"
|
||||||
|
"github.com/fluxcd/flux2/pkg/bootstrap/provider"
|
||||||
|
"github.com/fluxcd/flux2/pkg/manifestgen"
|
||||||
"github.com/fluxcd/flux2/pkg/manifestgen/install"
|
"github.com/fluxcd/flux2/pkg/manifestgen/install"
|
||||||
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
|
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
|
||||||
"github.com/fluxcd/flux2/pkg/manifestgen/sync"
|
"github.com/fluxcd/flux2/pkg/manifestgen/sync"
|
||||||
@@ -53,6 +53,9 @@ the bootstrap command will perform an upgrade if needed.`,
|
|||||||
# Run bootstrap for a private repository and assign organization teams to it
|
# Run bootstrap for a private repository and assign organization teams to it
|
||||||
flux bootstrap github --owner=<organization> --repository=<repository name> --team=<team1 slug> --team=<team2 slug>
|
flux bootstrap github --owner=<organization> --repository=<repository name> --team=<team1 slug> --team=<team2 slug>
|
||||||
|
|
||||||
|
# Run bootstrap for a private repository and assign organization teams with their access level(e.g maintain, admin) to it
|
||||||
|
flux bootstrap github --owner=<organization> --repository=<repository name> --team=<team1 slug>:<access-level>
|
||||||
|
|
||||||
# Run bootstrap for a repository path
|
# Run bootstrap for a repository path
|
||||||
flux bootstrap github --owner=<organization> --repository=<repository name> --path=dev-cluster
|
flux bootstrap github --owner=<organization> --repository=<repository name> --path=dev-cluster
|
||||||
|
|
||||||
@@ -94,7 +97,7 @@ var githubArgs githubFlags
|
|||||||
func init() {
|
func init() {
|
||||||
bootstrapGitHubCmd.Flags().StringVar(&githubArgs.owner, "owner", "", "GitHub user or organization name")
|
bootstrapGitHubCmd.Flags().StringVar(&githubArgs.owner, "owner", "", "GitHub user or organization name")
|
||||||
bootstrapGitHubCmd.Flags().StringVar(&githubArgs.repository, "repository", "", "GitHub repository name")
|
bootstrapGitHubCmd.Flags().StringVar(&githubArgs.repository, "repository", "", "GitHub repository name")
|
||||||
bootstrapGitHubCmd.Flags().StringSliceVar(&githubArgs.teams, "team", []string{}, "GitHub team to be given maintainer access (also accepts comma-separated values)")
|
bootstrapGitHubCmd.Flags().StringSliceVar(&githubArgs.teams, "team", []string{}, "GitHub team and the access to be given to it(team:maintain). Defaults to maintainer access if no access level is specified (also accepts comma-separated values)")
|
||||||
bootstrapGitHubCmd.Flags().BoolVar(&githubArgs.personal, "personal", false, "if true, the owner is assumed to be a GitHub user; otherwise an org")
|
bootstrapGitHubCmd.Flags().BoolVar(&githubArgs.personal, "personal", false, "if true, the owner is assumed to be a GitHub user; otherwise an org")
|
||||||
bootstrapGitHubCmd.Flags().BoolVar(&githubArgs.private, "private", true, "if true, the repository is setup or configured as private")
|
bootstrapGitHubCmd.Flags().BoolVar(&githubArgs.private, "private", true, "if true, the repository is setup or configured as private")
|
||||||
bootstrapGitHubCmd.Flags().DurationVar(&githubArgs.interval, "interval", time.Minute, "sync interval")
|
bootstrapGitHubCmd.Flags().DurationVar(&githubArgs.interval, "interval", time.Minute, "sync interval")
|
||||||
@@ -109,7 +112,11 @@ func init() {
|
|||||||
func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
|
func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
|
||||||
ghToken := os.Getenv(ghTokenEnvVar)
|
ghToken := os.Getenv(ghTokenEnvVar)
|
||||||
if ghToken == "" {
|
if ghToken == "" {
|
||||||
return fmt.Errorf("%s environment variable not found", ghTokenEnvVar)
|
var err error
|
||||||
|
ghToken, err = readPasswordFromStdin("Please enter your GitHub personal access token (PAT): ")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not read token: %w", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := bootstrapValidate(); err != nil {
|
if err := bootstrapValidate(); err != nil {
|
||||||
@@ -119,13 +126,15 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manifest base
|
// Manifest base
|
||||||
if ver, err := getVersion(bootstrapArgs.version); err == nil {
|
if ver, err := getVersion(bootstrapArgs.version); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
bootstrapArgs.version = ver
|
bootstrapArgs.version = ver
|
||||||
}
|
}
|
||||||
manifestsBase, err := buildEmbeddedManifestBase()
|
manifestsBase, err := buildEmbeddedManifestBase()
|
||||||
@@ -134,33 +143,48 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
defer os.RemoveAll(manifestsBase)
|
defer os.RemoveAll(manifestsBase)
|
||||||
|
|
||||||
|
var caBundle []byte
|
||||||
|
if bootstrapArgs.caFile != "" {
|
||||||
|
var err error
|
||||||
|
caBundle, err = os.ReadFile(bootstrapArgs.caFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to read TLS CA file: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
// Build GitHub provider
|
// Build GitHub provider
|
||||||
providerCfg := provider.Config{
|
providerCfg := provider.Config{
|
||||||
Provider: provider.GitProviderGitHub,
|
Provider: provider.GitProviderGitHub,
|
||||||
Hostname: githubArgs.hostname,
|
Hostname: githubArgs.hostname,
|
||||||
Token: ghToken,
|
Token: ghToken,
|
||||||
|
CaBundle: caBundle,
|
||||||
}
|
}
|
||||||
providerClient, err := provider.BuildGitProvider(providerCfg)
|
providerClient, err := provider.BuildGitProvider(providerCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lazy go-git repository
|
tmpDir, err := manifestgen.MkdirTempAbs("", "flux-bootstrap-")
|
||||||
tmpDir, err := ioutil.TempDir("", "flux-bootstrap-")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create temporary working dir: %w", err)
|
return fmt.Errorf("failed to create temporary working dir: %w", err)
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(tmpDir)
|
defer os.RemoveAll(tmpDir)
|
||||||
gitClient := gogit.New(tmpDir, &http.BasicAuth{
|
|
||||||
|
clientOpts := []gogit.ClientOption{gogit.WithDiskStorage(), gogit.WithFallbackToDefaultKnownHosts()}
|
||||||
|
gitClient, err := gogit.NewClient(tmpDir, &git.AuthOptions{
|
||||||
|
Transport: git.HTTPS,
|
||||||
Username: githubArgs.owner,
|
Username: githubArgs.owner,
|
||||||
Password: ghToken,
|
Password: ghToken,
|
||||||
})
|
CAFile: caBundle,
|
||||||
|
}, clientOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create a Git client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Install manifest config
|
// Install manifest config
|
||||||
installOptions := install.Options{
|
installOptions := install.Options{
|
||||||
BaseURL: rootArgs.defaults.BaseURL,
|
BaseURL: rootArgs.defaults.BaseURL,
|
||||||
Version: bootstrapArgs.version,
|
Version: bootstrapArgs.version,
|
||||||
Namespace: rootArgs.namespace,
|
Namespace: *kubeconfigArgs.Namespace,
|
||||||
Components: bootstrapComponents(),
|
Components: bootstrapComponents(),
|
||||||
Registry: bootstrapArgs.registry,
|
Registry: bootstrapArgs.registry,
|
||||||
ImagePullSecret: bootstrapArgs.imagePullSecret,
|
ImagePullSecret: bootstrapArgs.imagePullSecret,
|
||||||
@@ -181,23 +205,20 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
// Source generation and secret config
|
// Source generation and secret config
|
||||||
secretOpts := sourcesecret.Options{
|
secretOpts := sourcesecret.Options{
|
||||||
Name: bootstrapArgs.secretName,
|
Name: bootstrapArgs.secretName,
|
||||||
Namespace: rootArgs.namespace,
|
Namespace: *kubeconfigArgs.Namespace,
|
||||||
TargetPath: githubArgs.path.ToSlash(),
|
TargetPath: githubArgs.path.ToSlash(),
|
||||||
ManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile,
|
ManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile,
|
||||||
}
|
}
|
||||||
if bootstrapArgs.tokenAuth {
|
if bootstrapArgs.tokenAuth {
|
||||||
secretOpts.Username = "git"
|
secretOpts.Username = "git"
|
||||||
secretOpts.Password = ghToken
|
secretOpts.Password = ghToken
|
||||||
|
secretOpts.CAFile = caBundle
|
||||||
if bootstrapArgs.caFile != "" {
|
|
||||||
secretOpts.CAFilePath = bootstrapArgs.caFile
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
secretOpts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(bootstrapArgs.keyAlgorithm)
|
secretOpts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(bootstrapArgs.keyAlgorithm)
|
||||||
secretOpts.RSAKeyBits = int(bootstrapArgs.keyRSABits)
|
secretOpts.RSAKeyBits = int(bootstrapArgs.keyRSABits)
|
||||||
secretOpts.ECDSACurve = bootstrapArgs.keyECDSACurve.Curve
|
secretOpts.ECDSACurve = bootstrapArgs.keyECDSACurve.Curve
|
||||||
secretOpts.SSHHostname = githubArgs.hostname
|
|
||||||
|
|
||||||
|
secretOpts.SSHHostname = githubArgs.hostname
|
||||||
if bootstrapArgs.sshHostname != "" {
|
if bootstrapArgs.sshHostname != "" {
|
||||||
secretOpts.SSHHostname = bootstrapArgs.sshHostname
|
secretOpts.SSHHostname = bootstrapArgs.sshHostname
|
||||||
}
|
}
|
||||||
@@ -206,8 +227,8 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
// Sync manifest config
|
// Sync manifest config
|
||||||
syncOpts := sync.Options{
|
syncOpts := sync.Options{
|
||||||
Interval: githubArgs.interval,
|
Interval: githubArgs.interval,
|
||||||
Name: rootArgs.namespace,
|
Name: *kubeconfigArgs.Namespace,
|
||||||
Namespace: rootArgs.namespace,
|
Namespace: *kubeconfigArgs.Namespace,
|
||||||
Branch: bootstrapArgs.branch,
|
Branch: bootstrapArgs.branch,
|
||||||
Secret: bootstrapArgs.secretName,
|
Secret: bootstrapArgs.secretName,
|
||||||
TargetPath: githubArgs.path.ToSlash(),
|
TargetPath: githubArgs.path.ToSlash(),
|
||||||
@@ -216,17 +237,23 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
RecurseSubmodules: bootstrapArgs.recurseSubmodules,
|
RecurseSubmodules: bootstrapArgs.recurseSubmodules,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
entityList, err := bootstrap.LoadEntityListFromPath(bootstrapArgs.gpgKeyRingPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Bootstrap config
|
// Bootstrap config
|
||||||
bootstrapOpts := []bootstrap.GitProviderOption{
|
bootstrapOpts := []bootstrap.GitProviderOption{
|
||||||
bootstrap.WithProviderRepository(githubArgs.owner, githubArgs.repository, githubArgs.personal),
|
bootstrap.WithProviderRepository(githubArgs.owner, githubArgs.repository, githubArgs.personal),
|
||||||
bootstrap.WithBranch(bootstrapArgs.branch),
|
bootstrap.WithBranch(bootstrapArgs.branch),
|
||||||
bootstrap.WithBootstrapTransportType("https"),
|
bootstrap.WithBootstrapTransportType("https"),
|
||||||
bootstrap.WithAuthor(bootstrapArgs.authorName, bootstrapArgs.authorEmail),
|
bootstrap.WithSignature(bootstrapArgs.authorName, bootstrapArgs.authorEmail),
|
||||||
bootstrap.WithCommitMessageAppendix(bootstrapArgs.commitMessageAppendix),
|
bootstrap.WithCommitMessageAppendix(bootstrapArgs.commitMessageAppendix),
|
||||||
bootstrap.WithProviderTeamPermissions(mapTeamSlice(githubArgs.teams, ghDefaultPermission)),
|
bootstrap.WithProviderTeamPermissions(mapTeamSlice(githubArgs.teams, ghDefaultPermission)),
|
||||||
bootstrap.WithReadWriteKeyPermissions(githubArgs.readWriteKey),
|
bootstrap.WithReadWriteKeyPermissions(githubArgs.readWriteKey),
|
||||||
bootstrap.WithKubeconfig(rootArgs.kubeconfig, rootArgs.kubecontext),
|
bootstrap.WithKubeconfig(kubeconfigArgs, kubeclientOptions),
|
||||||
bootstrap.WithLogger(logger),
|
bootstrap.WithLogger(logger),
|
||||||
|
bootstrap.WithGitCommitSigning(entityList, bootstrapArgs.gpgPassphrase, bootstrapArgs.gpgKeyID),
|
||||||
}
|
}
|
||||||
if bootstrapArgs.sshHostname != "" {
|
if bootstrapArgs.sshHostname != "" {
|
||||||
bootstrapOpts = append(bootstrapOpts, bootstrap.WithSSHHostname(bootstrapArgs.sshHostname))
|
bootstrapOpts = append(bootstrapOpts, bootstrap.WithSSHHostname(bootstrapArgs.sshHostname))
|
||||||
|
|||||||
@@ -19,20 +19,20 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-git/go-git/v5/plumbing/transport/http"
|
"github.com/fluxcd/pkg/git"
|
||||||
|
"github.com/fluxcd/pkg/git/gogit"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/internal/bootstrap"
|
|
||||||
"github.com/fluxcd/flux2/internal/bootstrap/git/gogit"
|
|
||||||
"github.com/fluxcd/flux2/internal/bootstrap/provider"
|
|
||||||
"github.com/fluxcd/flux2/internal/flags"
|
"github.com/fluxcd/flux2/internal/flags"
|
||||||
"github.com/fluxcd/flux2/internal/utils"
|
"github.com/fluxcd/flux2/internal/utils"
|
||||||
|
"github.com/fluxcd/flux2/pkg/bootstrap"
|
||||||
|
"github.com/fluxcd/flux2/pkg/bootstrap/provider"
|
||||||
|
"github.com/fluxcd/flux2/pkg/manifestgen"
|
||||||
"github.com/fluxcd/flux2/pkg/manifestgen/install"
|
"github.com/fluxcd/flux2/pkg/manifestgen/install"
|
||||||
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
|
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
|
||||||
"github.com/fluxcd/flux2/pkg/manifestgen/sync"
|
"github.com/fluxcd/flux2/pkg/manifestgen/sync"
|
||||||
@@ -109,7 +109,11 @@ func init() {
|
|||||||
func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
|
func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
|
||||||
glToken := os.Getenv(glTokenEnvVar)
|
glToken := os.Getenv(glTokenEnvVar)
|
||||||
if glToken == "" {
|
if glToken == "" {
|
||||||
return fmt.Errorf("%s environment variable not found", glTokenEnvVar)
|
var err error
|
||||||
|
glToken, err = readPasswordFromStdin("Please enter your GitLab personal access token (PAT): ")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not read token: %w", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if projectNameIsValid, err := regexp.MatchString(gitlabProjectRegex, gitlabArgs.repository); err != nil || !projectNameIsValid {
|
if projectNameIsValid, err := regexp.MatchString(gitlabProjectRegex, gitlabArgs.repository); err != nil || !projectNameIsValid {
|
||||||
@@ -126,13 +130,15 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manifest base
|
// Manifest base
|
||||||
if ver, err := getVersion(bootstrapArgs.version); err == nil {
|
if ver, err := getVersion(bootstrapArgs.version); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
bootstrapArgs.version = ver
|
bootstrapArgs.version = ver
|
||||||
}
|
}
|
||||||
manifestsBase, err := buildEmbeddedManifestBase()
|
manifestsBase, err := buildEmbeddedManifestBase()
|
||||||
@@ -141,11 +147,21 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
defer os.RemoveAll(manifestsBase)
|
defer os.RemoveAll(manifestsBase)
|
||||||
|
|
||||||
|
var caBundle []byte
|
||||||
|
if bootstrapArgs.caFile != "" {
|
||||||
|
var err error
|
||||||
|
caBundle, err = os.ReadFile(bootstrapArgs.caFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to read TLS CA file: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Build GitLab provider
|
// Build GitLab provider
|
||||||
providerCfg := provider.Config{
|
providerCfg := provider.Config{
|
||||||
Provider: provider.GitProviderGitLab,
|
Provider: provider.GitProviderGitLab,
|
||||||
Hostname: gitlabArgs.hostname,
|
Hostname: gitlabArgs.hostname,
|
||||||
Token: glToken,
|
Token: glToken,
|
||||||
|
CaBundle: caBundle,
|
||||||
}
|
}
|
||||||
// Workaround for: https://github.com/fluxcd/go-git-providers/issues/55
|
// Workaround for: https://github.com/fluxcd/go-git-providers/issues/55
|
||||||
if hostname := providerCfg.Hostname; hostname != glDefaultDomain &&
|
if hostname := providerCfg.Hostname; hostname != glDefaultDomain &&
|
||||||
@@ -159,21 +175,28 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Lazy go-git repository
|
// Lazy go-git repository
|
||||||
tmpDir, err := ioutil.TempDir("", "flux-bootstrap-")
|
tmpDir, err := manifestgen.MkdirTempAbs("", "flux-bootstrap-")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create temporary working dir: %w", err)
|
return fmt.Errorf("failed to create temporary working dir: %w", err)
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(tmpDir)
|
defer os.RemoveAll(tmpDir)
|
||||||
gitClient := gogit.New(tmpDir, &http.BasicAuth{
|
|
||||||
|
clientOpts := []gogit.ClientOption{gogit.WithDiskStorage(), gogit.WithFallbackToDefaultKnownHosts()}
|
||||||
|
gitClient, err := gogit.NewClient(tmpDir, &git.AuthOptions{
|
||||||
|
Transport: git.HTTPS,
|
||||||
Username: gitlabArgs.owner,
|
Username: gitlabArgs.owner,
|
||||||
Password: glToken,
|
Password: glToken,
|
||||||
})
|
CAFile: caBundle,
|
||||||
|
}, clientOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create a Git client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Install manifest config
|
// Install manifest config
|
||||||
installOptions := install.Options{
|
installOptions := install.Options{
|
||||||
BaseURL: rootArgs.defaults.BaseURL,
|
BaseURL: rootArgs.defaults.BaseURL,
|
||||||
Version: bootstrapArgs.version,
|
Version: bootstrapArgs.version,
|
||||||
Namespace: rootArgs.namespace,
|
Namespace: *kubeconfigArgs.Namespace,
|
||||||
Components: bootstrapComponents(),
|
Components: bootstrapComponents(),
|
||||||
Registry: bootstrapArgs.registry,
|
Registry: bootstrapArgs.registry,
|
||||||
ImagePullSecret: bootstrapArgs.imagePullSecret,
|
ImagePullSecret: bootstrapArgs.imagePullSecret,
|
||||||
@@ -194,26 +217,25 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
// Source generation and secret config
|
// Source generation and secret config
|
||||||
secretOpts := sourcesecret.Options{
|
secretOpts := sourcesecret.Options{
|
||||||
Name: bootstrapArgs.secretName,
|
Name: bootstrapArgs.secretName,
|
||||||
Namespace: rootArgs.namespace,
|
Namespace: *kubeconfigArgs.Namespace,
|
||||||
TargetPath: gitlabArgs.path.String(),
|
TargetPath: gitlabArgs.path.String(),
|
||||||
ManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile,
|
ManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile,
|
||||||
}
|
}
|
||||||
if bootstrapArgs.tokenAuth {
|
if bootstrapArgs.tokenAuth {
|
||||||
secretOpts.Username = "git"
|
secretOpts.Username = "git"
|
||||||
secretOpts.Password = glToken
|
secretOpts.Password = glToken
|
||||||
|
secretOpts.CAFile = caBundle
|
||||||
if bootstrapArgs.caFile != "" {
|
|
||||||
secretOpts.CAFilePath = bootstrapArgs.caFile
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
|
keypair, err := sourcesecret.LoadKeyPairFromPath(bootstrapArgs.privateKeyFile, gitArgs.password)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
secretOpts.Keypair = keypair
|
||||||
secretOpts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(bootstrapArgs.keyAlgorithm)
|
secretOpts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(bootstrapArgs.keyAlgorithm)
|
||||||
secretOpts.RSAKeyBits = int(bootstrapArgs.keyRSABits)
|
secretOpts.RSAKeyBits = int(bootstrapArgs.keyRSABits)
|
||||||
secretOpts.ECDSACurve = bootstrapArgs.keyECDSACurve.Curve
|
secretOpts.ECDSACurve = bootstrapArgs.keyECDSACurve.Curve
|
||||||
secretOpts.SSHHostname = gitlabArgs.hostname
|
|
||||||
|
|
||||||
if bootstrapArgs.privateKeyFile != "" {
|
secretOpts.SSHHostname = gitlabArgs.hostname
|
||||||
secretOpts.PrivateKeyPath = bootstrapArgs.privateKeyFile
|
|
||||||
}
|
|
||||||
if bootstrapArgs.sshHostname != "" {
|
if bootstrapArgs.sshHostname != "" {
|
||||||
secretOpts.SSHHostname = bootstrapArgs.sshHostname
|
secretOpts.SSHHostname = bootstrapArgs.sshHostname
|
||||||
}
|
}
|
||||||
@@ -222,8 +244,8 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
// Sync manifest config
|
// Sync manifest config
|
||||||
syncOpts := sync.Options{
|
syncOpts := sync.Options{
|
||||||
Interval: gitlabArgs.interval,
|
Interval: gitlabArgs.interval,
|
||||||
Name: rootArgs.namespace,
|
Name: *kubeconfigArgs.Namespace,
|
||||||
Namespace: rootArgs.namespace,
|
Namespace: *kubeconfigArgs.Namespace,
|
||||||
Branch: bootstrapArgs.branch,
|
Branch: bootstrapArgs.branch,
|
||||||
Secret: bootstrapArgs.secretName,
|
Secret: bootstrapArgs.secretName,
|
||||||
TargetPath: gitlabArgs.path.ToSlash(),
|
TargetPath: gitlabArgs.path.ToSlash(),
|
||||||
@@ -232,17 +254,23 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
RecurseSubmodules: bootstrapArgs.recurseSubmodules,
|
RecurseSubmodules: bootstrapArgs.recurseSubmodules,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
entityList, err := bootstrap.LoadEntityListFromPath(bootstrapArgs.gpgKeyRingPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Bootstrap config
|
// Bootstrap config
|
||||||
bootstrapOpts := []bootstrap.GitProviderOption{
|
bootstrapOpts := []bootstrap.GitProviderOption{
|
||||||
bootstrap.WithProviderRepository(gitlabArgs.owner, gitlabArgs.repository, gitlabArgs.personal),
|
bootstrap.WithProviderRepository(gitlabArgs.owner, gitlabArgs.repository, gitlabArgs.personal),
|
||||||
bootstrap.WithBranch(bootstrapArgs.branch),
|
bootstrap.WithBranch(bootstrapArgs.branch),
|
||||||
bootstrap.WithBootstrapTransportType("https"),
|
bootstrap.WithBootstrapTransportType("https"),
|
||||||
bootstrap.WithAuthor(bootstrapArgs.authorName, bootstrapArgs.authorEmail),
|
bootstrap.WithSignature(bootstrapArgs.authorName, bootstrapArgs.authorEmail),
|
||||||
bootstrap.WithCommitMessageAppendix(bootstrapArgs.commitMessageAppendix),
|
bootstrap.WithCommitMessageAppendix(bootstrapArgs.commitMessageAppendix),
|
||||||
bootstrap.WithProviderTeamPermissions(mapTeamSlice(gitlabArgs.teams, glDefaultPermission)),
|
bootstrap.WithProviderTeamPermissions(mapTeamSlice(gitlabArgs.teams, glDefaultPermission)),
|
||||||
bootstrap.WithReadWriteKeyPermissions(gitlabArgs.readWriteKey),
|
bootstrap.WithReadWriteKeyPermissions(gitlabArgs.readWriteKey),
|
||||||
bootstrap.WithKubeconfig(rootArgs.kubeconfig, rootArgs.kubecontext),
|
bootstrap.WithKubeconfig(kubeconfigArgs, kubeclientOptions),
|
||||||
bootstrap.WithLogger(logger),
|
bootstrap.WithLogger(logger),
|
||||||
|
bootstrap.WithGitCommitSigning(entityList, bootstrapArgs.gpgPassphrase, bootstrapArgs.gpgKeyID),
|
||||||
}
|
}
|
||||||
if bootstrapArgs.sshHostname != "" {
|
if bootstrapArgs.sshHostname != "" {
|
||||||
bootstrapOpts = append(bootstrapOpts, bootstrap.WithSSHHostname(bootstrapArgs.sshHostname))
|
bootstrapOpts = append(bootstrapOpts, bootstrap.WithSSHHostname(bootstrapArgs.sshHostname))
|
||||||
|
|||||||
@@ -20,20 +20,12 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var encryptCmd = &cobra.Command{
|
var buildCmd = &cobra.Command{
|
||||||
Use: "encrypt",
|
Use: "build",
|
||||||
Short: "Encrypt secrets using SOPS",
|
Short: "Build a flux resource",
|
||||||
Long: "The encrypt sub-commands initialise and manage Secret encryption using SOPS.",
|
Long: "The build command is used to build flux resources.",
|
||||||
}
|
}
|
||||||
|
|
||||||
type encryptFlags struct {
|
|
||||||
export bool
|
|
||||||
}
|
|
||||||
|
|
||||||
var encryptArgs encryptFlags
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
encryptCmd.PersistentFlags().BoolVar(&encryptArgs.export, "export", false, "export in YAML format to stdout")
|
rootCmd.AddCommand(buildCmd)
|
||||||
|
|
||||||
rootCmd.AddCommand(encryptCmd)
|
|
||||||
}
|
}
|
||||||
116
cmd/flux/build_artifact.go
Normal file
116
cmd/flux/build_artifact.go
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 The Flux authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
oci "github.com/fluxcd/pkg/oci/client"
|
||||||
|
"github.com/fluxcd/pkg/sourceignore"
|
||||||
|
)
|
||||||
|
|
||||||
|
var buildArtifactCmd = &cobra.Command{
|
||||||
|
Use: "artifact",
|
||||||
|
Short: "Build artifact",
|
||||||
|
Long: `The build artifact command creates a tgz file with the manifests from the given directory or a single manifest file.`,
|
||||||
|
Example: ` # Build the given manifests directory into an artifact
|
||||||
|
flux build artifact --path ./path/to/local/manifests --output ./path/to/artifact.tgz
|
||||||
|
|
||||||
|
# Build the given single manifest file into an artifact
|
||||||
|
flux build artifact --path ./path/to/local/manifest.yaml --output ./path/to/artifact.tgz
|
||||||
|
|
||||||
|
# List the files bundled in the artifact
|
||||||
|
tar -ztvf ./path/to/artifact.tgz
|
||||||
|
`,
|
||||||
|
RunE: buildArtifactCmdRun,
|
||||||
|
}
|
||||||
|
|
||||||
|
type buildArtifactFlags struct {
|
||||||
|
output string
|
||||||
|
path string
|
||||||
|
ignorePaths []string
|
||||||
|
}
|
||||||
|
|
||||||
|
var excludeOCI = append(strings.Split(sourceignore.ExcludeVCS, ","), strings.Split(sourceignore.ExcludeExt, ",")...)
|
||||||
|
|
||||||
|
var buildArtifactArgs buildArtifactFlags
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
buildArtifactCmd.Flags().StringVarP(&buildArtifactArgs.path, "path", "p", "", "Path to the directory where the Kubernetes manifests are located.")
|
||||||
|
buildArtifactCmd.Flags().StringVarP(&buildArtifactArgs.output, "output", "o", "artifact.tgz", "Path to where the artifact tgz file should be written.")
|
||||||
|
buildArtifactCmd.Flags().StringSliceVar(&buildArtifactArgs.ignorePaths, "ignore-paths", excludeOCI, "set paths to ignore in .gitignore format")
|
||||||
|
|
||||||
|
buildCmd.AddCommand(buildArtifactCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildArtifactCmdRun(cmd *cobra.Command, args []string) error {
|
||||||
|
if buildArtifactArgs.path == "" {
|
||||||
|
return fmt.Errorf("invalid path %q", buildArtifactArgs.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
path := buildArtifactArgs.path
|
||||||
|
var err error
|
||||||
|
if buildArtifactArgs.path == "-" {
|
||||||
|
path, err = saveReaderToFile(os.Stdin)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer os.Remove(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := os.Stat(path); err != nil {
|
||||||
|
return fmt.Errorf("invalid path '%s', must point to an existing directory or file", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Actionf("building artifact from %s", path)
|
||||||
|
|
||||||
|
ociClient := oci.NewLocalClient()
|
||||||
|
if err := ociClient.Build(buildArtifactArgs.output, path, buildArtifactArgs.ignorePaths); err != nil {
|
||||||
|
return fmt.Errorf("bulding artifact failed, error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Successf("artifact created at %s", buildArtifactArgs.output)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveReaderToFile(reader io.Reader) (string, error) {
|
||||||
|
b, err := io.ReadAll(bufio.NewReader(reader))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
b = bytes.TrimRight(b, "\r\n")
|
||||||
|
f, err := os.CreateTemp("", "*.yaml")
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("unable to create temp dir for stdin")
|
||||||
|
}
|
||||||
|
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
if _, err := f.Write(b); err != nil {
|
||||||
|
return "", fmt.Errorf("error writing stdin to file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return f.Name(), nil
|
||||||
|
}
|
||||||
70
cmd/flux/build_artifact_test.go
Normal file
70
cmd/flux/build_artifact_test.go
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 The Flux authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_saveReaderToFile(t *testing.T) {
|
||||||
|
g := NewWithT(t)
|
||||||
|
|
||||||
|
testString := `apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: myapp
|
||||||
|
data:
|
||||||
|
foo: bar`
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
string string
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "yaml",
|
||||||
|
string: testString,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "yaml with carriage return",
|
||||||
|
string: testString + "\r\n",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tmpFile, err := saveReaderToFile(strings.NewReader(tt.string))
|
||||||
|
g.Expect(err).To(BeNil())
|
||||||
|
|
||||||
|
defer os.Remove(tmpFile)
|
||||||
|
|
||||||
|
b, err := os.ReadFile(tmpFile)
|
||||||
|
if tt.expectErr {
|
||||||
|
g.Expect(err).To(Not(BeNil()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
g.Expect(err).To(BeNil())
|
||||||
|
g.Expect(string(b)).To(BeEquivalentTo(testString))
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
138
cmd/flux/build_kustomization.go
Normal file
138
cmd/flux/build_kustomization.go
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2021 The Flux authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/fluxcd/flux2/internal/build"
|
||||||
|
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var buildKsCmd = &cobra.Command{
|
||||||
|
Use: "kustomization",
|
||||||
|
Aliases: []string{"ks"},
|
||||||
|
Short: "Build Kustomization",
|
||||||
|
Long: `The build command queries the Kubernetes API and fetches the specified Flux Kustomization.
|
||||||
|
It then uses the fetched in cluster flux kustomization to perform needed transformation on the local kustomization.yaml
|
||||||
|
pointed at by --path. The local kustomization.yaml is generated if it does not exist. Finally it builds the overlays using the local kustomization.yaml, and write the resulting multi-doc YAML to stdout.
|
||||||
|
|
||||||
|
It is possible to specify a Flux kustomization file using --kustomization-file.`,
|
||||||
|
Example: `# Build the local manifests as they were built on the cluster
|
||||||
|
flux build kustomization my-app --path ./path/to/local/manifests
|
||||||
|
|
||||||
|
# Build using a local flux kustomization file
|
||||||
|
flux build kustomization my-app --path ./path/to/local/manifests --kustomization-file ./path/to/local/my-app.yaml
|
||||||
|
|
||||||
|
# Build in dry-run mode without connecting to the cluster.
|
||||||
|
# Note that variable substitutions from Secrets and ConfigMaps are skipped in dry-run mode.
|
||||||
|
flux build kustomization my-app --path ./path/to/local/manifests --kustomization-file ./path/to/local/my-app.yaml --dry-run`,
|
||||||
|
ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
|
||||||
|
RunE: buildKsCmdRun,
|
||||||
|
}
|
||||||
|
|
||||||
|
type buildKsFlags struct {
|
||||||
|
kustomizationFile string
|
||||||
|
path string
|
||||||
|
dryRun bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var buildKsArgs buildKsFlags
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
buildKsCmd.Flags().StringVar(&buildKsArgs.path, "path", "", "Path to the manifests location.")
|
||||||
|
buildKsCmd.Flags().StringVar(&buildKsArgs.kustomizationFile, "kustomization-file", "", "Path to the Flux Kustomization YAML file.")
|
||||||
|
buildKsCmd.Flags().BoolVar(&buildKsArgs.dryRun, "dry-run", false, "Dry run mode.")
|
||||||
|
buildCmd.AddCommand(buildKsCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildKsCmdRun(cmd *cobra.Command, args []string) (err error) {
|
||||||
|
if len(args) < 1 {
|
||||||
|
return fmt.Errorf("%s name is required", kustomizationType.humanKind)
|
||||||
|
}
|
||||||
|
name := args[0]
|
||||||
|
|
||||||
|
if buildKsArgs.path == "" {
|
||||||
|
return fmt.Errorf("invalid resource path %q", buildKsArgs.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fs, err := os.Stat(buildKsArgs.path); err != nil || !fs.IsDir() {
|
||||||
|
return fmt.Errorf("invalid resource path %q", buildKsArgs.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
if buildKsArgs.dryRun && buildKsArgs.kustomizationFile == "" {
|
||||||
|
return fmt.Errorf("dry-run mode requires a kustomization file")
|
||||||
|
}
|
||||||
|
|
||||||
|
if buildKsArgs.kustomizationFile != "" {
|
||||||
|
if fs, err := os.Stat(buildKsArgs.kustomizationFile); os.IsNotExist(err) || fs.IsDir() {
|
||||||
|
return fmt.Errorf("invalid kustomization file %q", buildKsArgs.kustomizationFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var builder *build.Builder
|
||||||
|
if buildKsArgs.dryRun {
|
||||||
|
builder, err = build.NewBuilder(name, buildKsArgs.path,
|
||||||
|
build.WithTimeout(rootArgs.timeout),
|
||||||
|
build.WithKustomizationFile(buildKsArgs.kustomizationFile),
|
||||||
|
build.WithDryRun(buildKsArgs.dryRun),
|
||||||
|
build.WithNamespace(*kubeconfigArgs.Namespace),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
builder, err = build.NewBuilder(name, buildKsArgs.path,
|
||||||
|
build.WithClientConfig(kubeconfigArgs, kubeclientOptions),
|
||||||
|
build.WithTimeout(rootArgs.timeout),
|
||||||
|
build.WithKustomizationFile(buildKsArgs.kustomizationFile),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a signal channel
|
||||||
|
sigc := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sigc, os.Interrupt)
|
||||||
|
|
||||||
|
errChan := make(chan error)
|
||||||
|
go func() {
|
||||||
|
manifests, err := builder.Build()
|
||||||
|
if err != nil {
|
||||||
|
errChan <- err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Print(string(manifests))
|
||||||
|
errChan <- nil
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-sigc:
|
||||||
|
fmt.Println("Build cancelled... exiting.")
|
||||||
|
return builder.Cancel()
|
||||||
|
case err := <-errChan:
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
196
cmd/flux/build_kustomization_test.go
Normal file
196
cmd/flux/build_kustomization_test.go
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
//go:build unit
|
||||||
|
// +build unit
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 2021 The Flux authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"text/template"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setup(t *testing.T, tmpl map[string]string) {
|
||||||
|
t.Helper()
|
||||||
|
testEnv.CreateObjectFile("./testdata/build-kustomization/podinfo-source.yaml", tmpl, t)
|
||||||
|
testEnv.CreateObjectFile("./testdata/build-kustomization/podinfo-kustomization.yaml", tmpl, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildKustomization(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args string
|
||||||
|
resultFile string
|
||||||
|
assertFunc string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no args",
|
||||||
|
args: "build kustomization podinfo",
|
||||||
|
resultFile: "invalid resource path \"\"",
|
||||||
|
assertFunc: "assertError",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "build podinfo",
|
||||||
|
args: "build kustomization podinfo --path ./testdata/build-kustomization/podinfo",
|
||||||
|
resultFile: "./testdata/build-kustomization/podinfo-result.yaml",
|
||||||
|
assertFunc: "assertGoldenTemplateFile",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "build podinfo without service",
|
||||||
|
args: "build kustomization podinfo --path ./testdata/build-kustomization/delete-service",
|
||||||
|
resultFile: "./testdata/build-kustomization/podinfo-without-service-result.yaml",
|
||||||
|
assertFunc: "assertGoldenTemplateFile",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "build deployment and configmap with var substitution",
|
||||||
|
args: "build kustomization podinfo --path ./testdata/build-kustomization/var-substitution",
|
||||||
|
resultFile: "./testdata/build-kustomization/podinfo-with-var-substitution-result.yaml",
|
||||||
|
assertFunc: "assertGoldenTemplateFile",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpl := map[string]string{
|
||||||
|
"fluxns": allocateNamespace("flux-system"),
|
||||||
|
}
|
||||||
|
setup(t, tmpl)
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var assert assertFunc
|
||||||
|
|
||||||
|
switch tt.assertFunc {
|
||||||
|
case "assertGoldenTemplateFile":
|
||||||
|
assert = assertGoldenTemplateFile(tt.resultFile, tmpl)
|
||||||
|
case "assertError":
|
||||||
|
assert = assertError(tt.resultFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := cmdTestCase{
|
||||||
|
args: tt.args + " -n " + tmpl["fluxns"],
|
||||||
|
assert: assert,
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.runTestCmd(t)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildLocalKustomization(t *testing.T) {
|
||||||
|
podinfo := `apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
|
||||||
|
kind: Kustomization
|
||||||
|
metadata:
|
||||||
|
name: podinfo
|
||||||
|
namespace: {{ .fluxns }}
|
||||||
|
spec:
|
||||||
|
interval: 5m0s
|
||||||
|
path: ./kustomize
|
||||||
|
force: true
|
||||||
|
prune: true
|
||||||
|
sourceRef:
|
||||||
|
kind: GitRepository
|
||||||
|
name: podinfo
|
||||||
|
targetNamespace: default
|
||||||
|
postBuild:
|
||||||
|
substitute:
|
||||||
|
cluster_env: "prod"
|
||||||
|
cluster_region: "eu-central-1"
|
||||||
|
`
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args string
|
||||||
|
resultFile string
|
||||||
|
assertFunc string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no args",
|
||||||
|
args: "build kustomization podinfo --kustomization-file ./wrongfile/ --path ./testdata/build-kustomization/podinfo",
|
||||||
|
resultFile: "invalid kustomization file \"./wrongfile/\"",
|
||||||
|
assertFunc: "assertError",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "build podinfo",
|
||||||
|
args: "build kustomization podinfo --kustomization-file ./testdata/build-kustomization/podinfo.yaml --path ./testdata/build-kustomization/podinfo",
|
||||||
|
resultFile: "./testdata/build-kustomization/podinfo-result.yaml",
|
||||||
|
assertFunc: "assertGoldenTemplateFile",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "build podinfo without service",
|
||||||
|
args: "build kustomization podinfo --kustomization-file ./testdata/build-kustomization/podinfo.yaml --path ./testdata/build-kustomization/delete-service",
|
||||||
|
resultFile: "./testdata/build-kustomization/podinfo-without-service-result.yaml",
|
||||||
|
assertFunc: "assertGoldenTemplateFile",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "build deployment and configmap with var substitution",
|
||||||
|
args: "build kustomization podinfo --kustomization-file ./testdata/build-kustomization/podinfo.yaml --path ./testdata/build-kustomization/var-substitution",
|
||||||
|
resultFile: "./testdata/build-kustomization/podinfo-with-var-substitution-result.yaml",
|
||||||
|
assertFunc: "assertGoldenTemplateFile",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "build deployment and configmap with var substitution in dry-run mode",
|
||||||
|
args: "build kustomization podinfo --kustomization-file ./testdata/build-kustomization/podinfo.yaml --path ./testdata/build-kustomization/var-substitution --dry-run",
|
||||||
|
resultFile: "./testdata/build-kustomization/podinfo-with-var-substitution-result.yaml",
|
||||||
|
assertFunc: "assertGoldenTemplateFile",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpl := map[string]string{
|
||||||
|
"fluxns": allocateNamespace("flux-system"),
|
||||||
|
}
|
||||||
|
|
||||||
|
testEnv.CreateObjectFile("./testdata/build-kustomization/podinfo-source.yaml", tmpl, t)
|
||||||
|
|
||||||
|
temp, err := template.New("podinfo").Parse(podinfo)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
err = temp.Execute(&b, tmpl)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.WriteFile("./testdata/build-kustomization/podinfo.yaml", b.Bytes(), 0666)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer os.Remove("./testdata/build-kustomization/podinfo.yaml")
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var assert assertFunc
|
||||||
|
|
||||||
|
switch tt.assertFunc {
|
||||||
|
case "assertGoldenTemplateFile":
|
||||||
|
assert = assertGoldenTemplateFile(tt.resultFile, tmpl)
|
||||||
|
case "assertError":
|
||||||
|
assert = assertError(tt.resultFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := cmdTestCase{
|
||||||
|
args: tt.args + " -n " + tmpl["fluxns"],
|
||||||
|
assert: assert,
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.runTestCmd(t)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,21 +18,20 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Masterminds/semver/v3"
|
"github.com/Masterminds/semver/v3"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
v1 "k8s.io/api/apps/v1"
|
v1 "k8s.io/api/apps/v1"
|
||||||
apimachineryversion "k8s.io/apimachinery/pkg/version"
|
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
|
||||||
"github.com/fluxcd/pkg/version"
|
"github.com/fluxcd/pkg/version"
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/internal/utils"
|
"github.com/fluxcd/flux2/internal/utils"
|
||||||
|
"github.com/fluxcd/flux2/pkg/manifestgen"
|
||||||
"github.com/fluxcd/flux2/pkg/manifestgen/install"
|
"github.com/fluxcd/flux2/pkg/manifestgen/install"
|
||||||
"github.com/fluxcd/flux2/pkg/status"
|
"github.com/fluxcd/flux2/pkg/status"
|
||||||
)
|
)
|
||||||
@@ -54,10 +53,11 @@ type checkFlags struct {
|
|||||||
pre bool
|
pre bool
|
||||||
components []string
|
components []string
|
||||||
extraComponents []string
|
extraComponents []string
|
||||||
|
pollInterval time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
type kubectlVersion struct {
|
var kubernetesConstraints = []string{
|
||||||
ClientVersion *apimachineryversion.Info `json:"clientVersion"`
|
">=1.20.6-0",
|
||||||
}
|
}
|
||||||
|
|
||||||
var checkArgs checkFlags
|
var checkArgs checkFlags
|
||||||
@@ -69,23 +69,18 @@ func init() {
|
|||||||
"list of components, accepts comma-separated values")
|
"list of components, accepts comma-separated values")
|
||||||
checkCmd.Flags().StringSliceVar(&checkArgs.extraComponents, "components-extra", nil,
|
checkCmd.Flags().StringSliceVar(&checkArgs.extraComponents, "components-extra", nil,
|
||||||
"list of components in addition to those supplied or defaulted, accepts comma-separated values")
|
"list of components in addition to those supplied or defaulted, accepts comma-separated values")
|
||||||
|
checkCmd.Flags().DurationVar(&checkArgs.pollInterval, "poll-interval", 5*time.Second,
|
||||||
|
"how often the health checker should poll the cluster for the latest state of the resources.")
|
||||||
rootCmd.AddCommand(checkCmd)
|
rootCmd.AddCommand(checkCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runCheckCmd(cmd *cobra.Command, args []string) error {
|
func runCheckCmd(cmd *cobra.Command, args []string) error {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
logger.Actionf("checking prerequisites")
|
logger.Actionf("checking prerequisites")
|
||||||
checkFailed := false
|
checkFailed := false
|
||||||
|
|
||||||
fluxCheck()
|
fluxCheck()
|
||||||
|
|
||||||
if !kubectlCheck(ctx, ">=1.18.0-0") {
|
if !kubernetesCheck(kubernetesConstraints) {
|
||||||
checkFailed = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if !kubernetesCheck(">=1.16.0-0") {
|
|
||||||
checkFailed = true
|
checkFailed = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,9 +96,17 @@ func runCheckCmd(cmd *cobra.Command, args []string) error {
|
|||||||
if !componentsCheck() {
|
if !componentsCheck() {
|
||||||
checkFailed = true
|
checkFailed = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.Actionf("checking crds")
|
||||||
|
if !crdsCheck() {
|
||||||
|
checkFailed = true
|
||||||
|
}
|
||||||
|
|
||||||
if checkFailed {
|
if checkFailed {
|
||||||
|
logger.Failuref("check failed")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Successf("all checks passed")
|
logger.Successf("all checks passed")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -130,44 +133,8 @@ func fluxCheck() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func kubectlCheck(ctx context.Context, constraint string) bool {
|
func kubernetesCheck(constraints []string) bool {
|
||||||
_, err := exec.LookPath("kubectl")
|
cfg, err := utils.KubeConfig(kubeconfigArgs, kubeclientOptions)
|
||||||
if err != nil {
|
|
||||||
logger.Failuref("kubectl not found")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
kubectlArgs := []string{"version", "--client", "--output", "json"}
|
|
||||||
output, err := utils.ExecKubectlCommand(ctx, utils.ModeCapture, rootArgs.kubeconfig, rootArgs.kubecontext, kubectlArgs...)
|
|
||||||
if err != nil {
|
|
||||||
logger.Failuref("kubectl version can't be determined")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
kv := &kubectlVersion{}
|
|
||||||
if err = json.Unmarshal([]byte(output), kv); err != nil {
|
|
||||||
logger.Failuref("kubectl version output can't be unmarshalled")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
v, err := version.ParseVersion(kv.ClientVersion.GitVersion)
|
|
||||||
if err != nil {
|
|
||||||
logger.Failuref("kubectl version can't be parsed")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
c, _ := semver.NewConstraint(constraint)
|
|
||||||
if !c.Check(v) {
|
|
||||||
logger.Failuref("kubectl version %s < %s", v.Original(), constraint)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Successf("kubectl %s %s", v.String(), constraint)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func kubernetesCheck(constraint string) bool {
|
|
||||||
cfg, err := utils.KubeConfig(rootArgs.kubeconfig, rootArgs.kubecontext)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Failuref("Kubernetes client initialization failed: %s", err.Error())
|
logger.Failuref("Kubernetes client initialization failed: %s", err.Error())
|
||||||
return false
|
return false
|
||||||
@@ -191,13 +158,23 @@ func kubernetesCheck(constraint string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var valid bool
|
||||||
|
var vrange string
|
||||||
|
for _, constraint := range constraints {
|
||||||
c, _ := semver.NewConstraint(constraint)
|
c, _ := semver.NewConstraint(constraint)
|
||||||
if !c.Check(v) {
|
if c.Check(v) {
|
||||||
logger.Failuref("Kubernetes version %s < %s", v.Original(), constraint)
|
valid = true
|
||||||
|
vrange = constraint
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !valid {
|
||||||
|
logger.Failuref("Kubernetes version %s does not match %s", v.Original(), constraints[0])
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Successf("Kubernetes %s %s", v.String(), constraint)
|
logger.Successf("Kubernetes %s %s", v.String(), vrange)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,25 +182,32 @@ func componentsCheck() bool {
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
kubeConfig, err := utils.KubeConfig(rootArgs.kubeconfig, rootArgs.kubecontext)
|
kubeConfig, err := utils.KubeConfig(kubeconfigArgs, kubeclientOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
statusChecker, err := status.NewStatusChecker(kubeConfig, time.Second, rootArgs.timeout, logger)
|
statusChecker, err := status.NewStatusChecker(kubeConfig, checkArgs.pollInterval, rootArgs.timeout, logger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
ok := true
|
ok := true
|
||||||
selector := client.MatchingLabels{"app.kubernetes.io/instance": rootArgs.namespace}
|
selector := client.MatchingLabels{manifestgen.PartOfLabelKey: manifestgen.PartOfLabelValue}
|
||||||
var list v1.DeploymentList
|
var list v1.DeploymentList
|
||||||
if err := kubeClient.List(ctx, &list, client.InNamespace(rootArgs.namespace), selector); err == nil {
|
ns := *kubeconfigArgs.Namespace
|
||||||
|
if err := kubeClient.List(ctx, &list, client.InNamespace(ns), selector); err == nil {
|
||||||
|
if len(list.Items) == 0 {
|
||||||
|
logger.Failuref("no controllers found in the '%s' namespace with the label selector '%s=%s'",
|
||||||
|
ns, manifestgen.PartOfLabelKey, manifestgen.PartOfLabelValue)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
for _, d := range list.Items {
|
for _, d := range list.Items {
|
||||||
if ref, err := buildComponentObjectRefs(d.Name); err == nil {
|
if ref, err := buildComponentObjectRefs(d.Name); err == nil {
|
||||||
if err := statusChecker.Assess(ref...); err != nil {
|
if err := statusChecker.Assess(ref...); err != nil {
|
||||||
@@ -237,3 +221,34 @@ func componentsCheck() bool {
|
|||||||
}
|
}
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func crdsCheck() bool {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
ok := true
|
||||||
|
selector := client.MatchingLabels{manifestgen.PartOfLabelKey: manifestgen.PartOfLabelValue}
|
||||||
|
var list apiextensionsv1.CustomResourceDefinitionList
|
||||||
|
if err := kubeClient.List(ctx, &list, client.InNamespace(*kubeconfigArgs.Namespace), selector); err == nil {
|
||||||
|
if len(list.Items) == 0 {
|
||||||
|
logger.Failuref("no crds found with the label selector '%s=%s'",
|
||||||
|
manifestgen.PartOfLabelKey, manifestgen.PartOfLabelValue)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, crd := range list.Items {
|
||||||
|
if len(crd.Status.StoredVersions) > 0 {
|
||||||
|
logger.Successf(crd.Name + "/" + crd.Status.StoredVersions[0])
|
||||||
|
} else {
|
||||||
|
ok = false
|
||||||
|
logger.Failuref("no stored versions for %s", crd.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|||||||
53
cmd/flux/check_test.go
Normal file
53
cmd/flux/check_test.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
//go:build e2e
|
||||||
|
// +build e2e
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 2021 The Flux authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/fluxcd/flux2/internal/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCheckPre(t *testing.T) {
|
||||||
|
jsonOutput, err := utils.ExecKubectlCommand(context.TODO(), utils.ModeCapture, *kubeconfigArgs.KubeConfig, *kubeconfigArgs.Context, "version", "--output", "json")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error running utils.ExecKubectlCommand: %v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
var versions map[string]interface{}
|
||||||
|
if err := json.Unmarshal([]byte(jsonOutput), &versions); err != nil {
|
||||||
|
t.Fatalf("Error unmarshalling '%s': %v", jsonOutput, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
serverGitVersion := strings.TrimPrefix(
|
||||||
|
versions["serverVersion"].(map[string]interface{})["gitVersion"].(string),
|
||||||
|
"v")
|
||||||
|
|
||||||
|
cmd := cmdTestCase{
|
||||||
|
args: "check --pre",
|
||||||
|
assert: assertGoldenTemplateFile("testdata/check/check_pre.golden", map[string]string{
|
||||||
|
"serverVersion": serverGitVersion,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
cmd.runTestCmd(t)
|
||||||
|
}
|
||||||
@@ -17,7 +17,15 @@ limitations under the License.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/fluxcd/flux2/internal/utils"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/client-go/dynamic"
|
||||||
)
|
)
|
||||||
|
|
||||||
var completionCmd = &cobra.Command{
|
var completionCmd = &cobra.Command{
|
||||||
@@ -29,3 +37,76 @@ var completionCmd = &cobra.Command{
|
|||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(completionCmd)
|
rootCmd.AddCommand(completionCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func contextsCompletionFunc(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
rawConfig, err := kubeconfigArgs.ToRawKubeConfigLoader().RawConfig()
|
||||||
|
if err != nil {
|
||||||
|
return completionError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var comps []string
|
||||||
|
|
||||||
|
for name := range rawConfig.Contexts {
|
||||||
|
if strings.HasPrefix(name, toComplete) {
|
||||||
|
comps = append(comps, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return comps, cobra.ShellCompDirectiveNoFileComp
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceNamesCompletionFunc(gvk schema.GroupVersionKind) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
cfg, err := utils.KubeConfig(kubeconfigArgs, kubeclientOptions)
|
||||||
|
if err != nil {
|
||||||
|
return completionError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mapper, err := kubeconfigArgs.ToRESTMapper()
|
||||||
|
if err != nil {
|
||||||
|
return completionError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mapping, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
|
||||||
|
if err != nil {
|
||||||
|
return completionError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := dynamic.NewForConfig(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return completionError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var dr dynamic.ResourceInterface
|
||||||
|
if mapping.Scope.Name() == meta.RESTScopeNameNamespace {
|
||||||
|
dr = client.Resource(mapping.Resource).Namespace(*kubeconfigArgs.Namespace)
|
||||||
|
} else {
|
||||||
|
dr = client.Resource(mapping.Resource)
|
||||||
|
}
|
||||||
|
|
||||||
|
list, err := dr.List(ctx, metav1.ListOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return completionError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var comps []string
|
||||||
|
|
||||||
|
for _, item := range list.Items {
|
||||||
|
name := item.GetName()
|
||||||
|
|
||||||
|
if strings.HasPrefix(name, toComplete) {
|
||||||
|
comps = append(comps, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return comps, cobra.ShellCompDirectiveNoFileComp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func completionError(err error) ([]string, cobra.ShellCompDirective) {
|
||||||
|
cobra.CompError(err.Error())
|
||||||
|
return nil, cobra.ShellCompDirectiveError
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ limitations under the License.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -27,12 +28,12 @@ var completionZshCmd = &cobra.Command{
|
|||||||
Short: "Generates zsh completion scripts",
|
Short: "Generates zsh completion scripts",
|
||||||
Example: `To load completion run
|
Example: `To load completion run
|
||||||
|
|
||||||
. <(flux completion zsh) && compdef _flux flux
|
. <(flux completion zsh)
|
||||||
|
|
||||||
To configure your zsh shell to load completions for each session add to your zshrc
|
To configure your zsh shell to load completions for each session add to your zshrc
|
||||||
|
|
||||||
# ~/.zshrc or ~/.profile
|
# ~/.zshrc or ~/.profile
|
||||||
command -v flux >/dev/null && . <(flux completion zsh) && compdef _flux flux
|
command -v flux >/dev/null && . <(flux completion zsh)
|
||||||
|
|
||||||
or write a cached file in one of the completion directories in your ${fpath}:
|
or write a cached file in one of the completion directories in your ${fpath}:
|
||||||
|
|
||||||
@@ -43,6 +44,8 @@ mv _flux ~/.oh-my-zsh/completions # oh-my-zsh
|
|||||||
mv _flux ~/.zprezto/modules/completion/external/src/ # zprezto`,
|
mv _flux ~/.zprezto/modules/completion/external/src/ # zprezto`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
rootCmd.GenZshCompletion(os.Stdout)
|
rootCmd.GenZshCompletion(os.Stdout)
|
||||||
|
// Cobra doesn't source zsh completion file, explicitly doing it here
|
||||||
|
fmt.Println("compdef _flux flux")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -51,6 +52,18 @@ func init() {
|
|||||||
createCmd.PersistentFlags().BoolVar(&createArgs.export, "export", false, "export in YAML format to stdout")
|
createCmd.PersistentFlags().BoolVar(&createArgs.export, "export", false, "export in YAML format to stdout")
|
||||||
createCmd.PersistentFlags().StringSliceVar(&createArgs.labels, "label", nil,
|
createCmd.PersistentFlags().StringSliceVar(&createArgs.labels, "label", nil,
|
||||||
"set labels on the resource (can specify multiple labels with commas: label1=value1,label2=value2)")
|
"set labels on the resource (can specify multiple labels with commas: label1=value1,label2=value2)")
|
||||||
|
createCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
|
||||||
|
if len(args) < 1 {
|
||||||
|
return fmt.Errorf("name is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
name := args[0]
|
||||||
|
if !validateObjectName(name) {
|
||||||
|
return fmt.Errorf("name '%s' is invalid, it should adhere to standard defined in RFC 1123, the name can only contain alphanumeric characters or '-'", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
rootCmd.AddCommand(createCmd)
|
rootCmd.AddCommand(createCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,7 +117,7 @@ func (names apiType) upsertAndWait(object upsertWaitable, mutate func() error) e
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext) // NB globals
|
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions) // NB globals
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -150,3 +163,8 @@ func parseLabels() (map[string]string, error) {
|
|||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateObjectName(name string) bool {
|
||||||
|
r := regexp.MustCompile("^[a-z0-9]([a-z0-9\\-]){0,61}[a-z0-9]$")
|
||||||
|
return r.MatchString(name)
|
||||||
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/util/wait"
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
|
||||||
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
|
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2"
|
||||||
"github.com/fluxcd/pkg/apis/meta"
|
"github.com/fluxcd/pkg/apis/meta"
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/internal/utils"
|
"github.com/fluxcd/flux2/internal/utils"
|
||||||
@@ -63,9 +63,6 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func createAlertCmdRun(cmd *cobra.Command, args []string) error {
|
func createAlertCmdRun(cmd *cobra.Command, args []string) error {
|
||||||
if len(args) < 1 {
|
|
||||||
return fmt.Errorf("Alert name is required")
|
|
||||||
}
|
|
||||||
name := args[0]
|
name := args[0]
|
||||||
|
|
||||||
if alertArgs.providerRef == "" {
|
if alertArgs.providerRef == "" {
|
||||||
@@ -102,7 +99,7 @@ func createAlertCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
alert := notificationv1.Alert{
|
alert := notificationv1.Alert{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: name,
|
Name: name,
|
||||||
Namespace: rootArgs.namespace,
|
Namespace: *kubeconfigArgs.Namespace,
|
||||||
Labels: sourceLabels,
|
Labels: sourceLabels,
|
||||||
},
|
},
|
||||||
Spec: notificationv1.AlertSpec{
|
Spec: notificationv1.AlertSpec{
|
||||||
@@ -122,7 +119,7 @@ func createAlertCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/util/wait"
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
|
||||||
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
|
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2"
|
||||||
"github.com/fluxcd/pkg/apis/meta"
|
"github.com/fluxcd/pkg/apis/meta"
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/internal/utils"
|
"github.com/fluxcd/flux2/internal/utils"
|
||||||
@@ -73,9 +73,6 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func createAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
|
func createAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
|
||||||
if len(args) < 1 {
|
|
||||||
return fmt.Errorf("Provider name is required")
|
|
||||||
}
|
|
||||||
name := args[0]
|
name := args[0]
|
||||||
|
|
||||||
if alertProviderArgs.alertType == "" {
|
if alertProviderArgs.alertType == "" {
|
||||||
@@ -94,7 +91,7 @@ func createAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
provider := notificationv1.Provider{
|
provider := notificationv1.Provider{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: name,
|
Name: name,
|
||||||
Namespace: rootArgs.namespace,
|
Namespace: *kubeconfigArgs.Namespace,
|
||||||
Labels: sourceLabels,
|
Labels: sourceLabels,
|
||||||
},
|
},
|
||||||
Spec: notificationv1.ProviderSpec{
|
Spec: notificationv1.ProviderSpec{
|
||||||
@@ -118,7 +115,7 @@ func createAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,9 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/internal/flags"
|
"github.com/fluxcd/flux2/internal/flags"
|
||||||
"github.com/fluxcd/flux2/internal/utils"
|
"github.com/fluxcd/flux2/internal/utils"
|
||||||
@@ -116,13 +118,18 @@ type helmReleaseFlags struct {
|
|||||||
targetNamespace string
|
targetNamespace string
|
||||||
createNamespace bool
|
createNamespace bool
|
||||||
valuesFiles []string
|
valuesFiles []string
|
||||||
valuesFrom flags.HelmReleaseValuesFrom
|
valuesFrom []string
|
||||||
saName string
|
saName string
|
||||||
crds flags.CRDsPolicy
|
crds flags.CRDsPolicy
|
||||||
|
reconcileStrategy string
|
||||||
|
chartInterval time.Duration
|
||||||
|
kubeConfigSecretRef string
|
||||||
}
|
}
|
||||||
|
|
||||||
var helmReleaseArgs helmReleaseFlags
|
var helmReleaseArgs helmReleaseFlags
|
||||||
|
|
||||||
|
var supportedHelmReleaseValuesFromKinds = []string{"Secret", "ConfigMap"}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
createHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.name, "release-name", "", "name used for the Helm release, defaults to a composition of '[<target-namespace>-]<HelmRelease-name>'")
|
createHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.name, "release-name", "", "name used for the Helm release, defaults to a composition of '[<target-namespace>-]<HelmRelease-name>'")
|
||||||
createHelmReleaseCmd.Flags().Var(&helmReleaseArgs.source, "source", helmReleaseArgs.source.Description())
|
createHelmReleaseCmd.Flags().Var(&helmReleaseArgs.source, "source", helmReleaseArgs.source.Description())
|
||||||
@@ -132,16 +139,16 @@ func init() {
|
|||||||
createHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.targetNamespace, "target-namespace", "", "namespace to install this release, defaults to the HelmRelease namespace")
|
createHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.targetNamespace, "target-namespace", "", "namespace to install this release, defaults to the HelmRelease namespace")
|
||||||
createHelmReleaseCmd.Flags().BoolVar(&helmReleaseArgs.createNamespace, "create-target-namespace", false, "create the target namespace if it does not exist")
|
createHelmReleaseCmd.Flags().BoolVar(&helmReleaseArgs.createNamespace, "create-target-namespace", false, "create the target namespace if it does not exist")
|
||||||
createHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.saName, "service-account", "", "the name of the service account to impersonate when reconciling this HelmRelease")
|
createHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.saName, "service-account", "", "the name of the service account to impersonate when reconciling this HelmRelease")
|
||||||
|
createHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.reconcileStrategy, "reconcile-strategy", "ChartVersion", "the reconcile strategy for helm chart created by the helm release(accepted values: Revision and ChartRevision)")
|
||||||
|
createHelmReleaseCmd.Flags().DurationVarP(&helmReleaseArgs.chartInterval, "chart-interval", "", 0, "the interval of which to check for new chart versions")
|
||||||
createHelmReleaseCmd.Flags().StringSliceVar(&helmReleaseArgs.valuesFiles, "values", nil, "local path to values.yaml files, also accepts comma-separated values")
|
createHelmReleaseCmd.Flags().StringSliceVar(&helmReleaseArgs.valuesFiles, "values", nil, "local path to values.yaml files, also accepts comma-separated values")
|
||||||
createHelmReleaseCmd.Flags().Var(&helmReleaseArgs.valuesFrom, "values-from", helmReleaseArgs.valuesFrom.Description())
|
createHelmReleaseCmd.Flags().StringSliceVar(&helmReleaseArgs.valuesFrom, "values-from", nil, "a Kubernetes object reference that contains the values.yaml data key in the format '<kind>/<name>', where kind must be one of: (Secret,ConfigMap)")
|
||||||
createHelmReleaseCmd.Flags().Var(&helmReleaseArgs.crds, "crds", helmReleaseArgs.crds.Description())
|
createHelmReleaseCmd.Flags().Var(&helmReleaseArgs.crds, "crds", helmReleaseArgs.crds.Description())
|
||||||
|
createHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.kubeConfigSecretRef, "kubeconfig-secret-ref", "", "the name of the Kubernetes Secret that contains a key with the kubeconfig file for connecting to a remote cluster")
|
||||||
createCmd.AddCommand(createHelmReleaseCmd)
|
createCmd.AddCommand(createHelmReleaseCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
|
func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
|
||||||
if len(args) < 1 {
|
|
||||||
return fmt.Errorf("HelmRelease name is required")
|
|
||||||
}
|
|
||||||
name := args[0]
|
name := args[0]
|
||||||
|
|
||||||
if helmReleaseArgs.chart == "" {
|
if helmReleaseArgs.chart == "" {
|
||||||
@@ -157,10 +164,15 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
logger.Generatef("generating HelmRelease")
|
logger.Generatef("generating HelmRelease")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !validateStrategy(helmReleaseArgs.reconcileStrategy) {
|
||||||
|
return fmt.Errorf("'%s' is an invalid reconcile strategy(valid: Revision, ChartVersion)",
|
||||||
|
helmReleaseArgs.reconcileStrategy)
|
||||||
|
}
|
||||||
|
|
||||||
helmRelease := helmv2.HelmRelease{
|
helmRelease := helmv2.HelmRelease{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: name,
|
Name: name,
|
||||||
Namespace: rootArgs.namespace,
|
Namespace: *kubeconfigArgs.Namespace,
|
||||||
Labels: sourceLabels,
|
Labels: sourceLabels,
|
||||||
},
|
},
|
||||||
Spec: helmv2.HelmReleaseSpec{
|
Spec: helmv2.HelmReleaseSpec{
|
||||||
@@ -180,20 +192,44 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
Name: helmReleaseArgs.source.Name,
|
Name: helmReleaseArgs.source.Name,
|
||||||
Namespace: helmReleaseArgs.source.Namespace,
|
Namespace: helmReleaseArgs.source.Namespace,
|
||||||
},
|
},
|
||||||
|
ReconcileStrategy: helmReleaseArgs.reconcileStrategy,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Install: &helmv2.Install{
|
|
||||||
CreateNamespace: helmReleaseArgs.createNamespace,
|
|
||||||
},
|
|
||||||
Suspend: false,
|
Suspend: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if helmReleaseArgs.kubeConfigSecretRef != "" {
|
||||||
|
helmRelease.Spec.KubeConfig = &helmv2.KubeConfig{
|
||||||
|
SecretRef: meta.SecretKeyReference{
|
||||||
|
Name: helmReleaseArgs.kubeConfigSecretRef,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if helmReleaseArgs.chartInterval != 0 {
|
||||||
|
helmRelease.Spec.Chart.Spec.Interval = &metav1.Duration{
|
||||||
|
Duration: helmReleaseArgs.chartInterval,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if helmReleaseArgs.createNamespace {
|
||||||
|
if helmRelease.Spec.Install == nil {
|
||||||
|
helmRelease.Spec.Install = &helmv2.Install{}
|
||||||
|
}
|
||||||
|
|
||||||
|
helmRelease.Spec.Install.CreateNamespace = helmReleaseArgs.createNamespace
|
||||||
|
}
|
||||||
|
|
||||||
if helmReleaseArgs.saName != "" {
|
if helmReleaseArgs.saName != "" {
|
||||||
helmRelease.Spec.ServiceAccountName = helmReleaseArgs.saName
|
helmRelease.Spec.ServiceAccountName = helmReleaseArgs.saName
|
||||||
}
|
}
|
||||||
|
|
||||||
if helmReleaseArgs.crds != "" {
|
if helmReleaseArgs.crds != "" {
|
||||||
|
if helmRelease.Spec.Install == nil {
|
||||||
|
helmRelease.Spec.Install = &helmv2.Install{}
|
||||||
|
}
|
||||||
|
|
||||||
helmRelease.Spec.Install.CRDs = helmv2.Create
|
helmRelease.Spec.Install.CRDs = helmv2.Create
|
||||||
helmRelease.Spec.Upgrade = &helmv2.Upgrade{CRDs: helmv2.CRDsPolicy(helmReleaseArgs.crds.String())}
|
helmRelease.Spec.Upgrade = &helmv2.Upgrade{CRDs: helmv2.CRDsPolicy(helmReleaseArgs.crds.String())}
|
||||||
}
|
}
|
||||||
@@ -201,7 +237,7 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
if len(helmReleaseArgs.valuesFiles) > 0 {
|
if len(helmReleaseArgs.valuesFiles) > 0 {
|
||||||
valuesMap := make(map[string]interface{})
|
valuesMap := make(map[string]interface{})
|
||||||
for _, v := range helmReleaseArgs.valuesFiles {
|
for _, v := range helmReleaseArgs.valuesFiles {
|
||||||
data, err := ioutil.ReadFile(v)
|
data, err := os.ReadFile(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("reading values from %s failed: %w", v, err)
|
return fmt.Errorf("reading values from %s failed: %w", v, err)
|
||||||
}
|
}
|
||||||
@@ -227,11 +263,25 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
helmRelease.Spec.Values = &apiextensionsv1.JSON{Raw: jsonRaw}
|
helmRelease.Spec.Values = &apiextensionsv1.JSON{Raw: jsonRaw}
|
||||||
}
|
}
|
||||||
|
|
||||||
if helmReleaseArgs.valuesFrom.String() != "" {
|
if len(helmReleaseArgs.valuesFrom) != 0 {
|
||||||
helmRelease.Spec.ValuesFrom = []helmv2.ValuesReference{{
|
values := []helmv2.ValuesReference{}
|
||||||
Kind: helmReleaseArgs.valuesFrom.Kind,
|
for _, value := range helmReleaseArgs.valuesFrom {
|
||||||
Name: helmReleaseArgs.valuesFrom.Name,
|
sourceKind, sourceName := utils.ParseObjectKindName(value)
|
||||||
}}
|
if sourceKind == "" {
|
||||||
|
return fmt.Errorf("invalid Kubernetes object reference '%s', must be in format <kind>/<name>", value)
|
||||||
|
}
|
||||||
|
cleanSourceKind, ok := utils.ContainsEqualFoldItemString(supportedHelmReleaseValuesFromKinds, sourceKind)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("reference kind '%s' is not supported, must be one of: %s",
|
||||||
|
sourceKind, strings.Join(supportedHelmReleaseValuesFromKinds, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
values = append(values, helmv2.ValuesReference{
|
||||||
|
Name: sourceName,
|
||||||
|
Kind: cleanSourceKind,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
helmRelease.Spec.ValuesFrom = values
|
||||||
}
|
}
|
||||||
|
|
||||||
if createArgs.export {
|
if createArgs.export {
|
||||||
@@ -241,7 +291,7 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -310,3 +360,15 @@ func isHelmReleaseReady(ctx context.Context, kubeClient client.Client,
|
|||||||
return apimeta.IsStatusConditionTrue(helmRelease.Status.Conditions, meta.ReadyCondition), nil
|
return apimeta.IsStatusConditionTrue(helmRelease.Status.Conditions, meta.ReadyCondition), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateStrategy(input string) bool {
|
||||||
|
allowedStrategy := []string{"Revision", "ChartVersion"}
|
||||||
|
|
||||||
|
for _, strategy := range allowedStrategy {
|
||||||
|
if strategy == input {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import (
|
|||||||
|
|
||||||
"github.com/fluxcd/pkg/apis/meta"
|
"github.com/fluxcd/pkg/apis/meta"
|
||||||
|
|
||||||
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha2"
|
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta1"
|
||||||
)
|
)
|
||||||
|
|
||||||
var createImagePolicyCmd = &cobra.Command{
|
var createImagePolicyCmd = &cobra.Command{
|
||||||
@@ -84,9 +84,6 @@ func (obj imagePolicyAdapter) getObservedGeneration() int64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func createImagePolicyRun(cmd *cobra.Command, args []string) error {
|
func createImagePolicyRun(cmd *cobra.Command, args []string) error {
|
||||||
if len(args) < 1 {
|
|
||||||
return fmt.Errorf("ImagePolicy name is required")
|
|
||||||
}
|
|
||||||
objectName := args[0]
|
objectName := args[0]
|
||||||
|
|
||||||
if imagePolicyArgs.imageRef == "" {
|
if imagePolicyArgs.imageRef == "" {
|
||||||
@@ -101,11 +98,11 @@ func createImagePolicyRun(cmd *cobra.Command, args []string) error {
|
|||||||
var policy = imagev1.ImagePolicy{
|
var policy = imagev1.ImagePolicy{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: objectName,
|
Name: objectName,
|
||||||
Namespace: rootArgs.namespace,
|
Namespace: *kubeconfigArgs.Namespace,
|
||||||
Labels: labels,
|
Labels: labels,
|
||||||
},
|
},
|
||||||
Spec: imagev1.ImagePolicySpec{
|
Spec: imagev1.ImagePolicySpec{
|
||||||
ImageRepositoryRef: meta.LocalObjectReference{
|
ImageRepositoryRef: meta.NamespacedObjectReference{
|
||||||
Name: imagePolicyArgs.imageRef,
|
Name: imagePolicyArgs.imageRef,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import (
|
|||||||
|
|
||||||
"github.com/fluxcd/pkg/apis/meta"
|
"github.com/fluxcd/pkg/apis/meta"
|
||||||
|
|
||||||
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha2"
|
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta1"
|
||||||
)
|
)
|
||||||
|
|
||||||
var createImageRepositoryCmd = &cobra.Command{
|
var createImageRepositoryCmd = &cobra.Command{
|
||||||
@@ -83,9 +83,6 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func createImageRepositoryRun(cmd *cobra.Command, args []string) error {
|
func createImageRepositoryRun(cmd *cobra.Command, args []string) error {
|
||||||
if len(args) < 1 {
|
|
||||||
return fmt.Errorf("ImageRepository name is required")
|
|
||||||
}
|
|
||||||
objectName := args[0]
|
objectName := args[0]
|
||||||
|
|
||||||
if imageRepoArgs.image == "" {
|
if imageRepoArgs.image == "" {
|
||||||
@@ -104,7 +101,7 @@ func createImageRepositoryRun(cmd *cobra.Command, args []string) error {
|
|||||||
var repo = imagev1.ImageRepository{
|
var repo = imagev1.ImageRepository{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: objectName,
|
Name: objectName,
|
||||||
Namespace: rootArgs.namespace,
|
Namespace: *kubeconfigArgs.Namespace,
|
||||||
Labels: labels,
|
Labels: labels,
|
||||||
},
|
},
|
||||||
Spec: imagev1.ImageRepositorySpec{
|
Spec: imagev1.ImageRepositorySpec{
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
autov1 "github.com/fluxcd/image-automation-controller/api/v1alpha2"
|
autov1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var createImageUpdateCmd = &cobra.Command{
|
var createImageUpdateCmd = &cobra.Command{
|
||||||
@@ -49,12 +49,26 @@ mentioned in YAMLs in a git repository.`,
|
|||||||
--push-branch=image-updates \
|
--push-branch=image-updates \
|
||||||
--author-name=flux \
|
--author-name=flux \
|
||||||
--author-email=flux@example.com \
|
--author-email=flux@example.com \
|
||||||
--commit-template="{{range .Updated.Images}}{{println .}}{{end}}"`,
|
--commit-template="{{range .Updated.Images}}{{println .}}{{end}}"
|
||||||
|
|
||||||
|
# Configure image updates for a Git repository in a different namespace
|
||||||
|
flux create image update apps \
|
||||||
|
--namespace=apps \
|
||||||
|
--git-repo-ref=flux-system \
|
||||||
|
--git-repo-namespace=flux-system \
|
||||||
|
--git-repo-path="./clusters/my-cluster" \
|
||||||
|
--checkout-branch=main \
|
||||||
|
--push-branch=image-updates \
|
||||||
|
--author-name=flux \
|
||||||
|
--author-email=flux@example.com \
|
||||||
|
--commit-template="{{range .Updated.Images}}{{println .}}{{end}}"
|
||||||
|
`,
|
||||||
RunE: createImageUpdateRun,
|
RunE: createImageUpdateRun,
|
||||||
}
|
}
|
||||||
|
|
||||||
type imageUpdateFlags struct {
|
type imageUpdateFlags struct {
|
||||||
gitRepoRef string
|
gitRepoName string
|
||||||
|
gitRepoNamespace string
|
||||||
gitRepoPath string
|
gitRepoPath string
|
||||||
checkoutBranch string
|
checkoutBranch string
|
||||||
pushBranch string
|
pushBranch string
|
||||||
@@ -67,7 +81,8 @@ var imageUpdateArgs = imageUpdateFlags{}
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
flags := createImageUpdateCmd.Flags()
|
flags := createImageUpdateCmd.Flags()
|
||||||
flags.StringVar(&imageUpdateArgs.gitRepoRef, "git-repo-ref", "", "the name of a GitRepository resource with details of the upstream Git repository")
|
flags.StringVar(&imageUpdateArgs.gitRepoName, "git-repo-ref", "", "the name of a GitRepository resource with details of the upstream Git repository")
|
||||||
|
flags.StringVar(&imageUpdateArgs.gitRepoNamespace, "git-repo-namespace", "", "the namespace of the GitRepository resource, defaults to the ImageUpdateAutomation namespace")
|
||||||
flags.StringVar(&imageUpdateArgs.gitRepoPath, "git-repo-path", "", "path to the directory containing the manifests to be updated, defaults to the repository root")
|
flags.StringVar(&imageUpdateArgs.gitRepoPath, "git-repo-path", "", "path to the directory containing the manifests to be updated, defaults to the repository root")
|
||||||
flags.StringVar(&imageUpdateArgs.checkoutBranch, "checkout-branch", "", "the branch to checkout")
|
flags.StringVar(&imageUpdateArgs.checkoutBranch, "checkout-branch", "", "the branch to checkout")
|
||||||
flags.StringVar(&imageUpdateArgs.pushBranch, "push-branch", "", "the branch to push commits to, defaults to the checkout branch if not specified")
|
flags.StringVar(&imageUpdateArgs.pushBranch, "push-branch", "", "the branch to push commits to, defaults to the checkout branch if not specified")
|
||||||
@@ -79,12 +94,9 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func createImageUpdateRun(cmd *cobra.Command, args []string) error {
|
func createImageUpdateRun(cmd *cobra.Command, args []string) error {
|
||||||
if len(args) < 1 {
|
|
||||||
return fmt.Errorf("ImageUpdateAutomation name is required")
|
|
||||||
}
|
|
||||||
objectName := args[0]
|
objectName := args[0]
|
||||||
|
|
||||||
if imageUpdateArgs.gitRepoRef == "" {
|
if imageUpdateArgs.gitRepoName == "" {
|
||||||
return fmt.Errorf("a reference to a GitRepository is required (--git-repo-ref)")
|
return fmt.Errorf("a reference to a GitRepository is required (--git-repo-ref)")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,13 +120,14 @@ func createImageUpdateRun(cmd *cobra.Command, args []string) error {
|
|||||||
var update = autov1.ImageUpdateAutomation{
|
var update = autov1.ImageUpdateAutomation{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: objectName,
|
Name: objectName,
|
||||||
Namespace: rootArgs.namespace,
|
Namespace: *kubeconfigArgs.Namespace,
|
||||||
Labels: labels,
|
Labels: labels,
|
||||||
},
|
},
|
||||||
Spec: autov1.ImageUpdateAutomationSpec{
|
Spec: autov1.ImageUpdateAutomationSpec{
|
||||||
SourceRef: autov1.SourceReference{
|
SourceRef: autov1.CrossNamespaceSourceReference{
|
||||||
Kind: sourcev1.GitRepositoryKind,
|
Kind: sourcev1.GitRepositoryKind,
|
||||||
Name: imageUpdateArgs.gitRepoRef,
|
Name: imageUpdateArgs.gitRepoName,
|
||||||
|
Namespace: imageUpdateArgs.gitRepoNamespace,
|
||||||
},
|
},
|
||||||
|
|
||||||
GitSpec: &autov1.GitSpec{
|
GitSpec: &autov1.GitSpec{
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import (
|
|||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
|
||||||
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
|
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
|
||||||
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
|
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
|
||||||
"github.com/fluxcd/pkg/apis/meta"
|
"github.com/fluxcd/pkg/apis/meta"
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/internal/flags"
|
"github.com/fluxcd/flux2/internal/flags"
|
||||||
@@ -42,35 +42,38 @@ var createKsCmd = &cobra.Command{
|
|||||||
Use: "kustomization [name]",
|
Use: "kustomization [name]",
|
||||||
Aliases: []string{"ks"},
|
Aliases: []string{"ks"},
|
||||||
Short: "Create or update a Kustomization resource",
|
Short: "Create or update a Kustomization resource",
|
||||||
Long: "The kustomization source create command generates a Kustomize resource for a given source.",
|
Long: "The create command generates a Kustomization resource for a given source.",
|
||||||
Example: ` # Create a Kustomization resource from a source at a given path
|
Example: ` # Create a Kustomization resource from a source at a given path
|
||||||
flux create kustomization contour \
|
flux create kustomization kyverno \
|
||||||
--source=GitRepository/contour \
|
--source=GitRepository/kyverno \
|
||||||
--path="./examples/contour/" \
|
--path="./config/release" \
|
||||||
--prune=true \
|
--prune=true \
|
||||||
--interval=10m \
|
--interval=60m \
|
||||||
--validation=client \
|
--wait=true \
|
||||||
--health-check="Deployment/contour.projectcontour" \
|
|
||||||
--health-check="DaemonSet/envoy.projectcontour" \
|
|
||||||
--health-check-timeout=3m
|
--health-check-timeout=3m
|
||||||
|
|
||||||
# Create a Kustomization resource that depends on the previous one
|
# Create a Kustomization resource that depends on the previous one
|
||||||
flux create kustomization webapp \
|
flux create kustomization kyverno-policies \
|
||||||
--depends-on=contour \
|
--depends-on=kyverno \
|
||||||
--source=GitRepository/webapp \
|
--source=GitRepository/kyverno-policies \
|
||||||
--path="./deploy/overlays/dev" \
|
--path="./policies/flux" \
|
||||||
--prune=true \
|
--prune=true \
|
||||||
--interval=5m \
|
--interval=5m
|
||||||
--validation=client
|
|
||||||
|
|
||||||
# Create a Kustomization using a source from a different namespace
|
# Create a Kustomization using a source from a different namespace
|
||||||
flux create kustomization podinfo \
|
flux create kustomization podinfo \
|
||||||
--namespace=default \
|
--namespace=default \
|
||||||
--source=GitRepository/podinfo.flux-system \
|
--source=GitRepository/podinfo.flux-system \
|
||||||
--path="./deploy/overlays/dev" \
|
--path="./kustomize" \
|
||||||
--prune=true \
|
--prune=true \
|
||||||
--interval=5m \
|
--interval=5m
|
||||||
--validation=client
|
|
||||||
|
# Create a Kustomization resource that references an OCIRepository
|
||||||
|
flux create kustomization podinfo \
|
||||||
|
--source=OCIRepository/podinfo \
|
||||||
|
--target-namespace=default \
|
||||||
|
--prune=true \
|
||||||
|
--interval=5m
|
||||||
|
|
||||||
# Create a Kustomization resource that references a Bucket
|
# Create a Kustomization resource that references a Bucket
|
||||||
flux create kustomization secrets \
|
flux create kustomization secrets \
|
||||||
@@ -92,6 +95,8 @@ type kustomizationFlags struct {
|
|||||||
decryptionProvider flags.DecryptionProvider
|
decryptionProvider flags.DecryptionProvider
|
||||||
decryptionSecret string
|
decryptionSecret string
|
||||||
targetNamespace string
|
targetNamespace string
|
||||||
|
wait bool
|
||||||
|
kubeConfigSecretRef string
|
||||||
}
|
}
|
||||||
|
|
||||||
var kustomizationArgs = NewKustomizationFlags()
|
var kustomizationArgs = NewKustomizationFlags()
|
||||||
@@ -100,6 +105,7 @@ func init() {
|
|||||||
createKsCmd.Flags().Var(&kustomizationArgs.source, "source", kustomizationArgs.source.Description())
|
createKsCmd.Flags().Var(&kustomizationArgs.source, "source", kustomizationArgs.source.Description())
|
||||||
createKsCmd.Flags().Var(&kustomizationArgs.path, "path", "path to the directory containing a kustomization.yaml file")
|
createKsCmd.Flags().Var(&kustomizationArgs.path, "path", "path to the directory containing a kustomization.yaml file")
|
||||||
createKsCmd.Flags().BoolVar(&kustomizationArgs.prune, "prune", false, "enable garbage collection")
|
createKsCmd.Flags().BoolVar(&kustomizationArgs.prune, "prune", false, "enable garbage collection")
|
||||||
|
createKsCmd.Flags().BoolVar(&kustomizationArgs.wait, "wait", false, "enable health checking of all the applied resources")
|
||||||
createKsCmd.Flags().StringSliceVar(&kustomizationArgs.healthCheck, "health-check", nil, "workload to be included in the health assessment, in the format '<kind>/<name>.<namespace>'")
|
createKsCmd.Flags().StringSliceVar(&kustomizationArgs.healthCheck, "health-check", nil, "workload to be included in the health assessment, in the format '<kind>/<name>.<namespace>'")
|
||||||
createKsCmd.Flags().DurationVar(&kustomizationArgs.healthTimeout, "health-check-timeout", 2*time.Minute, "timeout of health checking operations")
|
createKsCmd.Flags().DurationVar(&kustomizationArgs.healthTimeout, "health-check-timeout", 2*time.Minute, "timeout of health checking operations")
|
||||||
createKsCmd.Flags().StringVar(&kustomizationArgs.validation, "validation", "", "validate the manifests before applying them on the cluster, can be 'client' or 'server'")
|
createKsCmd.Flags().StringVar(&kustomizationArgs.validation, "validation", "", "validate the manifests before applying them on the cluster, can be 'client' or 'server'")
|
||||||
@@ -108,6 +114,9 @@ func init() {
|
|||||||
createKsCmd.Flags().Var(&kustomizationArgs.decryptionProvider, "decryption-provider", kustomizationArgs.decryptionProvider.Description())
|
createKsCmd.Flags().Var(&kustomizationArgs.decryptionProvider, "decryption-provider", kustomizationArgs.decryptionProvider.Description())
|
||||||
createKsCmd.Flags().StringVar(&kustomizationArgs.decryptionSecret, "decryption-secret", "", "set the Kubernetes secret name that contains the OpenPGP private keys used for sops decryption")
|
createKsCmd.Flags().StringVar(&kustomizationArgs.decryptionSecret, "decryption-secret", "", "set the Kubernetes secret name that contains the OpenPGP private keys used for sops decryption")
|
||||||
createKsCmd.Flags().StringVar(&kustomizationArgs.targetNamespace, "target-namespace", "", "overrides the namespace of all Kustomization objects reconciled by this Kustomization")
|
createKsCmd.Flags().StringVar(&kustomizationArgs.targetNamespace, "target-namespace", "", "overrides the namespace of all Kustomization objects reconciled by this Kustomization")
|
||||||
|
createKsCmd.Flags().StringVar(&kustomizationArgs.kubeConfigSecretRef, "kubeconfig-secret-ref", "", "the name of the Kubernetes Secret that contains a key with the kubeconfig file for connecting to a remote cluster")
|
||||||
|
createKsCmd.Flags().MarkDeprecated("validation", "this arg is no longer used, all resources are validated using server-side apply dry-run")
|
||||||
|
|
||||||
createCmd.AddCommand(createKsCmd)
|
createCmd.AddCommand(createKsCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,9 +127,6 @@ func NewKustomizationFlags() kustomizationFlags {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func createKsCmdRun(cmd *cobra.Command, args []string) error {
|
func createKsCmdRun(cmd *cobra.Command, args []string) error {
|
||||||
if len(args) < 1 {
|
|
||||||
return fmt.Errorf("Kustomization name is required")
|
|
||||||
}
|
|
||||||
name := args[0]
|
name := args[0]
|
||||||
|
|
||||||
if kustomizationArgs.path == "" {
|
if kustomizationArgs.path == "" {
|
||||||
@@ -142,7 +148,7 @@ func createKsCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
kustomization := kustomizev1.Kustomization{
|
kustomization := kustomizev1.Kustomization{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: name,
|
Name: name,
|
||||||
Namespace: rootArgs.namespace,
|
Namespace: *kubeconfigArgs.Namespace,
|
||||||
Labels: kslabels,
|
Labels: kslabels,
|
||||||
},
|
},
|
||||||
Spec: kustomizev1.KustomizationSpec{
|
Spec: kustomizev1.KustomizationSpec{
|
||||||
@@ -158,12 +164,19 @@ func createKsCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
Namespace: kustomizationArgs.source.Namespace,
|
Namespace: kustomizationArgs.source.Namespace,
|
||||||
},
|
},
|
||||||
Suspend: false,
|
Suspend: false,
|
||||||
Validation: kustomizationArgs.validation,
|
|
||||||
TargetNamespace: kustomizationArgs.targetNamespace,
|
TargetNamespace: kustomizationArgs.targetNamespace,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(kustomizationArgs.healthCheck) > 0 {
|
if kustomizationArgs.kubeConfigSecretRef != "" {
|
||||||
|
kustomization.Spec.KubeConfig = &meta.KubeConfigReference{
|
||||||
|
SecretRef: meta.SecretKeyReference{
|
||||||
|
Name: kustomizationArgs.kubeConfigSecretRef,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(kustomizationArgs.healthCheck) > 0 && !kustomizationArgs.wait {
|
||||||
healthChecks := make([]meta.NamespacedObjectKindReference, 0)
|
healthChecks := make([]meta.NamespacedObjectKindReference, 0)
|
||||||
for _, w := range kustomizationArgs.healthCheck {
|
for _, w := range kustomizationArgs.healthCheck {
|
||||||
kindObj := strings.Split(w, "/")
|
kindObj := strings.Split(w, "/")
|
||||||
@@ -204,6 +217,13 @@ func createKsCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if kustomizationArgs.wait {
|
||||||
|
kustomization.Spec.Wait = true
|
||||||
|
kustomization.Spec.Timeout = &metav1.Duration{
|
||||||
|
Duration: kustomizationArgs.healthTimeout,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if kustomizationArgs.saName != "" {
|
if kustomizationArgs.saName != "" {
|
||||||
kustomization.Spec.ServiceAccountName = kustomizationArgs.saName
|
kustomization.Spec.ServiceAccountName = kustomizationArgs.saName
|
||||||
}
|
}
|
||||||
@@ -225,7 +245,7 @@ func createKsCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/util/wait"
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
|
||||||
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
|
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2"
|
||||||
"github.com/fluxcd/pkg/apis/meta"
|
"github.com/fluxcd/pkg/apis/meta"
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/internal/utils"
|
"github.com/fluxcd/flux2/internal/utils"
|
||||||
@@ -67,9 +67,6 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func createReceiverCmdRun(cmd *cobra.Command, args []string) error {
|
func createReceiverCmdRun(cmd *cobra.Command, args []string) error {
|
||||||
if len(args) < 1 {
|
|
||||||
return fmt.Errorf("Receiver name is required")
|
|
||||||
}
|
|
||||||
name := args[0]
|
name := args[0]
|
||||||
|
|
||||||
if receiverArgs.receiverType == "" {
|
if receiverArgs.receiverType == "" {
|
||||||
@@ -109,7 +106,7 @@ func createReceiverCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
receiver := notificationv1.Receiver{
|
receiver := notificationv1.Receiver{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: name,
|
Name: name,
|
||||||
Namespace: rootArgs.namespace,
|
Namespace: *kubeconfigArgs.Namespace,
|
||||||
Labels: sourceLabels,
|
Labels: sourceLabels,
|
||||||
},
|
},
|
||||||
Spec: notificationv1.ReceiverSpec{
|
Spec: notificationv1.ReceiverSpec{
|
||||||
@@ -130,7 +127,7 @@ func createReceiverCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import (
|
|||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
@@ -105,16 +106,13 @@ func init() {
|
|||||||
|
|
||||||
func NewSecretGitFlags() secretGitFlags {
|
func NewSecretGitFlags() secretGitFlags {
|
||||||
return secretGitFlags{
|
return secretGitFlags{
|
||||||
keyAlgorithm: flags.PublicKeyAlgorithm(sourcesecret.RSAPrivateKeyAlgorithm),
|
keyAlgorithm: flags.PublicKeyAlgorithm(sourcesecret.ECDSAPrivateKeyAlgorithm),
|
||||||
rsaBits: 2048,
|
rsaBits: 2048,
|
||||||
ecdsaCurve: flags.ECDSACurve{Curve: elliptic.P384()},
|
ecdsaCurve: flags.ECDSACurve{Curve: elliptic.P384()},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createSecretGitCmdRun(cmd *cobra.Command, args []string) error {
|
func createSecretGitCmdRun(cmd *cobra.Command, args []string) error {
|
||||||
if len(args) < 1 {
|
|
||||||
return fmt.Errorf("secret name is required")
|
|
||||||
}
|
|
||||||
name := args[0]
|
name := args[0]
|
||||||
if secretGitArgs.url == "" {
|
if secretGitArgs.url == "" {
|
||||||
return fmt.Errorf("url is required")
|
return fmt.Errorf("url is required")
|
||||||
@@ -132,14 +130,18 @@ func createSecretGitCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
opts := sourcesecret.Options{
|
opts := sourcesecret.Options{
|
||||||
Name: name,
|
Name: name,
|
||||||
Namespace: rootArgs.namespace,
|
Namespace: *kubeconfigArgs.Namespace,
|
||||||
Labels: labels,
|
Labels: labels,
|
||||||
ManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile,
|
ManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile,
|
||||||
}
|
}
|
||||||
switch u.Scheme {
|
switch u.Scheme {
|
||||||
case "ssh":
|
case "ssh":
|
||||||
|
keypair, err := sourcesecret.LoadKeyPairFromPath(secretGitArgs.privateKeyFile, secretGitArgs.password)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
opts.Keypair = keypair
|
||||||
opts.SSHHostname = u.Host
|
opts.SSHHostname = u.Host
|
||||||
opts.PrivateKeyPath = secretGitArgs.privateKeyFile
|
|
||||||
opts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(secretGitArgs.keyAlgorithm)
|
opts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(secretGitArgs.keyAlgorithm)
|
||||||
opts.RSAKeyBits = int(secretGitArgs.rsaBits)
|
opts.RSAKeyBits = int(secretGitArgs.rsaBits)
|
||||||
opts.ECDSACurve = secretGitArgs.ecdsaCurve.Curve
|
opts.ECDSACurve = secretGitArgs.ecdsaCurve.Curve
|
||||||
@@ -150,7 +152,13 @@ func createSecretGitCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
opts.Username = secretGitArgs.username
|
opts.Username = secretGitArgs.username
|
||||||
opts.Password = secretGitArgs.password
|
opts.Password = secretGitArgs.password
|
||||||
opts.CAFilePath = secretGitArgs.caFile
|
if secretGitArgs.caFile != "" {
|
||||||
|
caBundle, err := os.ReadFile(secretGitArgs.caFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to read TLS CA file: %w", err)
|
||||||
|
}
|
||||||
|
opts.CAFile = caBundle
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("git URL scheme '%s' not supported, can be: ssh, http and https", u.Scheme)
|
return fmt.Errorf("git URL scheme '%s' not supported, can be: ssh, http and https", u.Scheme)
|
||||||
}
|
}
|
||||||
@@ -161,7 +169,7 @@ func createSecretGitCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if createArgs.export {
|
if createArgs.export {
|
||||||
fmt.Println(secret.Content)
|
rootCmd.Println(secret.Content)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,14 +184,14 @@ func createSecretGitCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := upsertSecret(ctx, kubeClient, s); err != nil {
|
if err := upsertSecret(ctx, kubeClient, s); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
logger.Actionf("secret '%s' created in '%s' namespace", name, rootArgs.namespace)
|
logger.Actionf("git secret '%s' created in '%s' namespace", name, *kubeconfigArgs.Namespace)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
44
cmd/flux/create_secret_git_test.go
Normal file
44
cmd/flux/create_secret_git_test.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCreateGitSecret(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args string
|
||||||
|
assert assertFunc
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no args",
|
||||||
|
args: "create secret git",
|
||||||
|
assert: assertError("name is required"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "basic secret",
|
||||||
|
args: "create secret git podinfo-auth --url=https://github.com/stefanprodan/podinfo --username=my-username --password=my-password --namespace=my-namespace --export",
|
||||||
|
assert: assertGoldenFile("./testdata/create_secret/git/secret-git-basic.yaml"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ssh key",
|
||||||
|
args: "create secret git podinfo-auth --url=ssh://git@github.com/stefanprodan/podinfo --private-key-file=./testdata/create_secret/git/ecdsa.private --namespace=my-namespace --export",
|
||||||
|
assert: assertGoldenFile("testdata/create_secret/git/git-ssh-secret.yaml"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ssh key with password",
|
||||||
|
args: "create secret git podinfo-auth --url=ssh://git@github.com/stefanprodan/podinfo --private-key-file=./testdata/create_secret/git/ecdsa-password.private --password=password --namespace=my-namespace --export",
|
||||||
|
assert: assertGoldenFile("testdata/create_secret/git/git-ssh-secret-password.yaml"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
cmd := cmdTestCase{
|
||||||
|
args: tt.args,
|
||||||
|
assert: tt.assert,
|
||||||
|
}
|
||||||
|
cmd.runTestCmd(t)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
@@ -68,9 +69,6 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func createSecretHelmCmdRun(cmd *cobra.Command, args []string) error {
|
func createSecretHelmCmdRun(cmd *cobra.Command, args []string) error {
|
||||||
if len(args) < 1 {
|
|
||||||
return fmt.Errorf("secret name is required")
|
|
||||||
}
|
|
||||||
name := args[0]
|
name := args[0]
|
||||||
|
|
||||||
labels, err := parseLabels()
|
labels, err := parseLabels()
|
||||||
@@ -78,15 +76,34 @@ func createSecretHelmCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
caBundle := []byte{}
|
||||||
|
if secretHelmArgs.caFile != "" {
|
||||||
|
var err error
|
||||||
|
caBundle, err = os.ReadFile(secretHelmArgs.caFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to read TLS CA file: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var certFile, keyFile []byte
|
||||||
|
if secretHelmArgs.certFile != "" && secretHelmArgs.keyFile != "" {
|
||||||
|
if certFile, err = os.ReadFile(secretHelmArgs.certFile); err != nil {
|
||||||
|
return fmt.Errorf("failed to read cert file: %w", err)
|
||||||
|
}
|
||||||
|
if keyFile, err = os.ReadFile(secretHelmArgs.keyFile); err != nil {
|
||||||
|
return fmt.Errorf("failed to read key file: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
opts := sourcesecret.Options{
|
opts := sourcesecret.Options{
|
||||||
Name: name,
|
Name: name,
|
||||||
Namespace: rootArgs.namespace,
|
Namespace: *kubeconfigArgs.Namespace,
|
||||||
Labels: labels,
|
Labels: labels,
|
||||||
Username: secretHelmArgs.username,
|
Username: secretHelmArgs.username,
|
||||||
Password: secretHelmArgs.password,
|
Password: secretHelmArgs.password,
|
||||||
CAFilePath: secretHelmArgs.caFile,
|
CAFile: caBundle,
|
||||||
CertFilePath: secretHelmArgs.certFile,
|
CertFile: certFile,
|
||||||
KeyFilePath: secretHelmArgs.keyFile,
|
KeyFile: keyFile,
|
||||||
}
|
}
|
||||||
secret, err := sourcesecret.Generate(opts)
|
secret, err := sourcesecret.Generate(opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -94,13 +111,13 @@ func createSecretHelmCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if createArgs.export {
|
if createArgs.export {
|
||||||
fmt.Println(secret.Content)
|
rootCmd.Println(secret.Content)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -112,5 +129,6 @@ func createSecretHelmCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.Actionf("helm secret '%s' created in '%s' namespace", name, *kubeconfigArgs.Namespace)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2020 The Flux authors
|
Copyright 2022 The Flux authors
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@@ -14,34 +14,34 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package flags
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestHelmReleaseValuesFrom_Set(t *testing.T) {
|
func TestCreateHelmSecret(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
str string
|
args string
|
||||||
expect string
|
assert assertFunc
|
||||||
expectErr bool
|
|
||||||
}{
|
}{
|
||||||
{"supported", "Secret/foo", "Secret/foo", false},
|
{
|
||||||
{"lower case kind", "secret/foo", "Secret/foo", false},
|
args: "create secret helm",
|
||||||
{"unsupported", "Unsupported/kind", "", true},
|
assert: assertError("name is required"),
|
||||||
{"invalid format", "Secret", "", true},
|
},
|
||||||
{"empty", "", "", true},
|
{
|
||||||
|
args: "create secret helm helm-secret --username=my-username --password=my-password --namespace=my-namespace --export",
|
||||||
|
assert: assertGoldenFile("testdata/create_secret/helm/secret-helm.yaml"),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
var h HelmReleaseValuesFrom
|
cmd := cmdTestCase{
|
||||||
if err := h.Set(tt.str); (err != nil) != tt.expectErr {
|
args: tt.args,
|
||||||
t.Errorf("Set() error = %v, expectErr %v", err, tt.expectErr)
|
assert: tt.assert,
|
||||||
}
|
|
||||||
if str := h.String(); str != tt.expect {
|
|
||||||
t.Errorf("Set() = %v, expect %v", str, tt.expect)
|
|
||||||
}
|
}
|
||||||
|
cmd.runTestCmd(t)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
121
cmd/flux/create_secret_oci.go
Normal file
121
cmd/flux/create_secret_oci.go
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 The Flux authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/fluxcd/flux2/internal/utils"
|
||||||
|
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
|
||||||
|
"github.com/google/go-containerregistry/pkg/name"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
"sigs.k8s.io/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
var createSecretOCICmd = &cobra.Command{
|
||||||
|
Use: "oci [name]",
|
||||||
|
Short: "Create or update a Kubernetes image pull secret",
|
||||||
|
Long: `The create secret oci command generates a Kubernetes secret that can be used for OCIRepository authentication`,
|
||||||
|
Example: ` # Create an OCI authentication secret on disk and encrypt it with Mozilla SOPS
|
||||||
|
flux create secret oci podinfo-auth \
|
||||||
|
--url=ghcr.io \
|
||||||
|
--username=username \
|
||||||
|
--password=password \
|
||||||
|
--export > repo-auth.yaml
|
||||||
|
|
||||||
|
sops --encrypt --encrypted-regex '^(data|stringData)$' \
|
||||||
|
--in-place repo-auth.yaml
|
||||||
|
`,
|
||||||
|
RunE: createSecretOCICmdRun,
|
||||||
|
}
|
||||||
|
|
||||||
|
type secretOCIFlags struct {
|
||||||
|
url string
|
||||||
|
password string
|
||||||
|
username string
|
||||||
|
}
|
||||||
|
|
||||||
|
var secretOCIArgs = secretOCIFlags{}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
createSecretOCICmd.Flags().StringVar(&secretOCIArgs.url, "url", "", "oci repository address e.g ghcr.io/stefanprodan/charts")
|
||||||
|
createSecretOCICmd.Flags().StringVarP(&secretOCIArgs.username, "username", "u", "", "basic authentication username")
|
||||||
|
createSecretOCICmd.Flags().StringVarP(&secretOCIArgs.password, "password", "p", "", "basic authentication password")
|
||||||
|
|
||||||
|
createSecretCmd.AddCommand(createSecretOCICmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createSecretOCICmdRun(cmd *cobra.Command, args []string) error {
|
||||||
|
if len(args) < 1 {
|
||||||
|
return fmt.Errorf("name is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
secretName := args[0]
|
||||||
|
|
||||||
|
if secretOCIArgs.url == "" {
|
||||||
|
return fmt.Errorf("--url is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if secretOCIArgs.username == "" {
|
||||||
|
return fmt.Errorf("--username is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if secretOCIArgs.password == "" {
|
||||||
|
return fmt.Errorf("--password is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := name.ParseReference(secretOCIArgs.url); err != nil {
|
||||||
|
return fmt.Errorf("error parsing url: '%s'", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := sourcesecret.Options{
|
||||||
|
Name: secretName,
|
||||||
|
Namespace: *kubeconfigArgs.Namespace,
|
||||||
|
Registry: secretOCIArgs.url,
|
||||||
|
Password: secretOCIArgs.password,
|
||||||
|
Username: secretOCIArgs.username,
|
||||||
|
}
|
||||||
|
|
||||||
|
secret, err := sourcesecret.Generate(opts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if createArgs.export {
|
||||||
|
rootCmd.Println(secret.Content)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||||
|
defer cancel()
|
||||||
|
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var s corev1.Secret
|
||||||
|
if err := yaml.Unmarshal([]byte(secret.Content), &s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := upsertSecret(ctx, kubeClient, s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Actionf("oci secret '%s' created in '%s' namespace", secretName, *kubeconfigArgs.Namespace)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
51
cmd/flux/create_secret_oci_test.go
Normal file
51
cmd/flux/create_secret_oci_test.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 The Flux authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCreateSecretOCI(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args string
|
||||||
|
assert assertFunc
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
args: "create secret oci",
|
||||||
|
assert: assertError("name is required"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: "create secret oci ghcr",
|
||||||
|
assert: assertError("--url is required"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: "create secret oci ghcr --namespace=my-namespace --url ghcr.io --username stefanprodan --password=password --export",
|
||||||
|
assert: assertGoldenFile("testdata/create_secret/oci/create-secret.yaml"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
cmd := cmdTestCase{
|
||||||
|
args: tt.args,
|
||||||
|
assert: tt.assert,
|
||||||
|
}
|
||||||
|
cmd.runTestCmd(t)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
@@ -67,9 +68,6 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func createSecretTLSCmdRun(cmd *cobra.Command, args []string) error {
|
func createSecretTLSCmdRun(cmd *cobra.Command, args []string) error {
|
||||||
if len(args) < 1 {
|
|
||||||
return fmt.Errorf("secret name is required")
|
|
||||||
}
|
|
||||||
name := args[0]
|
name := args[0]
|
||||||
|
|
||||||
labels, err := parseLabels()
|
labels, err := parseLabels()
|
||||||
@@ -77,13 +75,32 @@ func createSecretTLSCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
caBundle := []byte{}
|
||||||
|
if secretTLSArgs.caFile != "" {
|
||||||
|
var err error
|
||||||
|
caBundle, err = os.ReadFile(secretTLSArgs.caFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to read TLS CA file: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var certFile, keyFile []byte
|
||||||
|
if secretTLSArgs.certFile != "" && secretTLSArgs.keyFile != "" {
|
||||||
|
if certFile, err = os.ReadFile(secretTLSArgs.certFile); err != nil {
|
||||||
|
return fmt.Errorf("failed to read cert file: %w", err)
|
||||||
|
}
|
||||||
|
if keyFile, err = os.ReadFile(secretTLSArgs.keyFile); err != nil {
|
||||||
|
return fmt.Errorf("failed to read key file: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
opts := sourcesecret.Options{
|
opts := sourcesecret.Options{
|
||||||
Name: name,
|
Name: name,
|
||||||
Namespace: rootArgs.namespace,
|
Namespace: *kubeconfigArgs.Namespace,
|
||||||
Labels: labels,
|
Labels: labels,
|
||||||
CAFilePath: secretTLSArgs.caFile,
|
CAFile: caBundle,
|
||||||
CertFilePath: secretTLSArgs.certFile,
|
CertFile: certFile,
|
||||||
KeyFilePath: secretTLSArgs.keyFile,
|
KeyFile: keyFile,
|
||||||
}
|
}
|
||||||
secret, err := sourcesecret.Generate(opts)
|
secret, err := sourcesecret.Generate(opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -91,13 +108,13 @@ func createSecretTLSCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if createArgs.export {
|
if createArgs.export {
|
||||||
fmt.Println(secret.Content)
|
rootCmd.Print(secret.Content)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -109,5 +126,6 @@ func createSecretTLSCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.Actionf("tls secret '%s' created in '%s' namespace", name, *kubeconfigArgs.Namespace)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
31
cmd/flux/create_secret_tls_test.go
Normal file
31
cmd/flux/create_secret_tls_test.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCreateTlsSecretNoArgs(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args string
|
||||||
|
assert assertFunc
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
args: "create secret tls",
|
||||||
|
assert: assertError("name is required"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: "create secret tls certs --namespace=my-namespace --cert-file=./testdata/create_secret/tls/test-cert.pem --key-file=./testdata/create_secret/tls/test-key.pem --export",
|
||||||
|
assert: assertGoldenFile("testdata/create_secret/tls/secret-tls.yaml"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
cmd := cmdTestCase{
|
||||||
|
args: tt.args,
|
||||||
|
assert: tt.assert,
|
||||||
|
}
|
||||||
|
cmd.runTestCmd(t)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,8 @@ limitations under the License.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -26,6 +28,14 @@ var createSourceCmd = &cobra.Command{
|
|||||||
Long: "The create source sub-commands generate sources.",
|
Long: "The create source sub-commands generate sources.",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type createSourceFlags struct {
|
||||||
|
fetchTimeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
var createSourceArgs createSourceFlags
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
createSourceCmd.PersistentFlags().DurationVar(&createSourceArgs.fetchTimeout, "fetch-timeout", createSourceArgs.fetchTimeout,
|
||||||
|
"set a timeout for fetch operations performed by source-controller (e.g. 'git clone' or 'helm repo update')")
|
||||||
createCmd.AddCommand(createSourceCmd)
|
createCmd.AddCommand(createSourceCmd)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
@@ -31,7 +31,9 @@ import (
|
|||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
|
||||||
"github.com/fluxcd/pkg/apis/meta"
|
"github.com/fluxcd/pkg/apis/meta"
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
"github.com/fluxcd/pkg/runtime/conditions"
|
||||||
|
|
||||||
|
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/internal/flags"
|
"github.com/fluxcd/flux2/internal/flags"
|
||||||
"github.com/fluxcd/flux2/internal/utils"
|
"github.com/fluxcd/flux2/internal/utils"
|
||||||
@@ -70,9 +72,10 @@ type sourceBucketFlags struct {
|
|||||||
region string
|
region string
|
||||||
insecure bool
|
insecure bool
|
||||||
secretRef string
|
secretRef string
|
||||||
|
ignorePaths []string
|
||||||
}
|
}
|
||||||
|
|
||||||
var sourceBucketArgs = NewSourceBucketFlags()
|
var sourceBucketArgs = newSourceBucketFlags()
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
createSourceBucketCmd.Flags().Var(&sourceBucketArgs.provider, "provider", sourceBucketArgs.provider.Description())
|
createSourceBucketCmd.Flags().Var(&sourceBucketArgs.provider, "provider", sourceBucketArgs.provider.Description())
|
||||||
@@ -83,20 +86,18 @@ func init() {
|
|||||||
createSourceBucketCmd.Flags().StringVar(&sourceBucketArgs.region, "region", "", "the bucket region")
|
createSourceBucketCmd.Flags().StringVar(&sourceBucketArgs.region, "region", "", "the bucket region")
|
||||||
createSourceBucketCmd.Flags().BoolVar(&sourceBucketArgs.insecure, "insecure", false, "for when connecting to a non-TLS S3 HTTP endpoint")
|
createSourceBucketCmd.Flags().BoolVar(&sourceBucketArgs.insecure, "insecure", false, "for when connecting to a non-TLS S3 HTTP endpoint")
|
||||||
createSourceBucketCmd.Flags().StringVar(&sourceBucketArgs.secretRef, "secret-ref", "", "the name of an existing secret containing credentials")
|
createSourceBucketCmd.Flags().StringVar(&sourceBucketArgs.secretRef, "secret-ref", "", "the name of an existing secret containing credentials")
|
||||||
|
createSourceBucketCmd.Flags().StringSliceVar(&sourceBucketArgs.ignorePaths, "ignore-paths", nil, "set paths to ignore in bucket resource (can specify multiple paths with commas: path1,path2)")
|
||||||
|
|
||||||
createSourceCmd.AddCommand(createSourceBucketCmd)
|
createSourceCmd.AddCommand(createSourceBucketCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSourceBucketFlags() sourceBucketFlags {
|
func newSourceBucketFlags() sourceBucketFlags {
|
||||||
return sourceBucketFlags{
|
return sourceBucketFlags{
|
||||||
provider: flags.SourceBucketProvider(sourcev1.GenericBucketProvider),
|
provider: flags.SourceBucketProvider(sourcev1.GenericBucketProvider),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
|
func createSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
|
||||||
if len(args) < 1 {
|
|
||||||
return fmt.Errorf("Bucket source name is required")
|
|
||||||
}
|
|
||||||
name := args[0]
|
name := args[0]
|
||||||
|
|
||||||
if sourceBucketArgs.name == "" {
|
if sourceBucketArgs.name == "" {
|
||||||
@@ -112,16 +113,22 @@ func createSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpDir, err := ioutil.TempDir("", name)
|
tmpDir, err := os.MkdirTemp("", name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(tmpDir)
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
|
var ignorePaths *string
|
||||||
|
if len(sourceBucketArgs.ignorePaths) > 0 {
|
||||||
|
ignorePathsStr := strings.Join(sourceBucketArgs.ignorePaths, "\n")
|
||||||
|
ignorePaths = &ignorePathsStr
|
||||||
|
}
|
||||||
|
|
||||||
bucket := &sourcev1.Bucket{
|
bucket := &sourcev1.Bucket{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: name,
|
Name: name,
|
||||||
Namespace: rootArgs.namespace,
|
Namespace: *kubeconfigArgs.Namespace,
|
||||||
Labels: sourceLabels,
|
Labels: sourceLabels,
|
||||||
},
|
},
|
||||||
Spec: sourcev1.BucketSpec{
|
Spec: sourcev1.BucketSpec{
|
||||||
@@ -133,9 +140,15 @@ func createSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
Interval: metav1.Duration{
|
Interval: metav1.Duration{
|
||||||
Duration: createArgs.interval,
|
Duration: createArgs.interval,
|
||||||
},
|
},
|
||||||
|
Ignore: ignorePaths,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if sourceHelmArgs.secretRef != "" {
|
|
||||||
|
if createSourceArgs.fetchTimeout > 0 {
|
||||||
|
bucket.Spec.Timeout = &metav1.Duration{Duration: createSourceArgs.fetchTimeout}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sourceBucketArgs.secretRef != "" {
|
||||||
bucket.Spec.SecretRef = &meta.LocalObjectReference{
|
bucket.Spec.SecretRef = &meta.LocalObjectReference{
|
||||||
Name: sourceBucketArgs.secretRef,
|
Name: sourceBucketArgs.secretRef,
|
||||||
}
|
}
|
||||||
@@ -148,7 +161,7 @@ func createSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -161,7 +174,7 @@ func createSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
secret := corev1.Secret{
|
secret := corev1.Secret{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: secretName,
|
Name: secretName,
|
||||||
Namespace: rootArgs.namespace,
|
Namespace: *kubeconfigArgs.Namespace,
|
||||||
Labels: sourceLabels,
|
Labels: sourceLabels,
|
||||||
},
|
},
|
||||||
StringData: map[string]string{},
|
StringData: map[string]string{},
|
||||||
@@ -234,3 +247,30 @@ func upsertBucket(ctx context.Context, kubeClient client.Client,
|
|||||||
logger.Successf("Bucket source updated")
|
logger.Successf("Bucket source updated")
|
||||||
return namespacedName, nil
|
return namespacedName, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isBucketReady(ctx context.Context, kubeClient client.Client,
|
||||||
|
namespacedName types.NamespacedName, bucket *sourcev1.Bucket) wait.ConditionFunc {
|
||||||
|
return func() (bool, error) {
|
||||||
|
err := kubeClient.Get(ctx, namespacedName, bucket)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if c := conditions.Get(bucket, meta.ReadyCondition); c != nil {
|
||||||
|
// Confirm the Ready condition we are observing is for the
|
||||||
|
// current generation
|
||||||
|
if c.ObservedGeneration != bucket.GetGeneration() {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Further check the Status
|
||||||
|
switch c.Status {
|
||||||
|
case metav1.ConditionTrue:
|
||||||
|
return true, nil
|
||||||
|
case metav1.ConditionFalse:
|
||||||
|
return false, fmt.Errorf(c.Message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,23 +20,25 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/fluxcd/pkg/apis/meta"
|
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
|
||||||
"github.com/manifoldco/promptui"
|
"github.com/manifoldco/promptui"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
"sigs.k8s.io/yaml"
|
"sigs.k8s.io/yaml"
|
||||||
|
|
||||||
|
"github.com/fluxcd/pkg/apis/meta"
|
||||||
|
"github.com/fluxcd/pkg/runtime/conditions"
|
||||||
|
|
||||||
|
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/internal/flags"
|
"github.com/fluxcd/flux2/internal/flags"
|
||||||
"github.com/fluxcd/flux2/internal/utils"
|
"github.com/fluxcd/flux2/internal/utils"
|
||||||
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
|
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
|
||||||
@@ -57,6 +59,8 @@ type sourceGitFlags struct {
|
|||||||
caFile string
|
caFile string
|
||||||
privateKeyFile string
|
privateKeyFile string
|
||||||
recurseSubmodules bool
|
recurseSubmodules bool
|
||||||
|
silent bool
|
||||||
|
ignorePaths []string
|
||||||
}
|
}
|
||||||
|
|
||||||
var createSourceGitCmd = &cobra.Command{
|
var createSourceGitCmd = &cobra.Command{
|
||||||
@@ -113,6 +117,7 @@ For private Git repositories, the basic authentication credentials are stored in
|
|||||||
# Create a source for a Git repository using basic authentication
|
# Create a source for a Git repository using basic authentication
|
||||||
flux create source git podinfo \
|
flux create source git podinfo \
|
||||||
--url=https://github.com/stefanprodan/podinfo \
|
--url=https://github.com/stefanprodan/podinfo \
|
||||||
|
--branch=master \
|
||||||
--username=username \
|
--username=username \
|
||||||
--password=password`,
|
--password=password`,
|
||||||
RunE: createSourceGitCmdRun,
|
RunE: createSourceGitCmdRun,
|
||||||
@@ -136,22 +141,21 @@ func init() {
|
|||||||
createSourceGitCmd.Flags().StringVar(&sourceGitArgs.privateKeyFile, "private-key-file", "", "path to a passwordless private key file used for authenticating to the Git SSH server")
|
createSourceGitCmd.Flags().StringVar(&sourceGitArgs.privateKeyFile, "private-key-file", "", "path to a passwordless private key file used for authenticating to the Git SSH server")
|
||||||
createSourceGitCmd.Flags().BoolVar(&sourceGitArgs.recurseSubmodules, "recurse-submodules", false,
|
createSourceGitCmd.Flags().BoolVar(&sourceGitArgs.recurseSubmodules, "recurse-submodules", false,
|
||||||
"when enabled, configures the GitRepository source to initialize and include Git submodules in the artifact it produces")
|
"when enabled, configures the GitRepository source to initialize and include Git submodules in the artifact it produces")
|
||||||
|
createSourceGitCmd.Flags().BoolVarP(&sourceGitArgs.silent, "silent", "s", false, "assumes the deploy key is already setup, skips confirmation")
|
||||||
|
createSourceGitCmd.Flags().StringSliceVar(&sourceGitArgs.ignorePaths, "ignore-paths", nil, "set paths to ignore in git resource (can specify multiple paths with commas: path1,path2)")
|
||||||
|
|
||||||
createSourceCmd.AddCommand(createSourceGitCmd)
|
createSourceCmd.AddCommand(createSourceGitCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSourceGitFlags() sourceGitFlags {
|
func newSourceGitFlags() sourceGitFlags {
|
||||||
return sourceGitFlags{
|
return sourceGitFlags{
|
||||||
keyAlgorithm: flags.PublicKeyAlgorithm(sourcesecret.RSAPrivateKeyAlgorithm),
|
keyAlgorithm: flags.PublicKeyAlgorithm(sourcesecret.ECDSAPrivateKeyAlgorithm),
|
||||||
keyRSABits: 2048,
|
keyRSABits: 2048,
|
||||||
keyECDSACurve: flags.ECDSACurve{Curve: elliptic.P384()},
|
keyECDSACurve: flags.ECDSACurve{Curve: elliptic.P384()},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
|
func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
|
||||||
if len(args) < 1 {
|
|
||||||
return fmt.Errorf("GitRepository source name is required")
|
|
||||||
}
|
|
||||||
name := args[0]
|
name := args[0]
|
||||||
|
|
||||||
if sourceGitArgs.url == "" {
|
if sourceGitArgs.url == "" {
|
||||||
@@ -171,14 +175,14 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if sourceGitArgs.caFile != "" && u.Scheme == "ssh" {
|
if sourceGitArgs.caFile != "" && u.Scheme == "ssh" {
|
||||||
return fmt.Errorf("specifing a CA file is not supported for Git over SSH")
|
return fmt.Errorf("specifying a CA file is not supported for Git over SSH")
|
||||||
}
|
}
|
||||||
|
|
||||||
if sourceGitArgs.recurseSubmodules && sourceGitArgs.gitImplementation == sourcev1.LibGit2Implementation {
|
if sourceGitArgs.recurseSubmodules && sourceGitArgs.gitImplementation == sourcev1.LibGit2Implementation {
|
||||||
return fmt.Errorf("recurse submodules requires --git-implementation=%s", sourcev1.GoGitImplementation)
|
return fmt.Errorf("recurse submodules requires --git-implementation=%s", sourcev1.GoGitImplementation)
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpDir, err := ioutil.TempDir("", name)
|
tmpDir, err := os.MkdirTemp("", name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -189,10 +193,16 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ignorePaths *string
|
||||||
|
if len(sourceGitArgs.ignorePaths) > 0 {
|
||||||
|
ignorePathsStr := strings.Join(sourceGitArgs.ignorePaths, "\n")
|
||||||
|
ignorePaths = &ignorePathsStr
|
||||||
|
}
|
||||||
|
|
||||||
gitRepository := sourcev1.GitRepository{
|
gitRepository := sourcev1.GitRepository{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: name,
|
Name: name,
|
||||||
Namespace: rootArgs.namespace,
|
Namespace: *kubeconfigArgs.Namespace,
|
||||||
Labels: sourceLabels,
|
Labels: sourceLabels,
|
||||||
},
|
},
|
||||||
Spec: sourcev1.GitRepositorySpec{
|
Spec: sourcev1.GitRepositorySpec{
|
||||||
@@ -202,9 +212,14 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
},
|
},
|
||||||
RecurseSubmodules: sourceGitArgs.recurseSubmodules,
|
RecurseSubmodules: sourceGitArgs.recurseSubmodules,
|
||||||
Reference: &sourcev1.GitRepositoryRef{},
|
Reference: &sourcev1.GitRepositoryRef{},
|
||||||
|
Ignore: ignorePaths,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if createSourceArgs.fetchTimeout > 0 {
|
||||||
|
gitRepository.Spec.Timeout = &metav1.Duration{Duration: createSourceArgs.fetchTimeout}
|
||||||
|
}
|
||||||
|
|
||||||
if sourceGitArgs.gitImplementation != "" {
|
if sourceGitArgs.gitImplementation != "" {
|
||||||
gitRepository.Spec.GitImplementation = sourceGitArgs.gitImplementation.String()
|
gitRepository.Spec.GitImplementation = sourceGitArgs.gitImplementation.String()
|
||||||
}
|
}
|
||||||
@@ -230,7 +245,7 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -239,21 +254,31 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
if sourceGitArgs.secretRef == "" {
|
if sourceGitArgs.secretRef == "" {
|
||||||
secretOpts := sourcesecret.Options{
|
secretOpts := sourcesecret.Options{
|
||||||
Name: name,
|
Name: name,
|
||||||
Namespace: rootArgs.namespace,
|
Namespace: *kubeconfigArgs.Namespace,
|
||||||
ManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile,
|
ManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile,
|
||||||
}
|
}
|
||||||
switch u.Scheme {
|
switch u.Scheme {
|
||||||
case "ssh":
|
case "ssh":
|
||||||
|
keypair, err := sourcesecret.LoadKeyPairFromPath(sourceGitArgs.privateKeyFile, sourceGitArgs.password)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
secretOpts.Keypair = keypair
|
||||||
secretOpts.SSHHostname = u.Host
|
secretOpts.SSHHostname = u.Host
|
||||||
secretOpts.PrivateKeyPath = sourceGitArgs.privateKeyFile
|
|
||||||
secretOpts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(sourceGitArgs.keyAlgorithm)
|
secretOpts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(sourceGitArgs.keyAlgorithm)
|
||||||
secretOpts.RSAKeyBits = int(sourceGitArgs.keyRSABits)
|
secretOpts.RSAKeyBits = int(sourceGitArgs.keyRSABits)
|
||||||
secretOpts.ECDSACurve = sourceGitArgs.keyECDSACurve.Curve
|
secretOpts.ECDSACurve = sourceGitArgs.keyECDSACurve.Curve
|
||||||
secretOpts.Password = sourceGitArgs.password
|
secretOpts.Password = sourceGitArgs.password
|
||||||
case "https":
|
case "https":
|
||||||
|
if sourceGitArgs.caFile != "" {
|
||||||
|
caBundle, err := os.ReadFile(sourceGitArgs.caFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to read TLS CA file: %w", err)
|
||||||
|
}
|
||||||
|
secretOpts.CAFile = caBundle
|
||||||
|
}
|
||||||
secretOpts.Username = sourceGitArgs.username
|
secretOpts.Username = sourceGitArgs.username
|
||||||
secretOpts.Password = sourceGitArgs.password
|
secretOpts.Password = sourceGitArgs.password
|
||||||
secretOpts.CAFilePath = sourceGitArgs.caFile
|
|
||||||
case "http":
|
case "http":
|
||||||
logger.Warningf("insecure configuration: credentials configured for an HTTP URL")
|
logger.Warningf("insecure configuration: credentials configured for an HTTP URL")
|
||||||
secretOpts.Username = sourceGitArgs.username
|
secretOpts.Username = sourceGitArgs.username
|
||||||
@@ -273,6 +298,7 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
if ppk, ok := s.StringData[sourcesecret.PublicKeySecretKey]; ok {
|
if ppk, ok := s.StringData[sourcesecret.PublicKeySecretKey]; ok {
|
||||||
logger.Generatef("deploy key: %s", ppk)
|
logger.Generatef("deploy key: %s", ppk)
|
||||||
|
if !sourceGitArgs.silent {
|
||||||
prompt := promptui.Prompt{
|
prompt := promptui.Prompt{
|
||||||
Label: "Have you added the deploy key to your repository",
|
Label: "Have you added the deploy key to your repository",
|
||||||
IsConfirm: true,
|
IsConfirm: true,
|
||||||
@@ -281,6 +307,7 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
return fmt.Errorf("aborting")
|
return fmt.Errorf("aborting")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
logger.Actionf("applying secret with repository credentials")
|
logger.Actionf("applying secret with repository credentials")
|
||||||
if err := upsertSecret(ctx, kubeClient, s); err != nil {
|
if err := upsertSecret(ctx, kubeClient, s); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -351,7 +378,14 @@ func isGitRepositoryReady(ctx context.Context, kubeClient client.Client,
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if c := apimeta.FindStatusCondition(gitRepository.Status.Conditions, meta.ReadyCondition); c != nil {
|
if c := conditions.Get(gitRepository, meta.ReadyCondition); c != nil {
|
||||||
|
// Confirm the Ready condition we are observing is for the
|
||||||
|
// current generation
|
||||||
|
if c.ObservedGeneration != gitRepository.GetGeneration() {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Further check the Status
|
||||||
switch c.Status {
|
switch c.Status {
|
||||||
case metav1.ConditionTrue:
|
case metav1.ConditionTrue:
|
||||||
return true, nil
|
return true, nil
|
||||||
|
|||||||
196
cmd/flux/create_source_git_test.go
Normal file
196
cmd/flux/create_source_git_test.go
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
//go:build unit
|
||||||
|
// +build unit
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 2021 The Flux authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fluxcd/pkg/apis/meta"
|
||||||
|
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||||
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
var pollInterval = 50 * time.Millisecond
|
||||||
|
var testTimeout = 10 * time.Second
|
||||||
|
|
||||||
|
// Update the GitRepository once created to exercise test specific behavior
|
||||||
|
type reconcileFunc func(repo *sourcev1.GitRepository)
|
||||||
|
|
||||||
|
// reconciler waits for an object to be created, then invokes a test supplied
|
||||||
|
// function to mutate that object, simulating a controller.
|
||||||
|
// Test should invoke run() to run the background reconciler task which
|
||||||
|
// polls to wait for the object to exist before applying the update function.
|
||||||
|
// Any errors from the reconciler are asserted on test completion.
|
||||||
|
type reconciler struct {
|
||||||
|
client client.Client
|
||||||
|
name types.NamespacedName
|
||||||
|
reconcile reconcileFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the background task that waits for the object to exist then applies
|
||||||
|
// the update function.
|
||||||
|
func (r *reconciler) run(t *testing.T) {
|
||||||
|
result := make(chan error)
|
||||||
|
go func() {
|
||||||
|
defer close(result)
|
||||||
|
err := wait.PollImmediate(
|
||||||
|
pollInterval,
|
||||||
|
testTimeout,
|
||||||
|
r.conditionFunc)
|
||||||
|
result <- err
|
||||||
|
}()
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if err := <-result; err != nil {
|
||||||
|
t.Errorf("Failure from test reconciler: '%v':", err.Error())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// A ConditionFunction that waits for the named GitRepository to be created,
|
||||||
|
// then sets the ready condition to true.
|
||||||
|
func (r *reconciler) conditionFunc() (bool, error) {
|
||||||
|
var repo sourcev1.GitRepository
|
||||||
|
if err := r.client.Get(context.Background(), r.name, &repo); err != nil {
|
||||||
|
if errors.IsNotFound(err) {
|
||||||
|
return false, nil // Keep polling until object is created
|
||||||
|
}
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
r.reconcile(&repo)
|
||||||
|
err := r.client.Status().Update(context.Background(), &repo)
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateSourceGitExport(t *testing.T) {
|
||||||
|
var command = "create source git podinfo --url=https://github.com/stefanprodan/podinfo --branch=master --ignore-paths .cosign,non-existent-dir/ -n default --interval 1m --export --timeout=" + testTimeout.String()
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
args string
|
||||||
|
assert assertFunc
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"ExportSucceeded",
|
||||||
|
command,
|
||||||
|
assertGoldenFile("testdata/create_source_git/export.golden"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
cmd := cmdTestCase{
|
||||||
|
args: tc.args,
|
||||||
|
assert: tc.assert,
|
||||||
|
}
|
||||||
|
cmd.runTestCmd(t)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateSourceGit(t *testing.T) {
|
||||||
|
// Default command used for multiple tests
|
||||||
|
var command = "create source git podinfo --url=https://github.com/stefanprodan/podinfo --branch=master --timeout=" + testTimeout.String()
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
args string
|
||||||
|
assert assertFunc
|
||||||
|
reconcile reconcileFunc
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"NoArgs",
|
||||||
|
"create source git",
|
||||||
|
assertError("name is required"),
|
||||||
|
nil,
|
||||||
|
}, {
|
||||||
|
"Succeeded",
|
||||||
|
command,
|
||||||
|
assertGoldenFile("testdata/create_source_git/success.golden"),
|
||||||
|
func(repo *sourcev1.GitRepository) {
|
||||||
|
newCondition := metav1.Condition{
|
||||||
|
Type: meta.ReadyCondition,
|
||||||
|
Status: metav1.ConditionTrue,
|
||||||
|
Reason: sourcev1.GitOperationSucceedReason,
|
||||||
|
Message: "succeeded message",
|
||||||
|
ObservedGeneration: repo.GetGeneration(),
|
||||||
|
}
|
||||||
|
apimeta.SetStatusCondition(&repo.Status.Conditions, newCondition)
|
||||||
|
repo.Status.Artifact = &sourcev1.Artifact{
|
||||||
|
Path: "some-path",
|
||||||
|
Revision: "v1",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
"Failed",
|
||||||
|
command,
|
||||||
|
assertError("failed message"),
|
||||||
|
func(repo *sourcev1.GitRepository) {
|
||||||
|
newCondition := metav1.Condition{
|
||||||
|
Type: meta.ReadyCondition,
|
||||||
|
Status: metav1.ConditionFalse,
|
||||||
|
Reason: sourcev1.URLInvalidReason,
|
||||||
|
Message: "failed message",
|
||||||
|
ObservedGeneration: repo.GetGeneration(),
|
||||||
|
}
|
||||||
|
apimeta.SetStatusCondition(&repo.Status.Conditions, newCondition)
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
"NoArtifact",
|
||||||
|
command,
|
||||||
|
assertError("GitRepository source reconciliation completed but no artifact was found"),
|
||||||
|
func(repo *sourcev1.GitRepository) {
|
||||||
|
// Updated with no artifact
|
||||||
|
newCondition := metav1.Condition{
|
||||||
|
Type: meta.ReadyCondition,
|
||||||
|
Status: metav1.ConditionTrue,
|
||||||
|
Reason: sourcev1.GitOperationSucceedReason,
|
||||||
|
Message: "succeeded message",
|
||||||
|
ObservedGeneration: repo.GetGeneration(),
|
||||||
|
}
|
||||||
|
apimeta.SetStatusCondition(&repo.Status.Conditions, newCondition)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
ns := allocateNamespace("podinfo")
|
||||||
|
setupTestNamespace(ns, t)
|
||||||
|
if tc.reconcile != nil {
|
||||||
|
r := reconciler{
|
||||||
|
client: testEnv.client,
|
||||||
|
name: types.NamespacedName{Namespace: ns, Name: "podinfo"},
|
||||||
|
reconcile: tc.reconcile,
|
||||||
|
}
|
||||||
|
r.run(t)
|
||||||
|
}
|
||||||
|
cmd := cmdTestCase{
|
||||||
|
args: tc.args + " -n=" + ns,
|
||||||
|
assert: tc.assert,
|
||||||
|
}
|
||||||
|
cmd.runTestCmd(t)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,22 +19,21 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/fluxcd/pkg/apis/meta"
|
"github.com/fluxcd/pkg/apis/meta"
|
||||||
|
"github.com/fluxcd/pkg/runtime/conditions"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
"sigs.k8s.io/yaml"
|
"sigs.k8s.io/yaml"
|
||||||
|
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/internal/utils"
|
"github.com/fluxcd/flux2/internal/utils"
|
||||||
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
|
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
|
||||||
@@ -45,23 +44,34 @@ var createSourceHelmCmd = &cobra.Command{
|
|||||||
Short: "Create or update a HelmRepository source",
|
Short: "Create or update a HelmRepository source",
|
||||||
Long: `The create source helm command generates a HelmRepository resource and waits for it to fetch the index.
|
Long: `The create source helm command generates a HelmRepository resource and waits for it to fetch the index.
|
||||||
For private Helm repositories, the basic authentication credentials are stored in a Kubernetes secret.`,
|
For private Helm repositories, the basic authentication credentials are stored in a Kubernetes secret.`,
|
||||||
Example: ` # Create a source for a public Helm repository
|
Example: ` # Create a source for an HTTPS public Helm repository
|
||||||
flux create source helm podinfo \
|
flux create source helm podinfo \
|
||||||
--url=https://stefanprodan.github.io/podinfo \
|
--url=https://stefanprodan.github.io/podinfo \
|
||||||
--interval=10m
|
--interval=10m
|
||||||
|
|
||||||
# Create a source for a Helm repository using basic authentication
|
# Create a source for an HTTPS Helm repository using basic authentication
|
||||||
flux create source helm podinfo \
|
flux create source helm podinfo \
|
||||||
--url=https://stefanprodan.github.io/podinfo \
|
--url=https://stefanprodan.github.io/podinfo \
|
||||||
--username=username \
|
--username=username \
|
||||||
--password=password
|
--password=password
|
||||||
|
|
||||||
# Create a source for a Helm repository using TLS authentication
|
# Create a source for an HTTPS Helm repository using TLS authentication
|
||||||
flux create source helm podinfo \
|
flux create source helm podinfo \
|
||||||
--url=https://stefanprodan.github.io/podinfo \
|
--url=https://stefanprodan.github.io/podinfo \
|
||||||
--cert-file=./cert.crt \
|
--cert-file=./cert.crt \
|
||||||
--key-file=./key.crt \
|
--key-file=./key.crt \
|
||||||
--ca-file=./ca.crt`,
|
--ca-file=./ca.crt
|
||||||
|
|
||||||
|
# Create a source for an OCI Helm repository
|
||||||
|
flux create source helm podinfo \
|
||||||
|
--url=oci://ghcr.io/stefanprodan/charts/podinfo
|
||||||
|
--username=username \
|
||||||
|
--password=password
|
||||||
|
|
||||||
|
# Create a source for an OCI Helm repository using an existing secret with basic auth or dockerconfig credentials
|
||||||
|
flux create source helm podinfo \
|
||||||
|
--url=oci://ghcr.io/stefanprodan/charts/podinfo
|
||||||
|
--secret-ref=docker-config`,
|
||||||
RunE: createSourceHelmCmdRun,
|
RunE: createSourceHelmCmdRun,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,16 +95,13 @@ func init() {
|
|||||||
createSourceHelmCmd.Flags().StringVar(&sourceHelmArgs.certFile, "cert-file", "", "TLS authentication cert file path")
|
createSourceHelmCmd.Flags().StringVar(&sourceHelmArgs.certFile, "cert-file", "", "TLS authentication cert file path")
|
||||||
createSourceHelmCmd.Flags().StringVar(&sourceHelmArgs.keyFile, "key-file", "", "TLS authentication key file path")
|
createSourceHelmCmd.Flags().StringVar(&sourceHelmArgs.keyFile, "key-file", "", "TLS authentication key file path")
|
||||||
createSourceHelmCmd.Flags().StringVar(&sourceHelmArgs.caFile, "ca-file", "", "TLS authentication CA file path")
|
createSourceHelmCmd.Flags().StringVar(&sourceHelmArgs.caFile, "ca-file", "", "TLS authentication CA file path")
|
||||||
createSourceHelmCmd.Flags().StringVarP(&sourceHelmArgs.secretRef, "secret-ref", "", "", "the name of an existing secret containing TLS or basic auth credentials")
|
createSourceHelmCmd.Flags().StringVarP(&sourceHelmArgs.secretRef, "secret-ref", "", "", "the name of an existing secret containing TLS, basic auth or docker-config credentials")
|
||||||
createSourceHelmCmd.Flags().BoolVarP(&sourceHelmArgs.passCredentials, "pass-credentials", "", false, "pass credentials to all domains")
|
createSourceHelmCmd.Flags().BoolVarP(&sourceHelmArgs.passCredentials, "pass-credentials", "", false, "pass credentials to all domains")
|
||||||
|
|
||||||
createSourceCmd.AddCommand(createSourceHelmCmd)
|
createSourceCmd.AddCommand(createSourceHelmCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
|
func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
|
||||||
if len(args) < 1 {
|
|
||||||
return fmt.Errorf("HelmRepository source name is required")
|
|
||||||
}
|
|
||||||
name := args[0]
|
name := args[0]
|
||||||
|
|
||||||
if sourceHelmArgs.url == "" {
|
if sourceHelmArgs.url == "" {
|
||||||
@@ -106,7 +113,7 @@ func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpDir, err := ioutil.TempDir("", name)
|
tmpDir, err := os.MkdirTemp("", name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -119,7 +126,7 @@ func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
helmRepository := &sourcev1.HelmRepository{
|
helmRepository := &sourcev1.HelmRepository{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: name,
|
Name: name,
|
||||||
Namespace: rootArgs.namespace,
|
Namespace: *kubeconfigArgs.Namespace,
|
||||||
Labels: sourceLabels,
|
Labels: sourceLabels,
|
||||||
},
|
},
|
||||||
Spec: sourcev1.HelmRepositorySpec{
|
Spec: sourcev1.HelmRepositorySpec{
|
||||||
@@ -130,6 +137,18 @@ func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
url, err := url.Parse(sourceHelmArgs.url)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to parse URL: %w", err)
|
||||||
|
}
|
||||||
|
if url.Scheme == sourcev1.HelmRepositoryTypeOCI {
|
||||||
|
helmRepository.Spec.Type = sourcev1.HelmRepositoryTypeOCI
|
||||||
|
}
|
||||||
|
|
||||||
|
if createSourceArgs.fetchTimeout > 0 {
|
||||||
|
helmRepository.Spec.Timeout = &metav1.Duration{Duration: createSourceArgs.fetchTimeout}
|
||||||
|
}
|
||||||
|
|
||||||
if sourceHelmArgs.secretRef != "" {
|
if sourceHelmArgs.secretRef != "" {
|
||||||
helmRepository.Spec.SecretRef = &meta.LocalObjectReference{
|
helmRepository.Spec.SecretRef = &meta.LocalObjectReference{
|
||||||
Name: sourceHelmArgs.secretRef,
|
Name: sourceHelmArgs.secretRef,
|
||||||
@@ -144,22 +163,41 @@ func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
caBundle := []byte{}
|
||||||
|
if sourceHelmArgs.caFile != "" {
|
||||||
|
var err error
|
||||||
|
caBundle, err = os.ReadFile(sourceHelmArgs.caFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to read TLS CA file: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var certFile, keyFile []byte
|
||||||
|
if sourceHelmArgs.certFile != "" && sourceHelmArgs.keyFile != "" {
|
||||||
|
if certFile, err = os.ReadFile(sourceHelmArgs.certFile); err != nil {
|
||||||
|
return fmt.Errorf("failed to read cert file: %w", err)
|
||||||
|
}
|
||||||
|
if keyFile, err = os.ReadFile(sourceHelmArgs.keyFile); err != nil {
|
||||||
|
return fmt.Errorf("failed to read key file: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
logger.Generatef("generating HelmRepository source")
|
logger.Generatef("generating HelmRepository source")
|
||||||
if sourceHelmArgs.secretRef == "" {
|
if sourceHelmArgs.secretRef == "" {
|
||||||
secretName := fmt.Sprintf("helm-%s", name)
|
secretName := fmt.Sprintf("helm-%s", name)
|
||||||
secretOpts := sourcesecret.Options{
|
secretOpts := sourcesecret.Options{
|
||||||
Name: secretName,
|
Name: secretName,
|
||||||
Namespace: rootArgs.namespace,
|
Namespace: *kubeconfigArgs.Namespace,
|
||||||
Username: sourceHelmArgs.username,
|
Username: sourceHelmArgs.username,
|
||||||
Password: sourceHelmArgs.password,
|
Password: sourceHelmArgs.password,
|
||||||
CertFilePath: sourceHelmArgs.certFile,
|
CAFile: caBundle,
|
||||||
KeyFilePath: sourceHelmArgs.keyFile,
|
CertFile: certFile,
|
||||||
CAFilePath: sourceHelmArgs.caFile,
|
KeyFile: keyFile,
|
||||||
ManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile,
|
ManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile,
|
||||||
}
|
}
|
||||||
secret, err := sourcesecret.Generate(secretOpts)
|
secret, err := sourcesecret.Generate(secretOpts)
|
||||||
@@ -196,6 +234,11 @@ func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
logger.Successf("HelmRepository source reconciliation completed")
|
logger.Successf("HelmRepository source reconciliation completed")
|
||||||
|
|
||||||
|
if helmRepository.Spec.Type == sourcev1.HelmRepositoryTypeOCI {
|
||||||
|
// OCI repos don't expose any artifact so we just return early here
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if helmRepository.Status.Artifact == nil {
|
if helmRepository.Status.Artifact == nil {
|
||||||
return fmt.Errorf("HelmRepository source reconciliation completed but no artifact was found")
|
return fmt.Errorf("HelmRepository source reconciliation completed but no artifact was found")
|
||||||
}
|
}
|
||||||
@@ -242,12 +285,14 @@ func isHelmRepositoryReady(ctx context.Context, kubeClient client.Client,
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Confirm the state we are observing is for the current generation
|
if c := conditions.Get(helmRepository, meta.ReadyCondition); c != nil {
|
||||||
if helmRepository.Generation != helmRepository.Status.ObservedGeneration {
|
// Confirm the Ready condition we are observing is for the
|
||||||
|
// current generation
|
||||||
|
if c.ObservedGeneration != helmRepository.GetGeneration() {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if c := apimeta.FindStatusCondition(helmRepository.Status.Conditions, meta.ReadyCondition); c != nil {
|
// Further check the Status
|
||||||
switch c.Status {
|
switch c.Status {
|
||||||
case metav1.ConditionTrue:
|
case metav1.ConditionTrue:
|
||||||
return true, nil
|
return true, nil
|
||||||
|
|||||||
81
cmd/flux/create_source_helm_test.go
Normal file
81
cmd/flux/create_source_helm_test.go
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
//go:build unit
|
||||||
|
// +build unit
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 2022 The Flux authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCreateSourceHelm(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args string
|
||||||
|
resultFile string
|
||||||
|
assertFunc string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no args",
|
||||||
|
args: "create source helm",
|
||||||
|
resultFile: "name is required",
|
||||||
|
assertFunc: "assertError",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "OCI repo",
|
||||||
|
args: "create source helm podinfo --url=oci://ghcr.io/stefanprodan/charts/podinfo --interval 5m --export",
|
||||||
|
resultFile: "./testdata/create_source_helm/oci.golden",
|
||||||
|
assertFunc: "assertGoldenTemplateFile",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "OCI repo with Secret ref",
|
||||||
|
args: "create source helm podinfo --url=oci://ghcr.io/stefanprodan/charts/podinfo --interval 5m --secret-ref=creds --export",
|
||||||
|
resultFile: "./testdata/create_source_helm/oci-with-secret.golden",
|
||||||
|
assertFunc: "assertGoldenTemplateFile",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "HTTPS repo",
|
||||||
|
args: "create source helm podinfo --url=https://stefanprodan.github.io/charts/podinfo --interval 5m --export",
|
||||||
|
resultFile: "./testdata/create_source_helm/https.golden",
|
||||||
|
assertFunc: "assertGoldenTemplateFile",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpl := map[string]string{
|
||||||
|
"fluxns": allocateNamespace("flux-system"),
|
||||||
|
}
|
||||||
|
setup(t, tmpl)
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var assert assertFunc
|
||||||
|
switch tt.assertFunc {
|
||||||
|
case "assertGoldenTemplateFile":
|
||||||
|
assert = assertGoldenTemplateFile(tt.resultFile, tmpl)
|
||||||
|
case "assertError":
|
||||||
|
assert = assertError(tt.resultFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := cmdTestCase{
|
||||||
|
args: tt.args + " -n " + tmpl["fluxns"],
|
||||||
|
assert: assert,
|
||||||
|
}
|
||||||
|
cmd.runTestCmd(t)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
247
cmd/flux/create_source_oci.go
Normal file
247
cmd/flux/create_source_oci.go
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 The Flux authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
|
||||||
|
"github.com/fluxcd/pkg/apis/meta"
|
||||||
|
"github.com/fluxcd/pkg/runtime/conditions"
|
||||||
|
|
||||||
|
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||||
|
|
||||||
|
"github.com/fluxcd/flux2/internal/flags"
|
||||||
|
"github.com/fluxcd/flux2/internal/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
var createSourceOCIRepositoryCmd = &cobra.Command{
|
||||||
|
Use: "oci [name]",
|
||||||
|
Short: "Create or update an OCIRepository",
|
||||||
|
Long: `The create source oci command generates an OCIRepository resource and waits for it to be ready.`,
|
||||||
|
Example: ` # Create an OCIRepository for a public container image
|
||||||
|
flux create source oci podinfo \
|
||||||
|
--url=oci://ghcr.io/stefanprodan/manifests/podinfo \
|
||||||
|
--tag=6.1.6 \
|
||||||
|
--interval=10m
|
||||||
|
`,
|
||||||
|
RunE: createSourceOCIRepositoryCmdRun,
|
||||||
|
}
|
||||||
|
|
||||||
|
type sourceOCIRepositoryFlags struct {
|
||||||
|
url string
|
||||||
|
tag string
|
||||||
|
semver string
|
||||||
|
digest string
|
||||||
|
secretRef string
|
||||||
|
serviceAccount string
|
||||||
|
certSecretRef string
|
||||||
|
ignorePaths []string
|
||||||
|
provider flags.SourceOCIProvider
|
||||||
|
insecure bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var sourceOCIRepositoryArgs = newSourceOCIFlags()
|
||||||
|
|
||||||
|
func newSourceOCIFlags() sourceOCIRepositoryFlags {
|
||||||
|
return sourceOCIRepositoryFlags{
|
||||||
|
provider: flags.SourceOCIProvider(sourcev1.GenericOCIProvider),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
createSourceOCIRepositoryCmd.Flags().Var(&sourceOCIRepositoryArgs.provider, "provider", sourceOCIRepositoryArgs.provider.Description())
|
||||||
|
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.url, "url", "", "the OCI repository URL")
|
||||||
|
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.tag, "tag", "", "the OCI artifact tag")
|
||||||
|
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.semver, "tag-semver", "", "the OCI artifact tag semver range")
|
||||||
|
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.digest, "digest", "", "the OCI artifact digest")
|
||||||
|
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.secretRef, "secret-ref", "", "the name of the Kubernetes image pull secret (type 'kubernetes.io/dockerconfigjson')")
|
||||||
|
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.serviceAccount, "service-account", "", "the name of the Kubernetes service account that refers to an image pull secret")
|
||||||
|
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.certSecretRef, "cert-ref", "", "the name of a secret to use for TLS certificates")
|
||||||
|
createSourceOCIRepositoryCmd.Flags().StringSliceVar(&sourceOCIRepositoryArgs.ignorePaths, "ignore-paths", nil, "set paths to ignore resources (can specify multiple paths with commas: path1,path2)")
|
||||||
|
createSourceOCIRepositoryCmd.Flags().BoolVar(&sourceOCIRepositoryArgs.insecure, "insecure", false, "for when connecting to a non-TLS registries over plain HTTP")
|
||||||
|
|
||||||
|
createSourceCmd.AddCommand(createSourceOCIRepositoryCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createSourceOCIRepositoryCmdRun(cmd *cobra.Command, args []string) error {
|
||||||
|
name := args[0]
|
||||||
|
|
||||||
|
if sourceOCIRepositoryArgs.url == "" {
|
||||||
|
return fmt.Errorf("url is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if sourceOCIRepositoryArgs.semver == "" && sourceOCIRepositoryArgs.tag == "" && sourceOCIRepositoryArgs.digest == "" {
|
||||||
|
return fmt.Errorf("--tag, --tag-semver or --digest is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceLabels, err := parseLabels()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var ignorePaths *string
|
||||||
|
if len(sourceOCIRepositoryArgs.ignorePaths) > 0 {
|
||||||
|
ignorePathsStr := strings.Join(sourceOCIRepositoryArgs.ignorePaths, "\n")
|
||||||
|
ignorePaths = &ignorePathsStr
|
||||||
|
}
|
||||||
|
|
||||||
|
repository := &sourcev1.OCIRepository{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: name,
|
||||||
|
Namespace: *kubeconfigArgs.Namespace,
|
||||||
|
Labels: sourceLabels,
|
||||||
|
},
|
||||||
|
Spec: sourcev1.OCIRepositorySpec{
|
||||||
|
Provider: sourceOCIRepositoryArgs.provider.String(),
|
||||||
|
URL: sourceOCIRepositoryArgs.url,
|
||||||
|
Insecure: sourceOCIRepositoryArgs.insecure,
|
||||||
|
Interval: metav1.Duration{
|
||||||
|
Duration: createArgs.interval,
|
||||||
|
},
|
||||||
|
Reference: &sourcev1.OCIRepositoryRef{},
|
||||||
|
Ignore: ignorePaths,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if digest := sourceOCIRepositoryArgs.digest; digest != "" {
|
||||||
|
repository.Spec.Reference.Digest = digest
|
||||||
|
}
|
||||||
|
if semver := sourceOCIRepositoryArgs.semver; semver != "" {
|
||||||
|
repository.Spec.Reference.SemVer = semver
|
||||||
|
}
|
||||||
|
if tag := sourceOCIRepositoryArgs.tag; tag != "" {
|
||||||
|
repository.Spec.Reference.Tag = tag
|
||||||
|
}
|
||||||
|
|
||||||
|
if createSourceArgs.fetchTimeout > 0 {
|
||||||
|
repository.Spec.Timeout = &metav1.Duration{Duration: createSourceArgs.fetchTimeout}
|
||||||
|
}
|
||||||
|
|
||||||
|
if saName := sourceOCIRepositoryArgs.serviceAccount; saName != "" {
|
||||||
|
repository.Spec.ServiceAccountName = saName
|
||||||
|
}
|
||||||
|
|
||||||
|
if secretName := sourceOCIRepositoryArgs.secretRef; secretName != "" {
|
||||||
|
repository.Spec.SecretRef = &meta.LocalObjectReference{
|
||||||
|
Name: secretName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if secretName := sourceOCIRepositoryArgs.certSecretRef; secretName != "" {
|
||||||
|
repository.Spec.CertSecretRef = &meta.LocalObjectReference{
|
||||||
|
Name: secretName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if createArgs.export {
|
||||||
|
return printExport(exportOCIRepository(repository))
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Actionf("applying OCIRepository")
|
||||||
|
namespacedName, err := upsertOCIRepository(ctx, kubeClient, repository)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Waitingf("waiting for OCIRepository reconciliation")
|
||||||
|
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
|
||||||
|
isOCIRepositoryReady(ctx, kubeClient, namespacedName, repository)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.Successf("OCIRepository reconciliation completed")
|
||||||
|
|
||||||
|
if repository.Status.Artifact == nil {
|
||||||
|
return fmt.Errorf("no artifact was found")
|
||||||
|
}
|
||||||
|
logger.Successf("fetched revision: %s", repository.Status.Artifact.Revision)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func upsertOCIRepository(ctx context.Context, kubeClient client.Client,
|
||||||
|
ociRepository *sourcev1.OCIRepository) (types.NamespacedName, error) {
|
||||||
|
namespacedName := types.NamespacedName{
|
||||||
|
Namespace: ociRepository.GetNamespace(),
|
||||||
|
Name: ociRepository.GetName(),
|
||||||
|
}
|
||||||
|
|
||||||
|
var existing sourcev1.OCIRepository
|
||||||
|
err := kubeClient.Get(ctx, namespacedName, &existing)
|
||||||
|
if err != nil {
|
||||||
|
if errors.IsNotFound(err) {
|
||||||
|
if err := kubeClient.Create(ctx, ociRepository); err != nil {
|
||||||
|
return namespacedName, err
|
||||||
|
} else {
|
||||||
|
logger.Successf("OCIRepository created")
|
||||||
|
return namespacedName, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return namespacedName, err
|
||||||
|
}
|
||||||
|
|
||||||
|
existing.Labels = ociRepository.Labels
|
||||||
|
existing.Spec = ociRepository.Spec
|
||||||
|
if err := kubeClient.Update(ctx, &existing); err != nil {
|
||||||
|
return namespacedName, err
|
||||||
|
}
|
||||||
|
ociRepository = &existing
|
||||||
|
logger.Successf("OCIRepository updated")
|
||||||
|
return namespacedName, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isOCIRepositoryReady(ctx context.Context, kubeClient client.Client,
|
||||||
|
namespacedName types.NamespacedName, ociRepository *sourcev1.OCIRepository) wait.ConditionFunc {
|
||||||
|
return func() (bool, error) {
|
||||||
|
err := kubeClient.Get(ctx, namespacedName, ociRepository)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if c := conditions.Get(ociRepository, meta.ReadyCondition); c != nil {
|
||||||
|
// Confirm the Ready condition we are observing is for the
|
||||||
|
// current generation
|
||||||
|
if c.ObservedGeneration != ociRepository.GetGeneration() {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Further check the Status
|
||||||
|
switch c.Status {
|
||||||
|
case metav1.ConditionTrue:
|
||||||
|
return true, nil
|
||||||
|
case metav1.ConditionFalse:
|
||||||
|
return false, fmt.Errorf(c.Message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
61
cmd/flux/create_source_oci_test.go
Normal file
61
cmd/flux/create_source_oci_test.go
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 The Flux authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCreateSourceOCI(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args string
|
||||||
|
assertFunc assertFunc
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "NoArgs",
|
||||||
|
args: "create source oci",
|
||||||
|
assertFunc: assertError("name is required"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "NoURL",
|
||||||
|
args: "create source oci podinfo",
|
||||||
|
assertFunc: assertError("url is required"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "export manifest",
|
||||||
|
args: "create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.1.6 --interval 10m --export",
|
||||||
|
assertFunc: assertGoldenFile("./testdata/oci/export.golden"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "export manifest with secret",
|
||||||
|
args: "create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.1.6 --interval 10m --secret-ref=creds --export",
|
||||||
|
assertFunc: assertGoldenFile("./testdata/oci/export_with_secret.golden"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
cmd := cmdTestCase{
|
||||||
|
args: tt.args,
|
||||||
|
assert: tt.assertFunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.runTestCmd(t)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -70,9 +70,6 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func createTenantCmdRun(cmd *cobra.Command, args []string) error {
|
func createTenantCmdRun(cmd *cobra.Command, args []string) error {
|
||||||
if len(args) < 1 {
|
|
||||||
return fmt.Errorf("tenant name is required")
|
|
||||||
}
|
|
||||||
tenant := args[0]
|
tenant := args[0]
|
||||||
if err := validation.IsQualifiedName(tenant); len(err) > 0 {
|
if err := validation.IsQualifiedName(tenant); len(err) > 0 {
|
||||||
return fmt.Errorf("invalid tenant name '%s': %v", tenant, err)
|
return fmt.Errorf("invalid tenant name '%s': %v", tenant, err)
|
||||||
@@ -148,7 +145,7 @@ func createTenantCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if createArgs.export {
|
if createArgs.export {
|
||||||
for i, _ := range tenantArgs.namespaces {
|
for i := range tenantArgs.namespaces {
|
||||||
if err := exportTenant(namespaces[i], accounts[i], roleBindings[i]); err != nil {
|
if err := exportTenant(namespaces[i], accounts[i], roleBindings[i]); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -159,12 +156,12 @@ func createTenantCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, _ := range tenantArgs.namespaces {
|
for i := range tenantArgs.namespaces {
|
||||||
logger.Actionf("applying namespace %s", namespaces[i].Name)
|
logger.Actionf("applying namespace %s", namespaces[i].Name)
|
||||||
if err := upsertNamespace(ctx, kubeClient, namespaces[i]); err != nil {
|
if err := upsertNamespace(ctx, kubeClient, namespaces[i]); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
55
cmd/flux/create_test.go
Normal file
55
cmd/flux/create_test.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/util/rand"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_validateObjectName(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
valid bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "flux-system",
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "-flux-system",
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "-flux-system-",
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "third.first",
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "THirdfirst",
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "THirdfirst",
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: rand.String(63),
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: rand.String(64),
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
valid := validateObjectName(tt.name)
|
||||||
|
if valid != tt.valid {
|
||||||
|
t.Errorf("expected name %q to return %t for validateObjectName func but got %t",
|
||||||
|
tt.name, tt.valid, valid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -60,13 +60,13 @@ func (del deleteCommand) run(cmd *cobra.Command, args []string) error {
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
namespacedName := types.NamespacedName{
|
namespacedName := types.NamespacedName{
|
||||||
Namespace: rootArgs.namespace,
|
Namespace: *kubeconfigArgs.Namespace,
|
||||||
Name: name,
|
Name: name,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +85,7 @@ func (del deleteCommand) run(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Actionf("deleting %s %s in %s namespace", del.humanKind, name, rootArgs.namespace)
|
logger.Actionf("deleting %s %s in %s namespace", del.humanKind, name, *kubeconfigArgs.Namespace)
|
||||||
err = kubeClient.Delete(ctx, del.object.asClientObject())
|
err = kubeClient.Delete(ctx, del.object.asClientObject())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
|
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var deleteAlertCmd = &cobra.Command{
|
var deleteAlertCmd = &cobra.Command{
|
||||||
@@ -28,6 +28,7 @@ var deleteAlertCmd = &cobra.Command{
|
|||||||
Long: "The delete alert command removes the given Alert from the cluster.",
|
Long: "The delete alert command removes the given Alert from the cluster.",
|
||||||
Example: ` # Delete an Alert and the Kubernetes resources created by it
|
Example: ` # Delete an Alert and the Kubernetes resources created by it
|
||||||
flux delete alert main`,
|
flux delete alert main`,
|
||||||
|
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.AlertKind)),
|
||||||
RunE: deleteCommand{
|
RunE: deleteCommand{
|
||||||
apiType: alertType,
|
apiType: alertType,
|
||||||
object: universalAdapter{¬ificationv1.Alert{}},
|
object: universalAdapter{¬ificationv1.Alert{}},
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
|
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var deleteAlertProviderCmd = &cobra.Command{
|
var deleteAlertProviderCmd = &cobra.Command{
|
||||||
@@ -28,6 +28,7 @@ var deleteAlertProviderCmd = &cobra.Command{
|
|||||||
Long: "The delete alert-provider command removes the given Provider from the cluster.",
|
Long: "The delete alert-provider command removes the given Provider from the cluster.",
|
||||||
Example: ` # Delete a Provider and the Kubernetes resources created by it
|
Example: ` # Delete a Provider and the Kubernetes resources created by it
|
||||||
flux delete alert-provider slack`,
|
flux delete alert-provider slack`,
|
||||||
|
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ProviderKind)),
|
||||||
RunE: deleteCommand{
|
RunE: deleteCommand{
|
||||||
apiType: alertProviderType,
|
apiType: alertProviderType,
|
||||||
object: universalAdapter{¬ificationv1.Provider{}},
|
object: universalAdapter{¬ificationv1.Provider{}},
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ var deleteHelmReleaseCmd = &cobra.Command{
|
|||||||
Long: "The delete helmrelease command removes the given HelmRelease from the cluster.",
|
Long: "The delete helmrelease command removes the given HelmRelease from the cluster.",
|
||||||
Example: ` # Delete a Helm release and the Kubernetes resources created by it
|
Example: ` # Delete a Helm release and the Kubernetes resources created by it
|
||||||
flux delete hr podinfo`,
|
flux delete hr podinfo`,
|
||||||
|
ValidArgsFunction: resourceNamesCompletionFunc(helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)),
|
||||||
RunE: deleteCommand{
|
RunE: deleteCommand{
|
||||||
apiType: helmReleaseType,
|
apiType: helmReleaseType,
|
||||||
object: universalAdapter{&helmv2.HelmRelease{}},
|
object: universalAdapter{&helmv2.HelmRelease{}},
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha2"
|
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta1"
|
||||||
)
|
)
|
||||||
|
|
||||||
var deleteImagePolicyCmd = &cobra.Command{
|
var deleteImagePolicyCmd = &cobra.Command{
|
||||||
@@ -28,6 +28,7 @@ var deleteImagePolicyCmd = &cobra.Command{
|
|||||||
Long: "The delete image policy command deletes the given ImagePolicy from the cluster.",
|
Long: "The delete image policy command deletes the given ImagePolicy from the cluster.",
|
||||||
Example: ` # Delete an image policy
|
Example: ` # Delete an image policy
|
||||||
flux delete image policy alpine3.x`,
|
flux delete image policy alpine3.x`,
|
||||||
|
ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImagePolicyKind)),
|
||||||
RunE: deleteCommand{
|
RunE: deleteCommand{
|
||||||
apiType: imagePolicyType,
|
apiType: imagePolicyType,
|
||||||
object: universalAdapter{&imagev1.ImagePolicy{}},
|
object: universalAdapter{&imagev1.ImagePolicy{}},
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha2"
|
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta1"
|
||||||
)
|
)
|
||||||
|
|
||||||
var deleteImageRepositoryCmd = &cobra.Command{
|
var deleteImageRepositoryCmd = &cobra.Command{
|
||||||
@@ -28,6 +28,7 @@ var deleteImageRepositoryCmd = &cobra.Command{
|
|||||||
Long: "The delete image repository command deletes the given ImageRepository from the cluster.",
|
Long: "The delete image repository command deletes the given ImageRepository from the cluster.",
|
||||||
Example: ` # Delete an image repository
|
Example: ` # Delete an image repository
|
||||||
flux delete image repository alpine`,
|
flux delete image repository alpine`,
|
||||||
|
ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImageRepositoryKind)),
|
||||||
RunE: deleteCommand{
|
RunE: deleteCommand{
|
||||||
apiType: imageRepositoryType,
|
apiType: imageRepositoryType,
|
||||||
object: universalAdapter{&imagev1.ImageRepository{}},
|
object: universalAdapter{&imagev1.ImageRepository{}},
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
autov1 "github.com/fluxcd/image-automation-controller/api/v1alpha2"
|
autov1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
|
||||||
)
|
)
|
||||||
|
|
||||||
var deleteImageUpdateCmd = &cobra.Command{
|
var deleteImageUpdateCmd = &cobra.Command{
|
||||||
@@ -28,6 +28,7 @@ var deleteImageUpdateCmd = &cobra.Command{
|
|||||||
Long: "The delete image update command deletes the given ImageUpdateAutomation from the cluster.",
|
Long: "The delete image update command deletes the given ImageUpdateAutomation from the cluster.",
|
||||||
Example: ` # Delete an image update automation
|
Example: ` # Delete an image update automation
|
||||||
flux delete image update latest-images`,
|
flux delete image update latest-images`,
|
||||||
|
ValidArgsFunction: resourceNamesCompletionFunc(autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)),
|
||||||
RunE: deleteCommand{
|
RunE: deleteCommand{
|
||||||
apiType: imageUpdateAutomationType,
|
apiType: imageUpdateAutomationType,
|
||||||
object: universalAdapter{&autov1.ImageUpdateAutomation{}},
|
object: universalAdapter{&autov1.ImageUpdateAutomation{}},
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
|
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var deleteKsCmd = &cobra.Command{
|
var deleteKsCmd = &cobra.Command{
|
||||||
@@ -27,8 +27,9 @@ var deleteKsCmd = &cobra.Command{
|
|||||||
Aliases: []string{"ks"},
|
Aliases: []string{"ks"},
|
||||||
Short: "Delete a Kustomization resource",
|
Short: "Delete a Kustomization resource",
|
||||||
Long: "The delete kustomization command deletes the given Kustomization from the cluster.",
|
Long: "The delete kustomization command deletes the given Kustomization from the cluster.",
|
||||||
Example: ` # Delete a kustomization and the Kubernetes resources created by it
|
Example: ` # Delete a kustomization and the Kubernetes resources created by it when prune is enabled
|
||||||
flux delete kustomization podinfo`,
|
flux delete kustomization podinfo`,
|
||||||
|
ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
|
||||||
RunE: deleteCommand{
|
RunE: deleteCommand{
|
||||||
apiType: kustomizationType,
|
apiType: kustomizationType,
|
||||||
object: universalAdapter{&kustomizev1.Kustomization{}},
|
object: universalAdapter{&kustomizev1.Kustomization{}},
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
|
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var deleteReceiverCmd = &cobra.Command{
|
var deleteReceiverCmd = &cobra.Command{
|
||||||
@@ -28,6 +28,7 @@ var deleteReceiverCmd = &cobra.Command{
|
|||||||
Long: "The delete receiver command removes the given Receiver from the cluster.",
|
Long: "The delete receiver command removes the given Receiver from the cluster.",
|
||||||
Example: ` # Delete an Receiver and the Kubernetes resources created by it
|
Example: ` # Delete an Receiver and the Kubernetes resources created by it
|
||||||
flux delete receiver main`,
|
flux delete receiver main`,
|
||||||
|
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ReceiverKind)),
|
||||||
RunE: deleteCommand{
|
RunE: deleteCommand{
|
||||||
apiType: receiverType,
|
apiType: receiverType,
|
||||||
object: universalAdapter{¬ificationv1.Receiver{}},
|
object: universalAdapter{¬ificationv1.Receiver{}},
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var deleteSourceBucketCmd = &cobra.Command{
|
var deleteSourceBucketCmd = &cobra.Command{
|
||||||
@@ -28,6 +28,7 @@ var deleteSourceBucketCmd = &cobra.Command{
|
|||||||
Long: "The delete source bucket command deletes the given Bucket from the cluster.",
|
Long: "The delete source bucket command deletes the given Bucket from the cluster.",
|
||||||
Example: ` # Delete a Bucket source
|
Example: ` # Delete a Bucket source
|
||||||
flux delete source bucket podinfo`,
|
flux delete source bucket podinfo`,
|
||||||
|
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.BucketKind)),
|
||||||
RunE: deleteCommand{
|
RunE: deleteCommand{
|
||||||
apiType: bucketType,
|
apiType: bucketType,
|
||||||
object: universalAdapter{&sourcev1.Bucket{}},
|
object: universalAdapter{&sourcev1.Bucket{}},
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var deleteSourceGitCmd = &cobra.Command{
|
var deleteSourceGitCmd = &cobra.Command{
|
||||||
@@ -28,6 +28,7 @@ var deleteSourceGitCmd = &cobra.Command{
|
|||||||
Long: "The delete source git command deletes the given GitRepository from the cluster.",
|
Long: "The delete source git command deletes the given GitRepository from the cluster.",
|
||||||
Example: ` # Delete a Git repository
|
Example: ` # Delete a Git repository
|
||||||
flux delete source git podinfo`,
|
flux delete source git podinfo`,
|
||||||
|
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.GitRepositoryKind)),
|
||||||
RunE: deleteCommand{
|
RunE: deleteCommand{
|
||||||
apiType: gitRepositoryType,
|
apiType: gitRepositoryType,
|
||||||
object: universalAdapter{&sourcev1.GitRepository{}},
|
object: universalAdapter{&sourcev1.GitRepository{}},
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var deleteSourceHelmCmd = &cobra.Command{
|
var deleteSourceHelmCmd = &cobra.Command{
|
||||||
@@ -28,6 +28,7 @@ var deleteSourceHelmCmd = &cobra.Command{
|
|||||||
Long: "The delete source helm command deletes the given HelmRepository from the cluster.",
|
Long: "The delete source helm command deletes the given HelmRepository from the cluster.",
|
||||||
Example: ` # Delete a Helm repository
|
Example: ` # Delete a Helm repository
|
||||||
flux delete source helm podinfo`,
|
flux delete source helm podinfo`,
|
||||||
|
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmRepositoryKind)),
|
||||||
RunE: deleteCommand{
|
RunE: deleteCommand{
|
||||||
apiType: helmRepositoryType,
|
apiType: helmRepositoryType,
|
||||||
object: universalAdapter{&sourcev1.HelmRepository{}},
|
object: universalAdapter{&sourcev1.HelmRepository{}},
|
||||||
|
|||||||
40
cmd/flux/delete_source_oci.go
Normal file
40
cmd/flux/delete_source_oci.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 The Flux authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var deleteSourceOCIRepositoryCmd = &cobra.Command{
|
||||||
|
Use: "oci [name]",
|
||||||
|
Short: "Delete an OCIRepository source",
|
||||||
|
Long: "The delete source oci command deletes the given OCIRepository from the cluster.",
|
||||||
|
Example: ` # Delete an OCIRepository
|
||||||
|
flux delete source oci podinfo`,
|
||||||
|
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.OCIRepositoryKind)),
|
||||||
|
RunE: deleteCommand{
|
||||||
|
apiType: ociRepositoryType,
|
||||||
|
object: universalAdapter{&sourcev1.OCIRepository{}},
|
||||||
|
}.run,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
deleteSourceCmd.AddCommand(deleteSourceOCIRepositoryCmd)
|
||||||
|
}
|
||||||
31
cmd/flux/diff.go
Normal file
31
cmd/flux/diff.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2021 The Flux authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var diffCmd = &cobra.Command{
|
||||||
|
Use: "diff",
|
||||||
|
Short: "Diff a flux resource",
|
||||||
|
Long: "The diff command is used to do a server-side dry-run on flux resources, then prints the diff.",
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(diffCmd)
|
||||||
|
}
|
||||||
111
cmd/flux/diff_artifact.go
Normal file
111
cmd/flux/diff_artifact.go
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 The Flux authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/fluxcd/flux2/internal/flags"
|
||||||
|
oci "github.com/fluxcd/pkg/oci/client"
|
||||||
|
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var diffArtifactCmd = &cobra.Command{
|
||||||
|
Use: "artifact",
|
||||||
|
Short: "Diff Artifact",
|
||||||
|
Long: `The diff artifact command computes the diff between the remote OCI artifact and a local directory or file`,
|
||||||
|
Example: `# Check if local files differ from remote
|
||||||
|
flux diff artifact oci://ghcr.io/stefanprodan/manifests:podinfo:6.2.0 --path=./kustomize`,
|
||||||
|
RunE: diffArtifactCmdRun,
|
||||||
|
}
|
||||||
|
|
||||||
|
type diffArtifactFlags struct {
|
||||||
|
path string
|
||||||
|
creds string
|
||||||
|
provider flags.SourceOCIProvider
|
||||||
|
ignorePaths []string
|
||||||
|
}
|
||||||
|
|
||||||
|
var diffArtifactArgs = newDiffArtifactArgs()
|
||||||
|
|
||||||
|
func newDiffArtifactArgs() diffArtifactFlags {
|
||||||
|
return diffArtifactFlags{
|
||||||
|
provider: flags.SourceOCIProvider(sourcev1.GenericOCIProvider),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
diffArtifactCmd.Flags().StringVar(&diffArtifactArgs.path, "path", "", "path to the directory where the Kubernetes manifests are located")
|
||||||
|
diffArtifactCmd.Flags().StringVar(&diffArtifactArgs.creds, "creds", "", "credentials for OCI registry in the format <username>[:<password>] if --provider is generic")
|
||||||
|
diffArtifactCmd.Flags().Var(&diffArtifactArgs.provider, "provider", sourceOCIRepositoryArgs.provider.Description())
|
||||||
|
diffArtifactCmd.Flags().StringSliceVar(&diffArtifactArgs.ignorePaths, "ignore-paths", excludeOCI, "set paths to ignore in .gitignore format")
|
||||||
|
diffCmd.AddCommand(diffArtifactCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func diffArtifactCmdRun(cmd *cobra.Command, args []string) error {
|
||||||
|
if len(args) < 1 {
|
||||||
|
return fmt.Errorf("artifact URL is required")
|
||||||
|
}
|
||||||
|
ociURL := args[0]
|
||||||
|
|
||||||
|
if diffArtifactArgs.path == "" {
|
||||||
|
return fmt.Errorf("invalid path %q", diffArtifactArgs.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
url, err := oci.ParseArtifactURL(ociURL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := os.Stat(diffArtifactArgs.path); err != nil {
|
||||||
|
return fmt.Errorf("invalid path '%s', must point to an existing directory or file", diffArtifactArgs.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
ociClient := oci.NewLocalClient()
|
||||||
|
|
||||||
|
if diffArtifactArgs.provider.String() == sourcev1.GenericOCIProvider && diffArtifactArgs.creds != "" {
|
||||||
|
logger.Actionf("logging in to registry with credentials")
|
||||||
|
if err := ociClient.LoginWithCredentials(diffArtifactArgs.creds); err != nil {
|
||||||
|
return fmt.Errorf("could not login with credentials: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if diffArtifactArgs.provider.String() != sourcev1.GenericOCIProvider {
|
||||||
|
logger.Actionf("logging in to registry with provider credentials")
|
||||||
|
ociProvider, err := diffArtifactArgs.provider.ToOCIProvider()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("provider not supported: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ociClient.LoginWithProvider(ctx, url, ociProvider); err != nil {
|
||||||
|
return fmt.Errorf("error during login with provider: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ociClient.Diff(ctx, url, diffArtifactArgs.path, diffArtifactArgs.ignorePaths); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Successf("no changes detected")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
109
cmd/flux/diff_artifact_test.go
Normal file
109
cmd/flux/diff_artifact_test.go
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
//go:build unit
|
||||||
|
// +build unit
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 2021 The Flux authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/distribution/distribution/v3/configuration"
|
||||||
|
"github.com/distribution/distribution/v3/registry"
|
||||||
|
_ "github.com/distribution/distribution/v3/registry/auth/htpasswd"
|
||||||
|
_ "github.com/distribution/distribution/v3/registry/storage/driver/inmemory"
|
||||||
|
"github.com/phayes/freeport"
|
||||||
|
ctrl "sigs.k8s.io/controller-runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
var dockerReg string
|
||||||
|
|
||||||
|
func setupRegistryServer(ctx context.Context) error {
|
||||||
|
// Registry config
|
||||||
|
config := &configuration.Configuration{}
|
||||||
|
port, err := freeport.GetFreePort()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get free port: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dockerReg = fmt.Sprintf("localhost:%d", port)
|
||||||
|
config.HTTP.Addr = fmt.Sprintf("127.0.0.1:%d", port)
|
||||||
|
config.HTTP.DrainTimeout = time.Duration(10) * time.Second
|
||||||
|
config.Storage = map[string]configuration.Parameters{"inmemory": map[string]interface{}{}}
|
||||||
|
dockerRegistry, err := registry.NewRegistry(ctx, config)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create docker registry: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start Docker registry
|
||||||
|
go dockerRegistry.ListenAndServe()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDiffArtifact(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
url string
|
||||||
|
argsTpl string
|
||||||
|
pushFile string
|
||||||
|
diffFile string
|
||||||
|
assert assertFunc
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "should not fail if there is no diff",
|
||||||
|
url: "oci://%s/podinfo:1.0.0",
|
||||||
|
argsTpl: "diff artifact %s --path=%s",
|
||||||
|
pushFile: "./testdata/diff-artifact/deployment.yaml",
|
||||||
|
diffFile: "./testdata/diff-artifact/deployment.yaml",
|
||||||
|
assert: assertGoldenFile("testdata/diff-artifact/success.golden"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should fail if there is a diff",
|
||||||
|
url: "oci://%s/podinfo:2.0.0",
|
||||||
|
argsTpl: "diff artifact %s --path=%s",
|
||||||
|
pushFile: "./testdata/diff-artifact/deployment.yaml",
|
||||||
|
diffFile: "./testdata/diff-artifact/deployment-diff.yaml",
|
||||||
|
assert: assertError("the remote artifact contents differs from the local one"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := ctrl.SetupSignalHandler()
|
||||||
|
err := setupRegistryServer(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("failed to start docker registry: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tt.url = fmt.Sprintf(tt.url, dockerReg)
|
||||||
|
_, err := executeCommand("push artifact " + tt.url + " --path=" + tt.pushFile + " --source=test --revision=test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(fmt.Errorf("failed to push image: %w", err).Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := cmdTestCase{
|
||||||
|
args: fmt.Sprintf(tt.argsTpl, tt.url, tt.diffFile),
|
||||||
|
assert: tt.assert,
|
||||||
|
}
|
||||||
|
cmd.runTestCmd(t)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
133
cmd/flux/diff_kustomization.go
Normal file
133
cmd/flux/diff_kustomization.go
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2021 The Flux authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/fluxcd/flux2/internal/build"
|
||||||
|
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var diffKsCmd = &cobra.Command{
|
||||||
|
Use: "kustomization",
|
||||||
|
Aliases: []string{"ks"},
|
||||||
|
Short: "Diff Kustomization",
|
||||||
|
Long: `The diff command does a build, then it performs a server-side dry-run and prints the diff.
|
||||||
|
Exit status: 0 No differences were found. 1 Differences were found. >1 diff failed with an error.`,
|
||||||
|
Example: `# Preview local changes as they were applied on the cluster
|
||||||
|
flux diff kustomization my-app --path ./path/to/local/manifests
|
||||||
|
|
||||||
|
# Preview using a local flux kustomization file
|
||||||
|
flux diff kustomization my-app --path ./path/to/local/manifests --kustomization-file ./path/to/local/my-app.yaml`,
|
||||||
|
ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
|
||||||
|
RunE: diffKsCmdRun,
|
||||||
|
}
|
||||||
|
|
||||||
|
type diffKsFlags struct {
|
||||||
|
kustomizationFile string
|
||||||
|
path string
|
||||||
|
progressBar bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var diffKsArgs diffKsFlags
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
diffKsCmd.Flags().StringVar(&diffKsArgs.path, "path", "", "Path to a local directory that matches the specified Kustomization.spec.path.")
|
||||||
|
diffKsCmd.Flags().BoolVar(&diffKsArgs.progressBar, "progress-bar", true, "Boolean to set the progress bar. The default value is true.")
|
||||||
|
diffKsCmd.Flags().StringVar(&diffKsArgs.kustomizationFile, "kustomization-file", "", "Path to the Flux Kustomization YAML file.")
|
||||||
|
diffCmd.AddCommand(diffKsCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func diffKsCmdRun(cmd *cobra.Command, args []string) error {
|
||||||
|
if len(args) < 1 {
|
||||||
|
return fmt.Errorf("%s name is required", kustomizationType.humanKind)
|
||||||
|
}
|
||||||
|
name := args[0]
|
||||||
|
|
||||||
|
if diffKsArgs.path == "" {
|
||||||
|
return &RequestError{StatusCode: 2, Err: fmt.Errorf("invalid resource path %q", diffKsArgs.path)}
|
||||||
|
}
|
||||||
|
|
||||||
|
if fs, err := os.Stat(diffKsArgs.path); err != nil || !fs.IsDir() {
|
||||||
|
return &RequestError{StatusCode: 2, Err: fmt.Errorf("invalid resource path %q", diffKsArgs.path)}
|
||||||
|
}
|
||||||
|
|
||||||
|
if diffKsArgs.kustomizationFile != "" {
|
||||||
|
if fs, err := os.Stat(diffKsArgs.kustomizationFile); os.IsNotExist(err) || fs.IsDir() {
|
||||||
|
return fmt.Errorf("invalid kustomization file %q", diffKsArgs.kustomizationFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
builder *build.Builder
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if diffKsArgs.progressBar {
|
||||||
|
builder, err = build.NewBuilder(name, diffKsArgs.path,
|
||||||
|
build.WithClientConfig(kubeconfigArgs, kubeclientOptions),
|
||||||
|
build.WithTimeout(rootArgs.timeout),
|
||||||
|
build.WithKustomizationFile(diffKsArgs.kustomizationFile),
|
||||||
|
build.WithProgressBar())
|
||||||
|
} else {
|
||||||
|
builder, err = build.NewBuilder(name, diffKsArgs.path,
|
||||||
|
build.WithClientConfig(kubeconfigArgs, kubeclientOptions),
|
||||||
|
build.WithTimeout(rootArgs.timeout),
|
||||||
|
build.WithKustomizationFile(diffKsArgs.kustomizationFile))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return &RequestError{StatusCode: 2, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a signal channel
|
||||||
|
sigc := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sigc, os.Interrupt)
|
||||||
|
|
||||||
|
errChan := make(chan error)
|
||||||
|
go func() {
|
||||||
|
output, hasChanged, err := builder.Diff()
|
||||||
|
if err != nil {
|
||||||
|
errChan <- &RequestError{StatusCode: 2, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Print(output)
|
||||||
|
|
||||||
|
if hasChanged {
|
||||||
|
errChan <- &RequestError{StatusCode: 1, Err: fmt.Errorf("identified at least one change, exiting with non-zero exit code")}
|
||||||
|
} else {
|
||||||
|
errChan <- nil
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-sigc:
|
||||||
|
fmt.Println("Build cancelled... exiting.")
|
||||||
|
return builder.Cancel()
|
||||||
|
case err := <-errChan:
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
145
cmd/flux/diff_kustomization_test.go
Normal file
145
cmd/flux/diff_kustomization_test.go
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
//go:build unit
|
||||||
|
// +build unit
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 2021 The Flux authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/fluxcd/flux2/internal/build"
|
||||||
|
"github.com/fluxcd/pkg/ssa"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDiffKustomization(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args string
|
||||||
|
objectFile string
|
||||||
|
assert assertFunc
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no args",
|
||||||
|
args: "diff kustomization podinfo",
|
||||||
|
objectFile: "",
|
||||||
|
assert: assertError("invalid resource path \"\""),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "diff nothing deployed",
|
||||||
|
args: "diff kustomization podinfo --path ./testdata/build-kustomization/podinfo --progress-bar=false",
|
||||||
|
objectFile: "",
|
||||||
|
assert: assertGoldenFile("./testdata/diff-kustomization/nothing-is-deployed.golden"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "diff with a deployment object",
|
||||||
|
args: "diff kustomization podinfo --path ./testdata/build-kustomization/podinfo --progress-bar=false",
|
||||||
|
objectFile: "./testdata/diff-kustomization/deployment.yaml",
|
||||||
|
assert: assertGoldenFile("./testdata/diff-kustomization/diff-with-deployment.golden"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "diff with a drifted service object",
|
||||||
|
args: "diff kustomization podinfo --path ./testdata/build-kustomization/podinfo --progress-bar=false",
|
||||||
|
objectFile: "./testdata/diff-kustomization/service.yaml",
|
||||||
|
assert: assertGoldenFile("./testdata/diff-kustomization/diff-with-drifted-service.golden"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "diff with a drifted secret object",
|
||||||
|
args: "diff kustomization podinfo --path ./testdata/build-kustomization/podinfo --progress-bar=false",
|
||||||
|
objectFile: "./testdata/diff-kustomization/secret.yaml",
|
||||||
|
assert: assertGoldenFile("./testdata/diff-kustomization/diff-with-drifted-secret.golden"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "diff with a drifted key in sops secret object",
|
||||||
|
args: "diff kustomization podinfo --path ./testdata/build-kustomization/podinfo --progress-bar=false",
|
||||||
|
objectFile: "./testdata/diff-kustomization/key-sops-secret.yaml",
|
||||||
|
assert: assertGoldenFile("./testdata/diff-kustomization/diff-with-drifted-key-sops-secret.golden"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "diff with a drifted value in sops secret object",
|
||||||
|
args: "diff kustomization podinfo --path ./testdata/build-kustomization/podinfo --progress-bar=false",
|
||||||
|
objectFile: "./testdata/diff-kustomization/value-sops-secret.yaml",
|
||||||
|
assert: assertGoldenFile("./testdata/diff-kustomization/diff-with-drifted-value-sops-secret.golden"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "diff with a sops dockerconfigjson secret object",
|
||||||
|
args: "diff kustomization podinfo --path ./testdata/build-kustomization/podinfo --progress-bar=false",
|
||||||
|
objectFile: "./testdata/diff-kustomization/dockerconfigjson-sops-secret.yaml",
|
||||||
|
assert: assertGoldenFile("./testdata/diff-kustomization/diff-with-dockerconfigjson-sops-secret.golden"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "diff with a sops stringdata secret object",
|
||||||
|
args: "diff kustomization podinfo --path ./testdata/build-kustomization/podinfo --progress-bar=false",
|
||||||
|
objectFile: "./testdata/diff-kustomization/stringdata-sops-secret.yaml",
|
||||||
|
assert: assertGoldenFile("./testdata/diff-kustomization/diff-with-drifted-stringdata-sops-secret.golden"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpl := map[string]string{
|
||||||
|
"fluxns": allocateNamespace("flux-system"),
|
||||||
|
}
|
||||||
|
|
||||||
|
b, _ := build.NewBuilder("podinfo", "", build.WithClientConfig(kubeconfigArgs, kubeclientOptions))
|
||||||
|
|
||||||
|
resourceManager, err := b.Manager()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
setup(t, tmpl)
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if tt.objectFile != "" {
|
||||||
|
resourceManager.ApplyAll(context.Background(), createObjectFromFile(tt.objectFile, tmpl, t), ssa.DefaultApplyOptions())
|
||||||
|
}
|
||||||
|
cmd := cmdTestCase{
|
||||||
|
args: tt.args + " -n " + tmpl["fluxns"],
|
||||||
|
assert: tt.assert,
|
||||||
|
}
|
||||||
|
cmd.runTestCmd(t)
|
||||||
|
if tt.objectFile != "" {
|
||||||
|
testEnv.DeleteObjectFile(tt.objectFile, tmpl, t)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createObjectFromFile(objectFile string, templateValues map[string]string, t *testing.T) []*unstructured.Unstructured {
|
||||||
|
buf, err := os.ReadFile(objectFile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error reading file '%s': %v", objectFile, err)
|
||||||
|
}
|
||||||
|
content, err := executeTemplate(string(buf), templateValues)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error evaluating template file '%s': '%v'", objectFile, err)
|
||||||
|
}
|
||||||
|
clientObjects, err := readYamlObjects(strings.NewReader(content))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error decoding yaml file '%s': %v", objectFile, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ssa.SetNativeKindsDefaults(clientObjects); err != nil {
|
||||||
|
t.Fatalf("Error setting native kinds defaults for '%s': %v", objectFile, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return clientObjects
|
||||||
|
}
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2021 The Flux authors
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"filippo.io/age"
|
|
||||||
"github.com/fluxcd/flux2/internal/utils"
|
|
||||||
"github.com/go-git/go-git/v5"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
|
||||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
var encryptInitCmd = &cobra.Command{
|
|
||||||
Use: "init",
|
|
||||||
Short: "Init SOPS encryption with age identity",
|
|
||||||
Long: "The encryption init command creates a new age identity and writes a .sops.yaml file to the current working directory.",
|
|
||||||
Example: ` # Init SOPS encryption with a new age identity
|
|
||||||
flux encryption init`,
|
|
||||||
RunE: encryptInitCmdRun,
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
encryptCmd.AddCommand(encryptInitCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func encryptInitCmdRun(cmd *cobra.Command, args []string) error {
|
|
||||||
// Confirm our current path is in a Git repository
|
|
||||||
path, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, err := git.PlainOpen(path); err != nil {
|
|
||||||
if err == git.ErrRepositoryNotExists {
|
|
||||||
err = fmt.Errorf("'%s' is not in a Git repository", path)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Abort early if .sops.yaml already exists
|
|
||||||
sopsCfgPath := filepath.Join(path, ".sops.yaml")
|
|
||||||
if _, err := os.Stat(sopsCfgPath); err == nil || os.IsExist(err) {
|
|
||||||
return fmt.Errorf("'%s' already contains a .sops.yaml config", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate a new identity
|
|
||||||
i, err := age.GenerateX25519Identity()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.Successf("Generated identity %s", i.Recipient().String())
|
|
||||||
|
|
||||||
// Attempt to configure identity in .sops.yaml
|
|
||||||
const sopsCfg = `creation_rules:
|
|
||||||
- path_regex: .*.yaml
|
|
||||||
encrypted_regex: ^(data|stringData)$
|
|
||||||
age: %s
|
|
||||||
`
|
|
||||||
if err := ioutil.WriteFile(sopsCfgPath, []byte(fmt.Sprintf(sopsCfg, i.Recipient().String())), 0644); err != nil {
|
|
||||||
logger.Failuref("Failed to write recipient to .sops.yaml file")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.Successf("Configured recipient in .sops.yaml file")
|
|
||||||
|
|
||||||
// Init client
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
|
||||||
defer cancel()
|
|
||||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a secret
|
|
||||||
secret := &corev1.Secret{
|
|
||||||
ObjectMeta: v1.ObjectMeta{
|
|
||||||
Name: "sops-age",
|
|
||||||
Namespace: rootArgs.namespace,
|
|
||||||
},
|
|
||||||
StringData: map[string]string{
|
|
||||||
"flux-auto.age": i.String(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if err := kubeClient.Create(ctx, secret); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.Successf(`Secret '%s' with private key created`, secret.Name)
|
|
||||||
|
|
||||||
// TODO(hidde): lookup kustomize based on path ref? Do direct cluster mutation? (Preferably not!)
|
|
||||||
// Feels something is missing in general to provide a user experience improving bridge between "die hard"
|
|
||||||
// `--export` and "please do not do this" direct-apply-to-cluster.
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -20,6 +20,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
@@ -73,19 +74,19 @@ func (export exportCommand) run(cmd *cobra.Command, args []string) error {
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if exportArgs.all {
|
if exportArgs.all {
|
||||||
err = kubeClient.List(ctx, export.list.asClientList(), client.InNamespace(rootArgs.namespace))
|
err = kubeClient.List(ctx, export.list.asClientList(), client.InNamespace(*kubeconfigArgs.Namespace))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if export.list.len() == 0 {
|
if export.list.len() == 0 {
|
||||||
return fmt.Errorf("no objects found in %s namespace", rootArgs.namespace)
|
return fmt.Errorf("no objects found in %s namespace", *kubeconfigArgs.Namespace)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < export.list.len(); i++ {
|
for i := 0; i < export.list.len(); i++ {
|
||||||
@@ -96,7 +97,7 @@ func (export exportCommand) run(cmd *cobra.Command, args []string) error {
|
|||||||
} else {
|
} else {
|
||||||
name := args[0]
|
name := args[0]
|
||||||
namespacedName := types.NamespacedName{
|
namespacedName := types.NamespacedName{
|
||||||
Namespace: rootArgs.namespace,
|
Namespace: *kubeconfigArgs.Namespace,
|
||||||
Name: name,
|
Name: name,
|
||||||
}
|
}
|
||||||
err = kubeClient.Get(ctx, namespacedName, export.object.asClientObject())
|
err = kubeClient.Get(ctx, namespacedName, export.object.asClientObject())
|
||||||
@@ -113,8 +114,8 @@ func printExport(export interface{}) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Println("---")
|
rootCmd.Println("---")
|
||||||
fmt.Println(resourceToString(data))
|
rootCmd.Println(resourceToString(data))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
|
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var exportAlertCmd = &cobra.Command{
|
var exportAlertCmd = &cobra.Command{
|
||||||
@@ -32,6 +32,7 @@ var exportAlertCmd = &cobra.Command{
|
|||||||
|
|
||||||
# Export a Alert
|
# Export a Alert
|
||||||
flux export alert main > main.yaml`,
|
flux export alert main > main.yaml`,
|
||||||
|
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.AlertKind)),
|
||||||
RunE: exportCommand{
|
RunE: exportCommand{
|
||||||
object: alertAdapter{¬ificationv1.Alert{}},
|
object: alertAdapter{¬ificationv1.Alert{}},
|
||||||
list: alertListAdapter{¬ificationv1.AlertList{}},
|
list: alertListAdapter{¬ificationv1.AlertList{}},
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
|
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var exportAlertProviderCmd = &cobra.Command{
|
var exportAlertProviderCmd = &cobra.Command{
|
||||||
@@ -32,6 +32,7 @@ var exportAlertProviderCmd = &cobra.Command{
|
|||||||
|
|
||||||
# Export a Provider
|
# Export a Provider
|
||||||
flux export alert-provider slack > slack.yaml`,
|
flux export alert-provider slack > slack.yaml`,
|
||||||
|
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ProviderKind)),
|
||||||
RunE: exportCommand{
|
RunE: exportCommand{
|
||||||
object: alertProviderAdapter{¬ificationv1.Provider{}},
|
object: alertProviderAdapter{¬ificationv1.Provider{}},
|
||||||
list: alertProviderListAdapter{¬ificationv1.ProviderList{}},
|
list: alertProviderListAdapter{¬ificationv1.ProviderList{}},
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ var exportHelmReleaseCmd = &cobra.Command{
|
|||||||
|
|
||||||
# Export a HelmRelease
|
# Export a HelmRelease
|
||||||
flux export hr my-app > app-release.yaml`,
|
flux export hr my-app > app-release.yaml`,
|
||||||
|
ValidArgsFunction: resourceNamesCompletionFunc(helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)),
|
||||||
RunE: exportCommand{
|
RunE: exportCommand{
|
||||||
object: helmReleaseAdapter{&helmv2.HelmRelease{}},
|
object: helmReleaseAdapter{&helmv2.HelmRelease{}},
|
||||||
list: helmReleaseListAdapter{&helmv2.HelmReleaseList{}},
|
list: helmReleaseListAdapter{&helmv2.HelmReleaseList{}},
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha2"
|
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta1"
|
||||||
)
|
)
|
||||||
|
|
||||||
var exportImagePolicyCmd = &cobra.Command{
|
var exportImagePolicyCmd = &cobra.Command{
|
||||||
@@ -32,6 +32,7 @@ var exportImagePolicyCmd = &cobra.Command{
|
|||||||
|
|
||||||
# Export a specific policy
|
# Export a specific policy
|
||||||
flux export image policy alpine1x > alpine1x.yaml`,
|
flux export image policy alpine1x > alpine1x.yaml`,
|
||||||
|
ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImagePolicyKind)),
|
||||||
RunE: exportCommand{
|
RunE: exportCommand{
|
||||||
object: imagePolicyAdapter{&imagev1.ImagePolicy{}},
|
object: imagePolicyAdapter{&imagev1.ImagePolicy{}},
|
||||||
list: imagePolicyListAdapter{&imagev1.ImagePolicyList{}},
|
list: imagePolicyListAdapter{&imagev1.ImagePolicyList{}},
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha2"
|
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta1"
|
||||||
)
|
)
|
||||||
|
|
||||||
var exportImageRepositoryCmd = &cobra.Command{
|
var exportImageRepositoryCmd = &cobra.Command{
|
||||||
@@ -32,6 +32,7 @@ var exportImageRepositoryCmd = &cobra.Command{
|
|||||||
|
|
||||||
# Export a specific ImageRepository resource
|
# Export a specific ImageRepository resource
|
||||||
flux export image repository alpine > alpine.yaml`,
|
flux export image repository alpine > alpine.yaml`,
|
||||||
|
ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImageRepositoryKind)),
|
||||||
RunE: exportCommand{
|
RunE: exportCommand{
|
||||||
object: imageRepositoryAdapter{&imagev1.ImageRepository{}},
|
object: imageRepositoryAdapter{&imagev1.ImageRepository{}},
|
||||||
list: imageRepositoryListAdapter{&imagev1.ImageRepositoryList{}},
|
list: imageRepositoryListAdapter{&imagev1.ImageRepositoryList{}},
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
autov1 "github.com/fluxcd/image-automation-controller/api/v1alpha2"
|
autov1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
|
||||||
)
|
)
|
||||||
|
|
||||||
var exportImageUpdateCmd = &cobra.Command{
|
var exportImageUpdateCmd = &cobra.Command{
|
||||||
@@ -32,6 +32,7 @@ var exportImageUpdateCmd = &cobra.Command{
|
|||||||
|
|
||||||
# Export a specific automation
|
# Export a specific automation
|
||||||
flux export image update latest-images > latest.yaml`,
|
flux export image update latest-images > latest.yaml`,
|
||||||
|
ValidArgsFunction: resourceNamesCompletionFunc(autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)),
|
||||||
RunE: exportCommand{
|
RunE: exportCommand{
|
||||||
object: imageUpdateAutomationAdapter{&autov1.ImageUpdateAutomation{}},
|
object: imageUpdateAutomationAdapter{&autov1.ImageUpdateAutomation{}},
|
||||||
list: imageUpdateAutomationListAdapter{&autov1.ImageUpdateAutomationList{}},
|
list: imageUpdateAutomationListAdapter{&autov1.ImageUpdateAutomationList{}},
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
|
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var exportKsCmd = &cobra.Command{
|
var exportKsCmd = &cobra.Command{
|
||||||
@@ -33,6 +33,7 @@ var exportKsCmd = &cobra.Command{
|
|||||||
|
|
||||||
# Export a Kustomization
|
# Export a Kustomization
|
||||||
flux export kustomization my-app > kustomization.yaml`,
|
flux export kustomization my-app > kustomization.yaml`,
|
||||||
|
ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
|
||||||
RunE: exportCommand{
|
RunE: exportCommand{
|
||||||
object: kustomizationAdapter{&kustomizev1.Kustomization{}},
|
object: kustomizationAdapter{&kustomizev1.Kustomization{}},
|
||||||
list: kustomizationListAdapter{&kustomizev1.KustomizationList{}},
|
list: kustomizationListAdapter{&kustomizev1.KustomizationList{}},
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user